All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jakarta.faces.component.UISelectMany Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022, 2022 Contributors to Eclipse Foundation.
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package jakarta.faces.component;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

import jakarta.el.ValueExpression;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.convert.Converter;
import jakarta.faces.model.SelectItem;

/**
 * 

* UISelectMany 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 or Collection in the * value property of the component. *

* *

* This component is generally rendered as a select box or a group of checkboxes. *

* *

* By default, the rendererType property must be set to "jakarta.faces.Listbox". This value * can be changed by calling the setRendererType() method. *

* *

* The {@link jakarta.faces.render.Renderer} for this component must perform the following logic on * getConvertedValue(): *

* *
* *

* Obtain the {@link jakarta.faces.convert.Converter} using the following algorithm: *

* *
* *

* If the component has an attached {@link jakarta.faces.convert.Converter}, use it. *

* *

* If not, look for a {@link ValueExpression} for value (if any). The {@link ValueExpression} must point to * something that is: *

* *
    *
  • *

    * An array of primitives (such as int[]). Look up the registered by-class * {@link jakarta.faces.convert.Converter} for this primitive type. *

    *
  • * *
  • *

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

    *
  • * *
  • *

    * A java.util.Collection. Do not convert the values. Instead, convert * the provided set of available options to string, exactly as done during render response, and for any match with the * submitted values, add the available option as object to the collection. *

    *
  • *
*
* *

* If for any reason a Converter cannot be found, assume the type to be a String array. *

* *
* *

* Use the selected {@link jakarta.faces.convert.Converter} (if any) to convert each element in the values array from * the request to the proper type, and store the result of each conversion in a data * structure, called targetForConvertedValues for discussion. Create targetForConvertedValues using * the following algorithm. *

* *
* *
    * *
  • *

    * If the component has a ValueExpression for value and the type of the expression is an * array, let targetForConvertedValues be a new array of the expected type. *

    *
  • * * *
  • *

    * If the component has a ValueExpression for value, let modelType be the type of the * value expression. If modelType is a Collection, do the following to arrive at * targetForConvertedValues: *

    * *
      * *
    • *

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

      *
    • * *
    • *

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

      *
    • * *
    • *

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

      * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
      modelType to targetForConvertedValues mapping
      If modelType is an instance ofthen targetForConvertedValues must be an instance of
      SortedSetTreeSet
      QueueLinkedList
      SetHashSet
      anything elseArrayList
      * *
    • * *
    * *
  • *

    * If the component does not have a ValueExpression for value, let * targetForConvertedValues be an array of type Object. *

    * *
* *
* *

* Return targetForConvertedValues after populating it with the converted values. *

* * */ public class UISelectMany extends UIInput { // ------------------------------------------------------ Manifest Constants /** *

* The standard component type for this component. *

*/ public static final String COMPONENT_TYPE = "jakarta.faces.SelectMany"; /** *

* The standard component family for this component. *

*/ public static final String COMPONENT_FAMILY = "jakarta.faces.SelectMany"; /** *

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

* Create a new {@link UISelectMany} instance with default property values. *

*/ public UISelectMany() { super(); setRendererType("jakarta.faces.Listbox"); } // -------------------------------------------------------------- Properties @Override public String getFamily() { return COMPONENT_FAMILY; } private transient Object submittedValue = null; @Override public Object getSubmittedValue() { if (submittedValue == null && !isValid() && considerEmptyStringNull(FacesContext.getCurrentInstance())) { // JAVASERVERFACES_SPEC_PUBLIC-671 return new String[0]; // Mojarra#5081 } return submittedValue; } @Override public void setSubmittedValue(Object submittedValue) { this.submittedValue = submittedValue; } /** *

* Return the currently selected values, or null if there are no currently selected values. This is a * typesafe alias for getValue(). *

* * @return the selected values, or null. */ public Object[] getSelectedValues() { return (Object[]) getValue(); } /** *

* Set the currently selected values, or null to indicate that there are no currently selected values. This * is a typesafe alias for setValue(). *

* * @param selectedValues The new selected values (if any) */ public void setSelectedValues(Object[] selectedValues) { setValue(selectedValues); } // ---------------------------------------------------------------- Bindings /** *

* Return any {@link ValueExpression} set for value if a {@link ValueExpression} for * selectedValues is requested; otherwise, perform the default superclass processing for this method. *

* * @param name Name of the attribute or property for which to retrieve a {@link ValueExpression} * @return the value expression, or null. * @throws NullPointerException if name is null * @since 1.2 */ @Override public ValueExpression getValueExpression(String name) { if ("selectedValues".equals(name)) { return super.getValueExpression("value"); } else { return super.getValueExpression(name); } } /** *

* Store any {@link ValueExpression} specified for selectedValues under value instead; * otherwise, perform the default superclass processing for this method. *

* * @param name Name of the attribute or property for which to set a {@link ValueExpression} * @param binding The {@link ValueExpression} to set, or null to remove any currently set * {@link ValueExpression} * * @throws NullPointerException if name is null * @since 1.2 */ @Override public void setValueExpression(String name, ValueExpression binding) { if ("selectedValues".equals(name)) { super.setValueExpression("value", binding); } else { super.setValueExpression(name, binding); } } // --------------------------------------------------------- UIInput Methods /** *

* Return true if the new value is different from the previous value. Value comparison must not be * sensitive to element order. *

* * @param previous old value of this component * @param value new value of this component * @return true if the new value is different from the previous value, false otherwise. */ @Override 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 (Object element : oldarray) { count1 = countElementOccurrence(element, oldarray); count2 = countElementOccurrence(element, newarray); if (count1 != count2) { valueChanged = true; break; } } return valueChanged; } /** *

* Return the number of occurrences of a particular element in the array. *

* * @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 (Object arrayElement : array) { 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 /** *

* In 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 * valid property to false. *

* *

* This method must explicitly support a value argument that is a single value or a value argument that is a * Collection or Array of values. *

* *

* If {@link #isRequired} returns true, and the current value is equal to the value of an inner * {@link UISelectItem} whose {@link UISelectItem#isNoSelectionOption} method returns true, enqueue an * error message and set the valid property to false. *

* * @param context The {@link FacesContext} for the current request * * @param value The converted value to test for membership. * * @throws NullPointerException if context is null */ @Override 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(); } return new ArrayIterator(value); } // ---------------------------------------------------------- Nested Classes /** * Exposes an Array as an Iterator. */ private static final class ArrayIterator implements Iterator { private final int length; private final Object value; private int idx = 0; // -------------------------------------------------------- Constructors ArrayIterator(Object value) { this.value = value; length = Array.getLength(value); } // ------------------------------------------------------------ Iterator @Override public boolean hasNext() { return idx < length; } @Override public Object next() { if (idx >= length) { throw new NoSuchElementException(); } else { return Array.get(value, idx++); } } @Override public void remove() { throw new UnsupportedOperationException(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy