1 /* 2 * $Id: UISelectMany.java,v 1.63 2007/07/27 19:59:08 rlubke Exp $ 3 */ 4 5 /* 6 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 7 * 8 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. 9 * 10 * The contents of this file are subject to the terms of either the GNU 11 * General Public License Version 2 only ("GPL") or the Common Development 12 * and Distribution License("CDDL") (collectively, the "License"). You 13 * may not use this file except in compliance with the License. You can obtain 14 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html 15 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific 16 * language governing permissions and limitations under the License. 17 * 18 * When distributing the software, include this License Header Notice in each 19 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt. 20 * Sun designates this particular file as subject to the "Classpath" exception 21 * as provided by Sun in the GPL Version 2 section of the License file that 22 * accompanied this code. If applicable, add the following below the License 23 * Header, with the fields enclosed by brackets [] replaced by your own 24 * identifying information: "Portions Copyrighted [year] 25 * [name of copyright owner]" 26 * 27 * Contributor(s): 28 * 29 * If you wish your version of this file to be governed by only the CDDL or 30 * only the GPL Version 2, indicate your decision by adding "[Contributor] 31 * elects to include this software in this distribution under the [CDDL or GPL 32 * Version 2] license." If you don't indicate a single choice of license, a 33 * recipient has the option to distribute your version of this file under 34 * either the CDDL, the GPL Version 2 or to extend the choice of license to 35 * its licensees as provided above. However, if you add GPL Version 2 code 36 * and therefore, elected the GPL Version 2 license, then the option applies 37 * only if the new code is made subject to such option by the copyright 38 * holder. 39 */ 40 41 package javax.faces.component; 42 43 44 import java.lang.reflect.Array; 45 import java.util.Collection; 46 import java.util.Iterator; 47 import java.util.List; 48 import java.util.NoSuchElementException; 49 50 import javax.el.ValueExpression; 51 import javax.faces.application.FacesMessage; 52 import javax.faces.context.FacesContext; 53 import javax.faces.el.ValueBinding; 54 import javax.faces.convert.Converter; 55 56 57 /** 58 * <p><strong class="changed_modified_2_0">UISelectMany</strong> is a 59 * {@link UIComponent} that represents the user's choice of a zero or 60 * more items from among a discrete set of available options. The user 61 * can modify the selected values. Optionally, the component can be 62 * preconfigured with zero or more currently selected items, by storing 63 * them as an array <span class="changed_added_2_0">or 64 * <code>Collection</code></span> in the <code>value</code> property of 65 * the component.</p> 66 * 67 * <p>This component is generally rendered as a select box or a group of 68 * checkboxes.</p> 69 * 70 * <p>By default, the <code>rendererType</code> property must be set to 71 * "<code>javax.faces.Listbox</code>". This value can be changed by 72 * calling the <code>setRendererType()</code> method.</p> 73 * 74 * <p>The {@link javax.faces.render.Renderer} for this component must 75 * perform the following logic on <a 76 * name="#getConvertedValue"><code>getConvertedValue()</code></a>:</p> 77 * 78 * <ul> 79 * 80 * <p>Obtain the {@link javax.faces.convert.Converter} using the following algorithm:</p> 81 * 82 * <ul> 83 * 84 * <p>If the component has an attached {@link javax.faces.convert.Converter}, use it.</p> 85 * 86 * <p>If not, look for a {@link ValueExpression} for <code>value</code> 87 * (if any). The {@link ValueExpression} must point to something that 88 * is:</p> 89 * 90 * <ul> <li><p>An array of primitives (such as <code>int[]</code>). 91 * Look up the registered by-class {@link javax.faces.convert.Converter} 92 * for this primitive type.</p></li> 93 94 * <li><p>An array of objects (such as <code>Integer[]</code> or 95 * <code>String[]</code>). Look up the registered by-class {@link 96 * javax.faces.convert.Converter} for the underlying element 97 * type.</p></li> 98 99 * <li class="changed_added_2_0"><p>A <code>java.util.Collection</code>. 100 * Do not convert the values.</p></li> 101 102 * </ul> 103 * 104 * <p>If for any reason a <code>Converter</code> cannot be found, assume 105 * the type to be a String array.</p> 106 107 * </ul> 108 109 * <p>Use the selected {@link javax.faces.convert.Converter} (if any) to 110 * convert each element in the values array from the request to the 111 * proper type, <span class="changed_added_2_0">and store the result of 112 * each conversion in a data structure, called 113 * <em>targetForConvertedValues</em> for discussion. Create 114 * <em>targetForConvertedValues</em> using the following 115 * algorithm.</span></p> 116 117 * <div class="changed_added_2_0"> 118 119 * <ul> 120 121 * <li><p>If the component has a <code>ValueExpression</code> for 122 * <code>value</code> and the type of the expression is an array, let 123 * <em>targetForConvertedValues</em> be a new array of the expected 124 * type.</p></li> 125 126 127 * <li><p>If the component has a <code>ValueExpression</code> for 128 * <code>value</code>, let <em>modelType</em> be the type of the value 129 * expression. If <em>modelType</em> is a <code>Collection</code>, do 130 * the following to arrive at <em>targetForConvertedValues</em>:</p> 131 132 * <ul> 133 134 * <li><p>Ask the component for its attribute under the key 135 * "<code>collectionType</code>", without the quotes. If there is a 136 * value for that key, the value must be a String that is a fully 137 * qualified Java class name, or a <code>Class</code> object, or a 138 * <code>ValueExpression</code> that evaluates to a String or a 139 * <code>Class</code>. In all cases, the value serves to identify the 140 * concrete type of the class that implements <code>Collection</code>. 141 * For discussion, this is called <em>collectionType</em>. Let 142 * <em>targetForConvertedValues</em> be a new instance of 143 * <code>Collection</code> implemented by the concrete class specified 144 * in <em>collectionType</em>. If, <em>collectionType</em> can not be 145 * discovered, or an instance of <code>Collection</code> implemented by 146 * the concrete class specified in <em>collectionType</em> cannot be 147 * created, throw a {@link javax.faces.FacesException} with a correctly 148 * localized error message. Note that <code>FacesException</code> is 149 * thrown instead of <code>ConverterException</code> because this case 150 * would only arise from developer error, rather than end-user 151 * error.</p></li> 152 153 * <li><p>If there is no "<code>collectionType</code>" attribute, call 154 * <code>getValue()</code> on the component. The result will implement 155 * <code>Collection</code>. If the result also implements 156 * <code>Cloneable</code>, let <em>targetForConvertedValues</em> be the 157 * result of calling its <code>clone()</code> method, then calling 158 * <code>clear()</code> on the cloned <code>Collection</code>. If 159 * unable to clone the value for any reason, log a message and proceed 160 * to the next step.</p></li> 161 162 * <li><p>If <em>modelType</em> is a concrete class, let 163 * <em>targetForConvertedValues</em> be a new instance of that class. 164 * Otherwise, the concrete type for <em>targetForConvertedValues</em> is 165 * taken from the following table. All classes are in the 166 * <code>java.util</code> package. All collections must be created with 167 * an initial capacity equal to the length of the values array from the 168 * request.</p> 169 170 * <table border="1"> 171 172 * <tr> 173 174 * <th>If <em>modelType</em> is an instance of</th> 175 176 * <th>then <em>targetForConvertedValues</em> must be an instance 177 * of</th> 178 179 * </tr> 180 181 * <tr> 182 183 * <td><code>SortedSet</code></td> 184 185 * <td><code>TreeSet</code></td> 186 187 * </tr> 188 189 * <tr> 190 191 * <td><code>Queue</code></td> 192 193 * <td><code>LinkedList</code></td> 194 195 * </tr> 196 197 * <tr> 198 199 * <td><code>Set</code></td> 200 201 * <td><code>HashSet</code></td> 202 203 * </tr> 204 205 * <tr> 206 207 * <td>anything else</td> 208 209 * <td><code>ArrayList</code></td> 210 211 * </tr> 212 213 * </table> 214 215 * </li> 216 217 * </ul> 218 219 * <li><p>If the component does not have a <code>ValueExpression</code> 220 * for <code>value</code>, let <em>targetForConvertedValues</em> be an 221 * array of type <code>Object</code>.</p> 222 223 * </ul> 224 225 * </div> 226 227 * <p>Return <em>targetForConvertedValues</em> after populating it with 228 * the converted values.</p> 229 230 * </ul> 231 * 232 */ 233 234 public class UISelectMany extends UIInput { 235 236 237 // ------------------------------------------------------ Manifest Constants 238 239 240 /** 241 * <p>The standard component type for this component.</p> 242 */ 243 public static final String COMPONENT_TYPE = "javax.faces.SelectMany"; 244 245 246 /** 247 * <p>The standard component family for this component.</p> 248 */ 249 public static final String COMPONENT_FAMILY = "javax.faces.SelectMany"; 250 251 252 /** 253 * <p>The message identifier of the 254 * {@link javax.faces.application.FacesMessage} to be created if 255 * a value not matching the available options is specified. 256 */ 257 public static final String INVALID_MESSAGE_ID = 258 "javax.faces.component.UISelectMany.INVALID"; 259 260 261 // ------------------------------------------------------------ Constructors 262 263 264 /** 265 * <p>Create a new {@link UISelectMany} instance with default property 266 * values.</p> 267 */ 268 public UISelectMany() { 269 270 super(); 271 setRendererType("javax.faces.Listbox"); 272 273 } 274 275 276 // -------------------------------------------------------------- Properties 277 278 279 public String getFamily() { 280 281 return (COMPONENT_FAMILY); 282 283 } 284 285 286 /** 287 * <p>Return the currently selected values, or <code>null</code> if there 288 * are no currently selected values. This is a typesafe alias for 289 * <code>getValue()</code>.</p> 290 */ 291 public Object[] getSelectedValues() { 292 293 return ((Object[]) getValue()); 294 295 } 296 297 298 /** 299 * <p>Set the currently selected values, or <code>null</code> to indicate 300 * that there are no currently selected values. This is a typesafe 301 * alias for <code>setValue()</code>.</p> 302 * 303 * @param selectedValues The new selected values (if any) 304 */ 305 public void setSelectedValues(Object selectedValues[]) { 306 307 setValue(selectedValues); 308 309 } 310 311 312 // ---------------------------------------------------------------- Bindings 313 314 315 /** 316 * <p>Return any {@link ValueBinding} set for <code>value</code> if 317 * a {@link ValueBinding} for <code>selectedValues</code> is 318 * requested; otherwise, perform the default superclass processing 319 * for this method.</p> 320 * 321 * <p>This method relies on the superclass to provide the 322 * <code>ValueExpression</code> to <code>ValueBinding</code> 323 * wrapping.</p> 324 * 325 * @param name Name of the attribute or property for which to retrieve 326 * a {@link ValueBinding} 327 * 328 * @throws NullPointerException if <code>name</code> 329 * is <code>null</code> 330 * 331 * @deprecated this has been replaced by {@link #getValueExpression(java.lang.String)}. 332 */ 333 public ValueBinding getValueBinding(String name) { 334 335 if ("selectedValues".equals(name)) { 336 return (super.getValueBinding("value")); 337 } else { 338 return (super.getValueBinding(name)); 339 } 340 341 } 342 343 344 /** 345 * <p>Store any {@link ValueBinding} specified for 346 * <code>selectedValues</code> under <code>value</code> instead; 347 * otherwise, perform the default superclass processing for this 348 * method.</p> 349 * 350 * <p>This method relies on the superclass to wrap the argument 351 * <code>ValueBinding</code> in a <code>ValueExpression</code>.</p> 352 * 353 * @param name Name of the attribute or property for which to set 354 * a {@link ValueBinding} 355 * @param binding The {@link ValueBinding} to set, or <code>null</code> 356 * to remove any currently set {@link ValueBinding} 357 * 358 * @throws NullPointerException if <code>name</code> 359 * is <code>null</code> 360 * 361 * @deprecated This has been replaced by {@link #setValueExpression(java.lang.String, javax.el.ValueExpression)}. 362 */ 363 public void setValueBinding(String name, ValueBinding binding) { 364 365 if ("selectedValues".equals(name)) { 366 super.setValueBinding("value", binding); 367 } else { 368 super.setValueBinding(name, binding); 369 } 370 371 } 372 373 /** 374 * <p>Return any {@link ValueExpression} set for <code>value</code> if a 375 * {@link ValueExpression} for <code>selectedValues</code> is requested; 376 * otherwise, perform the default superclass processing for this method.</p> 377 * 378 * @param name Name of the attribute or property for which to retrieve 379 * a {@link ValueExpression} 380 * 381 * @throws NullPointerException if <code>name</code> 382 * is <code>null</code> 383 * @since 1.2 384 */ 385 public ValueExpression getValueExpression(String name) { 386 387 if ("selectedValues".equals(name)) { 388 return (super.getValueExpression("value")); 389 } else { 390 return (super.getValueExpression(name)); 391 } 392 393 } 394 395 /** 396 * <p>Store any {@link ValueExpression} specified for 397 * <code>selectedValues</code> under <code>value</code> instead; 398 * otherwise, perform the default superclass processing for this method.</p> 399 * 400 * @param name Name of the attribute or property for which to set 401 * a {@link ValueExpression} 402 * @param binding The {@link ValueExpression} to set, or <code>null</code> 403 * to remove any currently set {@link ValueExpression} 404 * 405 * @throws NullPointerException if <code>name</code> 406 * is <code>null</code> 407 * @since 1.2 408 */ 409 public void setValueExpression(String name, ValueExpression binding) { 410 411 if ("selectedValues".equals(name)) { 412 super.setValueExpression("value", binding); 413 } else { 414 super.setValueExpression(name, binding); 415 } 416 417 } 418 419 // --------------------------------------------------------- UIInput Methods 420 421 422 /** 423 * <p>Return <code>true</code> if the new value is different from the 424 * previous value. Value comparison must not be sensitive to element order. 425 * </p> 426 * 427 * @param previous old value of this component 428 * @param value new value of this component 429 */ 430 protected boolean compareValues(Object previous, Object value) { 431 432 if ((previous == null) && (value != null)) { 433 return (true); 434 } else if ((previous != null) && (value == null)) { 435 return (true); 436 } else if ((previous == null)) { 437 return (false); 438 } 439 440 boolean valueChanged = false; 441 Object oldarray[]; 442 Object newarray[]; 443 444 // The arrays may be arrays of primitives; for simplicity, 445 // perform the boxing here. 446 if (!(previous instanceof Object[])) { 447 previous = toObjectArray(previous); 448 } 449 450 if (!(value instanceof Object[])) { 451 value = toObjectArray(value); 452 } 453 454 // If values are still not of the type Object[], it is perhaps a 455 // mistake by the renderers, so return false, so that 456 // ValueChangedEvent is not queued in this case. 457 if (!(previous instanceof Object[]) || 458 !(value instanceof Object[])) { 459 return false; 460 } 461 oldarray = (Object[]) previous; 462 newarray = (Object[])value; 463 464 // If we got here then both the arrays cannot be null 465 // if their lengths vary, return false. 466 if ( oldarray.length != newarray.length) { 467 return true; 468 } 469 470 // make sure every element in the previous array occurs the same 471 // number of times in the current array. This should help us 472 // to find out the values changed are not. Since we cannot assume 473 // the browser will send the elements in the same order everytime, 474 // it will not suffice to just compare the element position and position. 475 int count1; 476 int count2; 477 for ( int i= 0; i < oldarray.length; ++i ) { 478 count1 = countElementOccurrence(oldarray[i], oldarray); 479 count2 = countElementOccurrence(oldarray[i], newarray); 480 if ( count1 != count2 ) { 481 valueChanged = true; 482 break; 483 } 484 } 485 return valueChanged; 486 487 } 488 489 490 /** 491 * <p>Return the number of occurrances of a particular element in the 492 * array.</p> 493 * 494 * @param element object whose occurrance is to be counted in the array. 495 * @param array object representing the old value of this component. 496 */ 497 private static int countElementOccurrence(Object element, Object[] array) { 498 499 int count = 0; 500 for ( int i= 0; i < array.length; ++i ) { 501 Object arrayElement = array[i]; 502 if (arrayElement != null && element != null) { 503 if (arrayElement.equals(element)) { 504 count ++; 505 } 506 } 507 } 508 return count; 509 510 } 511 512 513 /** 514 * Convert an array of primitives to an array of boxed objects. 515 * @param primitiveArray object containing the primitive values 516 * @return an Object array, or null if the incoming value is not 517 * in fact an array at all. 518 */ 519 private static Object[] toObjectArray(Object primitiveArray) { 520 if (primitiveArray == null) { 521 throw new NullPointerException(); 522 } 523 524 if (primitiveArray instanceof Object[]) { 525 return (Object[]) primitiveArray; 526 } 527 528 if (primitiveArray instanceof List) { 529 return ((List) primitiveArray).toArray(); 530 } 531 532 Class clazz = primitiveArray.getClass(); 533 if (!clazz.isArray()) { 534 return null; 535 } 536 537 int length = Array.getLength(primitiveArray); 538 Object[] array = new Object[length]; 539 for (int i = 0; i < length; i++) { 540 array[i] = Array.get(primitiveArray, i); 541 } 542 543 return array; 544 } 545 546 // ------------------------------------------------------ Validation Methods 547 548 549 550 /** 551 * <p><span class="changed_modified_2_0">In</span> addition to the standard 552 * validation behavior inherited from {@link UIInput}, ensure that 553 * any specified values are equal to one of the available options. 554 * Before comparing each option, coerce the option value type to the 555 * type of this component's value following the Expression Language 556 * coercion rules. If the specified value is not equal to any of 557 * the options, enqueue an error message and set the 558 * <code>valid</code> property to <code>false</code>.</p> 559 * 560 * <p class="changed_modified_2_0">This method must explicitly 561 * support a value argument that is a single value or a value 562 * argument that is a <code>Collection</code> or Array of 563 * values.</p> 564 565 * <p class="changed_added_2_0">If {@link #isRequired} returns 566 * <code>true</code>, and the current value is equal to the value of 567 * an inner {@link UISelectItem} whose {@link 568 * UISelectItem#isNoSelectionOption} method returns 569 * <code>true</code>, enqueue an error message and set the 570 * <code>valid</code> property to <code>false</code>.</p> 571 572 * @param context The {@link FacesContext} for the current request 573 * 574 * @param value The converted value to test for membership. 575 * 576 * @throws NullPointerException if <code>context</code> 577 * is <code>null</code> 578 */ 579 580 protected void validateValue(FacesContext context, Object value) { 581 super.validateValue(context, value); 582 583 // Skip validation if it is not necessary 584 if (!isValid() || (value == null)) { 585 return; 586 } 587 588 boolean doAddMessage = false; 589 590 // Ensure that the values match one of the available options 591 // Don't arrays cast to "Object[]", as we may now be using an array 592 // of primitives 593 Converter converter = getConverter(); 594 for (Iterator i = getValuesIterator(value); i.hasNext(); ) { 595 Iterator items = new SelectItemsIterator(context, this); 596 Object currentValue = i.next(); 597 if (!SelectUtils.matchValue(context, 598 this, 599 currentValue, 600 items, 601 converter)) { 602 doAddMessage = true; 603 break; 604 } 605 } 606 607 // Ensure that if the value is noSelection and a 608 // value is required, a message is queued 609 if (isRequired()) { 610 for (Iterator i = getValuesIterator(value); i.hasNext();) { 611 Iterator items = new SelectItemsIterator(context, this); 612 Object currentValue = i.next(); 613 if (SelectUtils.valueIsNoSelectionOption(context, 614 this, 615 currentValue, 616 items, 617 converter)) { 618 doAddMessage = true; 619 break; 620 } 621 } 622 } 623 624 if (doAddMessage) { 625 // Enqueue an error message if an invalid value was specified 626 FacesMessage message = 627 MessageFactory.getMessage(context, 628 INVALID_MESSAGE_ID, 629 MessageFactory.getLabel(context, this)); 630 context.addMessage(getClientId(context), message); 631 setValid(false); 632 } 633 634 } 635 636 637 // --------------------------------------------------------- Private Methods 638 639 640 private Iterator getValuesIterator(Object value) { 641 642 if (value instanceof Collection) { 643 return ((Collection) value).iterator(); 644 } else { 645 return (new ArrayIterator(value)); 646 } 647 648 } 649 650 651 // ---------------------------------------------------------- Nested Classes 652 653 654 /** 655 * Exposes an Array as an Iterator. 656 */ 657 private static final class ArrayIterator implements Iterator { 658 659 private int length; 660 private int idx = 0; 661 private Object value; 662 663 664 // -------------------------------------------------------- Constructors 665 666 667 ArrayIterator(Object value) { 668 669 this.value = value; 670 length = Array.getLength(value); 671 672 } 673 674 675 // ------------------------------------------------------------ Iterator 676 677 678 public boolean hasNext() { 679 return (idx < length); 680 } 681 682 683 public Object next() { 684 685 if (idx >= length) { 686 throw new NoSuchElementException(); 687 } else { 688 return Array.get(value, idx++); 689 } 690 691 } 692 693 694 public void remove() { 695 696 throw new UnsupportedOperationException(); 697 698 } 699 700 } 701 }