Home » Mojarra-2.0.1 » javax » faces » component » [javadoc | source]

    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   }

Home » Mojarra-2.0.1 » javax » faces » component » [javadoc | source]