javax.faces.component.UISelectMany Maven / Gradle / Ivy
/*
* $Id: UISelectMany.java,v 1.63.4.1 2008/09/25 16:29:44 rlubke Exp $
*/
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. 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.html
* or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [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.Iterator;
import java.util.List;
import javax.el.ValueExpression;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.convert.Converter;
/**
* 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 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.List
. Assume that the element type is
* java.lang.String
, so no conversion is required.
*
*
* 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 or list from the request to the proper type. If the component
* has a {@link ValueBinding} for value
, create an array
* of the expected type to hold the converted values. If the component
* does not have a {@link ValueBinding} for value
, create
* an array of type Object
. Store the created array
* as the local value of the component, set the component's valid
* state to true
and return.
*
*
*
*/
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
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()
.
*/
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}
*
* @throws NullPointerException if name
* is null
*
* @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));
}
}
/**
* 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)}.
*/
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}
*
* @throws NullPointerException if name
* is null
* @since 1.2
*/
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
*/
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
*/
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 List) {
return ((List) 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
.
*
* @param context The {@link FacesContext} for the current request
*
* @param value The converted value to test for membership.
*
* @throws NullPointerException if context
* is null
*/
protected void validateValue(FacesContext context, Object value) {
super.validateValue(context, value);
// Skip validation if it is not necessary
if (!isValid() || (value == null)) {
return;
}
// 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
boolean isList = (value instanceof List);
int length = isList ? ((List) value).size() : Array.getLength(value);
boolean found = true;
Converter converter = getConverter();
FacesContext ctx = getFacesContext();
for (int i = 0; i < length; i++) {
Iterator items = new SelectItemsIterator(this);
Object indexValue = isList ?
((List) value).get(i) : Array.get(value, i);
found = SelectUtils.matchValue(ctx, this, indexValue, items, converter);
if (!found) {
break;
}
}
// Enqueue an error message if an invalid value was specified
if (!found) {
FacesMessage message =
MessageFactory.getMessage(context, INVALID_MESSAGE_ID,
MessageFactory.getLabel(context, this));
context.addMessage(getClientId(context), message);
setValid(false);
}
}
}