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