/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.faces.component;


import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import javax.el.ValueExpression;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.convert.Converter;


/**
 * <p><strong class="changed_modified_2_0">UISelectMany</strong> is a
 * {@link UIComponent} that represents the user's choice of a zero or
 * more items from among a discrete set of available options.  The user
 * can modify the selected values.  Optionally, the component can be
 * preconfigured with zero or more currently selected items, by storing
 * them as an array <span class="changed_added_2_0">or
 * <code>Collection</code></span> in the <code>value</code> property of
 * the component.</p>
 *
 * <p>This component is generally rendered as a select box or a group of
 * checkboxes.</p>
 *
 * <p>By default, the <code>rendererType</code> property must be set to
 * "<code>javax.faces.Listbox</code>".  This value can be changed by
 * calling the <code>setRendererType()</code> method.</p>
 *
 * <p>The {@link javax.faces.render.Renderer} for this component must
 * perform the following logic on <a
 * name="#getConvertedValue"><code>getConvertedValue()</code></a>:</p>
 *
 * <ul>
 *
 * <p>Obtain the {@link javax.faces.convert.Converter} using the following algorithm:</p>
 *
 * <ul> 
 *
 * <p>If the component has an attached {@link javax.faces.convert.Converter}, use it.</p>
 *
 * <p>If not, look for a {@link ValueExpression} for <code>value</code>
 * (if any).  The {@link ValueExpression} must point to something that
 * is:</p>
 *
 * <ul> <li><p>An array of primitives (such as <code>int[]</code>).
 * Look up the registered by-class {@link javax.faces.convert.Converter}
 * for this primitive type.</p></li> 

 * <li><p>An array of objects (such as <code>Integer[]</code> or
 * <code>String[]</code>).  Look up the registered by-class {@link
 * javax.faces.convert.Converter} for the underlying element
 * type.</p></li> 

 * <li class="changed_added_2_0"><p>A <code>java.util.Collection</code>.
 * Do not convert the values.</p></li>

 * </ul>
 *
 * <p>If for any reason a <code>Converter</code> cannot be found, assume
 * the type to be a String array.</p>

 * </ul>

 * <p>Use the selected {@link javax.faces.convert.Converter} (if any) to
 * convert each element in the values array from the request to the
 * proper type, <span class="changed_added_2_0">and store the result of
 * each conversion in a data structure, called
 * <em>targetForConvertedValues</em> for discussion.  Create
 * <em>targetForConvertedValues</em> using the following
 * algorithm.</span></p>

 * <div class="changed_added_2_0">

 * <ul>

 * <li><p>If the component has a <code>ValueExpression</code> for
 * <code>value</code> and the type of the expression is an array, let
 * <em>targetForConvertedValues</em> be a new array of the expected
 * type.</p></li>


 * <li><p>If the component has a <code>ValueExpression</code> for
 * <code>value</code>, let <em>modelType</em> be the type of the value
 * expression.  If <em>modelType</em> is a <code>Collection</code>, do
 * the following to arrive at <em>targetForConvertedValues</em>:</p>

 * <ul>

 * <li><p>Ask the component for its attribute under the key
 * "<code>collectionType</code>", without the quotes.  If there is a
 * value for that key, the value must be a String that is a fully
 * qualified Java class name, or a <code>Class</code> object, or a
 * <code>ValueExpression</code> that evaluates to a String or a
 * <code>Class</code>.  In all cases, the value serves to identify the
 * concrete type of the class that implements <code>Collection</code>.
 * For discussion, this is called <em>collectionType</em>.  Let
 * <em>targetForConvertedValues</em> be a new instance of
 * <code>Collection</code> implemented by the concrete class specified
 * in <em>collectionType</em>.  If, <em>collectionType</em> can not be
 * discovered, or an instance of <code>Collection</code> implemented by
 * the concrete class specified in <em>collectionType</em> cannot be
 * created, throw a {@link javax.faces.FacesException} with a correctly
 * localized error message.  Note that <code>FacesException</code> is
 * thrown instead of <code>ConverterException</code> because this case
 * would only arise from developer error, rather than end-user
 * error.</p></li>

 * <li><p>If there is no "<code>collectionType</code>" attribute, call
 * <code>getValue()</code> on the component.  The result will implement
 * <code>Collection</code>.  If the result also implements
 * <code>Cloneable</code>, let <em>targetForConvertedValues</em> be the
 * result of calling its <code>clone()</code> method, then calling
 * <code>clear()</code> on the cloned <code>Collection</code>.  If
 * unable to clone the value for any reason, log a message and proceed
 * to the next step.</p></li>

 * <li><p>If <em>modelType</em> is a concrete class, let
 * <em>targetForConvertedValues</em> be a new instance of that class.
 * Otherwise, the concrete type for <em>targetForConvertedValues</em> is
 * taken from the following table.  All classes are in the
 * <code>java.util</code> package.  All collections must be created with
 * an initial capacity equal to the length of the values array from the
 * request.</p>

 * <table border="1">

 * <tr>

 * <th>If <em>modelType</em> is an instance of</th>
 
 * <th>then <em>targetForConvertedValues</em> must be an instance
 * of</th>

 * </tr>

 * <tr>

 * <td><code>SortedSet</code></td>

 * <td><code>TreeSet</code></td>

 * </tr>

 * <tr>

 * <td><code>Queue</code></td>

 * <td><code>LinkedList</code></td>

 * </tr>

 * <tr>

 * <td><code>Set</code></td>

 * <td><code>HashSet</code></td>

 * </tr>

 * <tr>

 * <td>anything else</td>

 * <td><code>ArrayList</code></td>

 * </tr>

 * </table>

 * </li>

 * </ul>

 * <li><p>If the component does not have a <code>ValueExpression</code>
 * for <code>value</code>, let <em>targetForConvertedValues</em> be an
 * array of type <code>Object</code>.</p>

 * </ul>

 * </div>

 * <p>Return <em>targetForConvertedValues</em> after populating it with
 * the converted values.</p>

 * </ul>
 *
 */

public class UISelectMany extends UIInput {


    // ------------------------------------------------------ Manifest Constants


    /**
     * <p>The standard component type for this component.</p>
     */
    public static final String COMPONENT_TYPE = "javax.faces.SelectMany";


    /**
     * <p>The standard component family for this component.</p>
     */
    public static final String COMPONENT_FAMILY = "javax.faces.SelectMany";


    /**
     * <p>The message identifier of the
     * {@link javax.faces.application.FacesMessage} to be created if
     * a value not matching the available options is specified.
     */
    public static final String INVALID_MESSAGE_ID =
        "javax.faces.component.UISelectMany.INVALID";


    // ------------------------------------------------------------ Constructors


    /**
     * <p>Create a new {@link UISelectMany} instance with default property
     * values.</p>
     */
    public UISelectMany() {

        super();
        setRendererType("javax.faces.Listbox");

    }


    // -------------------------------------------------------------- Properties


    public String getFamily() {

        return (COMPONENT_FAMILY);

    }


    /**
     * <p>Return the currently selected values, or <code>null</code> if there
     * are no currently selected values.  This is a typesafe alias for
     * <code>getValue()</code>.</p>
     */
    public Object[] getSelectedValues() {

        return ((Object[]) getValue());

    }


    /**
     * <p>Set the currently selected values, or <code>null</code> to indicate
     * that there are no currently selected values.  This is a typesafe
     * alias for <code>setValue()</code>.</p>
     *
     * @param selectedValues The new selected values (if any)
     */
    public void setSelectedValues(Object selectedValues[]) {

        setValue(selectedValues);

    }


    // ---------------------------------------------------------------- Bindings


    /**
     * <p>Return any {@link ValueBinding} set for <code>value</code> if
     * a {@link ValueBinding} for <code>selectedValues</code> is
     * requested; otherwise, perform the default superclass processing
     * for this method.</p>
     *
     * <p>This method relies on the superclass to provide the
     * <code>ValueExpression</code> to <code>ValueBinding</code>
     * wrapping.</p>
     *
     * @param name Name of the attribute or property for which to retrieve
     *  a {@link ValueBinding}
     *
     * @throws NullPointerException if <code>name</code>
     *  is <code>null</code>
     *
     * @deprecated this has been replaced by {@link #getValueExpression(java.lang.String)}.
     */
    public ValueBinding getValueBinding(String name) {

        if ("selectedValues".equals(name)) {
            return (super.getValueBinding("value"));
        } else {
            return (super.getValueBinding(name));
        }

    }


    /**
     * <p>Store any {@link ValueBinding} specified for
     * <code>selectedValues</code> under <code>value</code> instead;
     * otherwise, perform the default superclass processing for this
     * method.</p>
     *
     * <p>This method relies on the superclass to wrap the argument
     * <code>ValueBinding</code> in a <code>ValueExpression</code>.</p>
     *
     * @param name Name of the attribute or property for which to set
     *  a {@link ValueBinding}
     * @param binding The {@link ValueBinding} to set, or <code>null</code>
     *  to remove any currently set {@link ValueBinding}
     *
     * @throws NullPointerException if <code>name</code>
     *  is <code>null</code>
     *
     * @deprecated This has been replaced by {@link #setValueExpression(java.lang.String, javax.el.ValueExpression)}.
     */
    public void setValueBinding(String name, ValueBinding binding) {

        if ("selectedValues".equals(name)) {
            super.setValueBinding("value", binding);
        } else {
            super.setValueBinding(name, binding);
        }

    }

    /**
     * <p>Return any {@link ValueExpression} set for <code>value</code> if a
     * {@link ValueExpression} for <code>selectedValues</code> is requested;
     * otherwise, perform the default superclass processing for this method.</p>
     *
     * @param name Name of the attribute or property for which to retrieve
     *  a {@link ValueExpression}
     *
     * @throws NullPointerException if <code>name</code>
     *  is <code>null</code>
     * @since 1.2
     */
    public ValueExpression getValueExpression(String name) {

        if ("selectedValues".equals(name)) {
            return (super.getValueExpression("value"));
        } else {
            return (super.getValueExpression(name));
        }

    }
    
    /**
     * <p>Store any {@link ValueExpression} specified for
     * <code>selectedValues</code> under <code>value</code> instead;
     * otherwise, perform the default superclass processing for this method.</p>
     *
     * @param name Name of the attribute or property for which to set
     *  a {@link ValueExpression}
     * @param binding The {@link ValueExpression} to set, or <code>null</code>
     *  to remove any currently set {@link ValueExpression}
     *
     * @throws NullPointerException if <code>name</code>
     *  is <code>null</code>
     * @since 1.2
     */
    public void setValueExpression(String name, ValueExpression binding) {

        if ("selectedValues".equals(name)) {
            super.setValueExpression("value", binding);
        } else {
            super.setValueExpression(name, binding);
        }

    }
    
    // --------------------------------------------------------- UIInput Methods


    /**
     * <p>Return <code>true</code> if the new value is different from the
     * previous value. Value comparison must not be sensitive to element order.
     * </p>
     *
     * @param previous old value of this component
     * @param value new value of this component
     */
    protected boolean compareValues(Object previous, Object value) {

        if ((previous == null) && (value != null)) {
            return (true);
        } else if ((previous != null) && (value == null)) {
            return (true);
        } else if ((previous == null)) {
            return (false);
        }

        boolean valueChanged = false;
        Object oldarray[];
        Object newarray[];

        // The arrays may be arrays of primitives;  for simplicity,
        // perform the boxing here.
        if (!(previous instanceof Object[])) {
            previous = toObjectArray(previous);
        }

        if (!(value instanceof Object[])) {
            value = toObjectArray(value);
        }

        // If values are still not of the type Object[], it is perhaps a
        // mistake by the renderers, so return false, so that
        // ValueChangedEvent is not queued in this case.
        if (!(previous instanceof Object[]) || 
              !(value instanceof Object[])) {
              return false;
        }
        oldarray = (Object[]) previous;
        newarray = (Object[])value;
       
        // If we got here then both the arrays cannot be null
        // if their lengths vary, return false.
        if ( oldarray.length != newarray.length) {
            return true;
        }
        
        // make sure every element in the previous array occurs the same
        // number of times in the current array. This should help us
        // to find out the values changed are not. Since we cannot assume
        // the browser will send the elements in the same order everytime,
        // it will not suffice to just compare the element position and position.
        int count1;
        int count2;
        for ( int i= 0; i < oldarray.length; ++i ) {
            count1 = countElementOccurrence(oldarray[i], oldarray);
            count2 = countElementOccurrence(oldarray[i], newarray);
            if ( count1 != count2 ) {
                valueChanged = true;
                break;
            }     
        }    
        return valueChanged;

    }    

    
    /**
     * <p>Return the number of occurrances of a particular element in the
     * array.</p>
     *
     * @param element object whose occurrance is to be counted in the array.
     * @param array object representing the old value of this component.
     */
    private static int countElementOccurrence(Object element, Object[] array) {

        int count = 0;
        for ( int i= 0; i < array.length; ++i ) {
            Object arrayElement = array[i];
            if (arrayElement != null && element != null) {
                if (arrayElement.equals(element)) {
                    count ++;
                }
            }
        }    
        return count;

    }    

    
    /**
     * Convert an array of primitives to an array of boxed objects.
     * @param primitiveArray object containing the primitive values
     * @return an Object array, or null if the incoming value is not
     * in fact an array at all.
     */
    private static Object[] toObjectArray(Object primitiveArray) {
        if (primitiveArray == null) {
            throw new NullPointerException();
        }
        
        if (primitiveArray instanceof Object[]) {
            return (Object[]) primitiveArray;
        }

        if (primitiveArray instanceof Collection) {
            return ((Collection) primitiveArray).toArray();
        }
          
        Class clazz = primitiveArray.getClass();
        if (!clazz.isArray()) {
            return null;
        }

        int length = Array.getLength(primitiveArray);
        Object[] array = new Object[length];
        for (int i = 0; i < length; i++) {
            array[i] = Array.get(primitiveArray, i);
        }

        return array;
    }

    // ------------------------------------------------------ Validation Methods

    

    /**
     * <p><span class="changed_modified_2_0">In</span> addition to the standard
     * validation behavior inherited from {@link UIInput}, ensure that
     * any specified values are equal to one of the available options.
     * Before comparing each option, coerce the option value type to the
     * type of this component's value following the Expression Language
     * coercion rules.  If the specified value is not equal to any of
     * the options, enqueue an error message and set the
     * <code>valid</code> property to <code>false</code>.</p>
     *
     * <p class="changed_modified_2_0">This method must explicitly
     * support a value argument that is a single value or a value
     * argument that is a <code>Collection</code> or Array of
     * values.</p>

     * <p class="changed_added_2_0">If {@link #isRequired} returns
     * <code>true</code>, and the current value is equal to the value of
     * an inner {@link UISelectItem} whose {@link
     * UISelectItem#isNoSelectionOption} method returns
     * <code>true</code>, enqueue an error message and set the
     * <code>valid</code> property to <code>false</code>.</p>

     * @param context The {@link FacesContext} for the current request
     *
     * @param value The converted value to test for membership.
     *
     * @throws NullPointerException if <code>context</code>
     *  is <code>null</code>
     */

    protected void validateValue(FacesContext context, Object value) {
        super.validateValue(context, value);

        // Skip validation if it is not necessary
        if (!isValid() || (value == null)) {
            return;
        }
        
        boolean doAddMessage = false;

        // Ensure that the values match one of the available options
        // Don't arrays cast to "Object[]", as we may now be using an array
        // of primitives
        Converter converter = getConverter();
        for (Iterator i = getValuesIterator(value); i.hasNext(); ) {
            Iterator items = new SelectItemsIterator(context, this);
            Object currentValue = i.next();
            if (!SelectUtils.matchValue(context,
                                        this,
                                        currentValue,
                                        items,
                                        converter)) {
                doAddMessage = true;
                break;
            }
        }
        
        // Ensure that if the value is noSelection and a
        // value is required, a message is queued
        if (isRequired()) {
            for (Iterator i = getValuesIterator(value); i.hasNext();) {
                Iterator items = new SelectItemsIterator(context, this);
                Object currentValue = i.next();
                if (SelectUtils.valueIsNoSelectionOption(context,
                        this,
                        currentValue,
                        items,
                        converter)) {
                    doAddMessage = true;
                    break;
                }
            }
        }
        
        if (doAddMessage) {
            // Enqueue an error message if an invalid value was specified
            FacesMessage message =
                    MessageFactory.getMessage(context,
                    INVALID_MESSAGE_ID,
                    MessageFactory.getLabel(context, this));
            context.addMessage(getClientId(context), message);
            setValid(false);
        }

    }


    // --------------------------------------------------------- Private Methods


    private Iterator getValuesIterator(Object value) {

        if (value instanceof Collection) {
            return ((Collection) value).iterator();
        } else {
            return (new ArrayIterator(value));
        }

    }


    // ---------------------------------------------------------- Nested Classes


    /**
     * Exposes an Array as an Iterator.
     */
    private static final class ArrayIterator implements Iterator {

        private int length;
        private int idx = 0;
        private Object value;


        // -------------------------------------------------------- Constructors


        ArrayIterator(Object value) {

            this.value = value;
            length = Array.getLength(value);

        }


        // ------------------------------------------------------------ Iterator


        public boolean hasNext() {
            return (idx < length);
        }


        public Object next() {

            if (idx >= length) {
                throw new NoSuchElementException();
            } else {
                return Array.get(value, idx++);
            }
            
        }


        public void remove() {

            throw new UnsupportedOperationException();

        }

    }
}
