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.Arrays;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.Iterator;
27  import java.util.List;
28  import javax.el.ValueExpression;
29  import javax.faces.application.FacesMessage;
30  import javax.faces.context.FacesContext;
31  import javax.faces.convert.ConverterException;
32  import javax.faces.el.ValueBinding;
33  import javax.faces.render.Renderer;
34  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
35  
36  /**
37   * Base class for the various component classes that allow a user to select zero or more options
38   * from a set.
39   * <p>
40   * This is not an abstract class; java code can create an instance of this, configure its
41   * rendererType appropriately, and add it to a view. However there is no tag class for this
42   * component, ie it cannot be added directly to a page when using JSP (and other presentation
43   * technologies are expected to behave similarly). Instead, there is a family of subclasses that
44   * extend this base functionality, and they do have tag classes.
45   * </p>
46   * <p>
47   * See the javadoc for this class in the
48   * <a href="http://java.sun.com/j2ee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a>
49   * for further details.
50   * </p>
51   */
52  @JSFComponent(defaultRendererType = "javax.faces.Listbox")
53  public class UISelectMany extends UIInput
54  {
55      public static final String COMPONENT_TYPE = "javax.faces.SelectMany";
56      public static final String COMPONENT_FAMILY = "javax.faces.SelectMany";
57  
58      public static final String INVALID_MESSAGE_ID = "javax.faces.component.UISelectMany.INVALID";
59  
60      public UISelectMany()
61      {
62          setRendererType("javax.faces.Listbox");
63      }
64  
65      @Override
66      public String getFamily()
67      {
68          return COMPONENT_FAMILY;
69      }
70  
71      public Object[] getSelectedValues()
72      {
73          return (Object[]) getValue();
74      }
75  
76      public void setSelectedValues(Object[] selectedValues)
77      {
78          setValue(selectedValues);
79      }
80  
81      /**
82       * @deprecated Use getValueExpression instead
83       */
84      public ValueBinding getValueBinding(String name)
85      {
86          if (name == null)
87          {
88              throw new NullPointerException("name");
89          }
90          if (name.equals("selectedValues"))
91          {
92              return super.getValueBinding("value");
93          }
94          else
95          {
96              return super.getValueBinding(name);
97          }
98      }
99  
100     /**
101      * @deprecated Use setValueExpression instead
102      */
103     public void setValueBinding(String name, ValueBinding binding)
104     {
105         if (name == null)
106         {
107             throw new NullPointerException("name");
108         }
109         if (name.equals("selectedValues"))
110         {
111             super.setValueBinding("value", binding);
112         }
113         else
114         {
115             super.setValueBinding(name, binding);
116         }
117     }
118 
119     public ValueExpression getValueExpression(String name)
120     {
121         if (name == null)
122         {
123             throw new NullPointerException("name");
124         }
125         if (name.equals("selectedValues"))
126         {
127             return super.getValueExpression("value");
128         }
129         else
130         {
131             return super.getValueExpression(name);
132         }
133     }
134 
135     public void setValueExpression(String name, ValueExpression binding)
136     {
137         if (name == null)
138         {
139             throw new NullPointerException("name");
140         }
141         if (name.equals("selectedValues"))
142         {
143             super.setValueExpression("value", binding);
144         }
145         else
146         {
147             super.setValueExpression(name, binding);
148         }
149     }
150 
151     /**
152      * @return true if Objects are different (!)
153      */
154     protected boolean compareValues(Object previous, Object value)
155     {
156         if (previous == null)
157         {
158             // one is null, the other not
159             return value != null;
160         }
161         else if (value == null)
162         {
163             // one is null, the other not
164             return previous != null;
165         }
166         else
167         {
168             if (previous instanceof Object[] && value instanceof Object[])
169             {
170                 return compareObjectArrays((Object[]) previous, (Object[]) value);
171             }
172             else if (previous instanceof List && value instanceof List)
173             {
174                 return compareLists((List) previous, (List) value);
175             }
176             else if (previous.getClass().isArray() && value.getClass().isArray())
177             {
178                 return comparePrimitiveArrays(previous, value);
179             }
180             else
181             {
182                 // Objects have different classes
183                 return true;
184             }
185         }
186     }
187 
188     private boolean compareObjectArrays(Object[] previous, Object[] value)
189     {
190         int length = value.length;
191         if (previous.length != length)
192         {
193             // different length
194             return true;
195         }
196 
197         boolean[] scoreBoard = new boolean[length];
198         for (int i = 0; i < length; i++)
199         {
200             Object p = previous[i];
201             boolean found = false;
202             for (int j = 0; j < length; j++)
203             {
204                 if (scoreBoard[j] == false)
205                 {
206                     Object v = value[j];
207                     if ((p == null && v == null) || (p != null && v != null && p.equals(v)))
208                     {
209                         scoreBoard[j] = true;
210                         found = true;
211                         break;
212                     }
213                 }
214             }
215             if (!found)
216             {
217                 return true; // current element of previous array not found in new array
218             }
219         }
220 
221         return false; // arrays are identical
222     }
223 
224     private boolean compareLists(List previous, List value)
225     {
226         int length = value.size();
227         if (previous.size() != length)
228         {
229             // different length
230             return true;
231         }
232 
233         boolean[] scoreBoard = new boolean[length];
234         for (int i = 0; i < length; i++)
235         {
236             Object p = previous.get(i);
237             boolean found = false;
238             for (int j = 0; j < length; j++)
239             {
240                 if (scoreBoard[j] == false)
241                 {
242                     Object v = value.get(j);
243                     if ((p == null && v == null) || (p != null && v != null && p.equals(v)))
244                     {
245                         scoreBoard[j] = true;
246                         found = true;
247                         break;
248                     }
249                 }
250             }
251             if (!found)
252             {
253                 return true; // current element of previous List not found in new List
254             }
255         }
256 
257         return false; // Lists are identical
258     }
259 
260     private boolean comparePrimitiveArrays(Object previous, Object value)
261     {
262         int length = Array.getLength(value);
263         if (Array.getLength(previous) != length)
264         {
265             // different length
266             return true;
267         }
268 
269         boolean[] scoreBoard = new boolean[length];
270         for (int i = 0; i < length; i++)
271         {
272             Object p = Array.get(previous, i);
273             boolean found = false;
274             for (int j = 0; j < length; j++)
275             {
276                 if (scoreBoard[j] == false)
277                 {
278                     Object v = Array.get(value, j);
279                     if ((p == null && v == null) || (p != null && v != null && p.equals(v)))
280                     {
281                         scoreBoard[j] = true;
282                         found = true;
283                         break;
284                     }
285                 }
286             }
287             if (!found)
288             {
289                 return true; // current element of previous array not found in new array
290             }
291         }
292 
293         return false; // arrays are identical
294     }
295 
296     protected void validateValue(FacesContext context, Object convertedValue)
297     {
298         Iterator itemValues = _createItemValuesIterator(convertedValue);
299 
300         // verify that iterator was successfully created for convertedValue type
301         if (itemValues == null)
302         {
303             _MessageUtils.addErrorMessage(context, this, INVALID_MESSAGE_ID, new Object[]
304             { _MessageUtils.getLabel(context, this) });
305             setValid(false);
306             return;
307         }
308 
309         boolean hasValues = itemValues.hasNext();
310 
311         // if UISelectMany is required, then there must be some selected values
312         if (isRequired() && !hasValues)
313         {
314             if (getRequiredMessage() != null)
315             {
316                 String requiredMessage = getRequiredMessage();
317                 context.addMessage(this.getClientId(context), new FacesMessage(
318                         FacesMessage.SEVERITY_ERROR, requiredMessage, requiredMessage));
319             }
320             else
321             {
322                 _MessageUtils.addErrorMessage(context, this, REQUIRED_MESSAGE_ID, new Object[]
323                 { _MessageUtils.getLabel(context, this) });
324             }
325             setValid(false);
326             return;
327         }
328 
329         // run the validators only if there are item values to validate
330         if (hasValues)
331         {
332             _ComponentUtils.callValidators(context, this, convertedValue);
333         }
334 
335         if (isValid() && hasValues)
336         {
337             // all selected values must match to the values of the available options
338 
339             _SelectItemsUtil._ValueConverter converter = new _SelectItemsUtil._ValueConverter()
340             {
341                 public Object getConvertedValue(FacesContext context, String value)
342                 {
343                     Object convertedValue = UISelectMany.this.getConvertedValue(context,
344                             new String[]
345                             { value });
346                     if (convertedValue instanceof Collection)
347                     {
348                         Iterator iter = ((Collection) convertedValue).iterator();
349                         if (iter.hasNext())
350                         {
351                             return iter.next();
352                         }
353                         return null;
354                     }
355                     return ((Object[]) convertedValue)[0];
356                 }
357             };
358 
359             Collection items = new ArrayList();
360             for (Iterator iter = new _SelectItemsIterator(this); iter.hasNext();)
361             {
362                 items.add(iter.next());
363             }
364             while (itemValues.hasNext())
365             {
366                 Object itemValue = itemValues.next();
367 
368                 if (!_SelectItemsUtil.matchValue(context, itemValue, items.iterator(), converter))
369                 {
370                     _MessageUtils.addErrorMessage(context, this, INVALID_MESSAGE_ID, new Object[]
371                     { _MessageUtils.getLabel(context, this) });
372                     setValid(false);
373                     return;
374                 }
375             }
376         }
377     }
378 
379     /**
380      * First part is identical to super.validate except the empty condition. Second part: iterate
381      * through UISelectItem and UISelectItems and check current values against these items
382      */
383     public void validate(FacesContext context)
384     {
385         // TODO : Setting the submitted value to null in the super class causes a bug, if set to
386         // null, you'll get the following error :
387         // java.lang.NullPointerException at
388         // org.apache.myfaces.renderkit._SharedRendererUtils.getConvertedUISelectManyValue(_SharedRendererUtils.java:118)
389         super.validate(context);
390     }
391 
392     protected Object getConvertedValue(FacesContext context, Object submittedValue)
393     {
394         try
395         {
396             Renderer renderer = getRenderer(context);
397             if (renderer != null)
398             {
399                 return renderer.getConvertedValue(context, this, submittedValue);
400             }
401             else if (submittedValue == null)
402             {
403                 return null;
404             }
405             else if (submittedValue instanceof String[])
406             {
407                 return _SharedRendererUtils.getConvertedUISelectManyValue(context, this,
408                         (String[]) submittedValue);
409             }
410         }
411         catch (ConverterException e)
412         {
413             FacesMessage facesMessage = e.getFacesMessage();
414             if (facesMessage != null)
415             {
416                 context.addMessage(getClientId(context), facesMessage);
417             }
418             else
419             {
420                 _MessageUtils.addErrorMessage(context, this, CONVERSION_MESSAGE_ID, new Object[]
421                 { _MessageUtils.getLabel(context, this) });
422             }
423             setValid(false);
424         }
425         return submittedValue;
426     }
427 
428     private Iterator _createItemValuesIterator(Object convertedValue)
429     {
430         if (convertedValue == null)
431         {
432             return Collections.EMPTY_LIST.iterator();
433         }
434         else
435         {
436             Class valueClass = convertedValue.getClass();
437             if (valueClass.isArray())
438             {
439                 return new _PrimitiveArrayIterator(convertedValue);
440             }
441             else if (convertedValue instanceof Object[])
442             {
443                 Object[] values = (Object[]) convertedValue;
444                 return Arrays.asList(values).iterator();
445             }
446             else if (convertedValue instanceof List)
447             {
448                 List values = (List) convertedValue;
449                 return values.iterator();
450             }
451             else
452             {
453                 // unsupported type for iteration
454                 return null;
455             }
456         }
457     }
458 }