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