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

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

Go to download

Jakarta Faces defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handing, input validation, page navigation, and support for internationalization and accessibility.

There is a newer version: 4.1.0
Show newest version
/*
 * Copyright (c) 1997, 2018 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 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.convert.Converter;
import javax.faces.el.ValueBinding;


/**
 * 

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 * "javax.faces.Listbox". This value can be changed by * calling the setRendererType() method.

* *

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

* *
* *

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

* *
* *

If the component has an attached {@link javax.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 javax.faces.convert.Converter} * for this primitive type.

  • *
  • An array of objects (such as Integer[] or * String[]). Look up the registered by-class {@link * javax.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 javax.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 javax.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 = "javax.faces.SelectMany"; /** *

The standard component family for this component.

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

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 /** *

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

*/ public UISelectMany() { super(); setRendererType("javax.faces.Listbox"); } // -------------------------------------------------------------- Properties @Override public String getFamily() { return (COMPONENT_FAMILY); } /** *

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 ValueBinding} set for value if * a {@link ValueBinding} for selectedValues is * requested; otherwise, perform the default superclass processing * for this method.

* *

This method relies on the superclass to provide the * ValueExpression to ValueBinding * wrapping.

* * @param name Name of the attribute or property for which to retrieve * a {@link ValueBinding} * @return the value binding, or null * @throws NullPointerException if name * is null * * @deprecated this has been replaced by {@link #getValueExpression(java.lang.String)}. */ @Override public ValueBinding getValueBinding(String name) { if ("selectedValues".equals(name)) { return (super.getValueBinding("value")); } else { return (super.getValueBinding(name)); } } /** *

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

* *

This method relies on the superclass to wrap the argument * ValueBinding in a ValueExpression.

* * @param name Name of the attribute or property for which to set * a {@link ValueBinding} * @param binding The {@link ValueBinding} to set, or null * to remove any currently set {@link ValueBinding} * * @throws NullPointerException if name * is null * * @deprecated This has been replaced by {@link #setValueExpression(java.lang.String, javax.el.ValueExpression)}. */ @Override public void setValueBinding(String name, ValueBinding binding) { if ("selectedValues".equals(name)) { super.setValueBinding("value", binding); } else { super.setValueBinding(name, binding); } } /** *

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 ( 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; } /** *

Return the number of occurrances 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 ( 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 /** *

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(); } 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 @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