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