/*
* 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.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.
*/
/*
* (C) Copyright International Business Machines Corp., 2001,2002
* The source code for this program is not published or otherwise
* divested of its trade secrets, irrespective of what has been
* deposited with the U. S. Copyright Office.
*/
// MenuRenderer.java
package com.sun.faces.renderkit.html_basic;
import static com.sun.faces.RIConstants.NO_VALUE;
import static com.sun.faces.renderkit.RenderKitUtils.getSelectItems;
import static com.sun.faces.renderkit.RenderKitUtils.renderOnchange;
import static com.sun.faces.renderkit.RenderKitUtils.renderPassThruAttributes;
import static com.sun.faces.renderkit.RenderKitUtils.renderXHTMLStyleBooleanAttributes;
import static com.sun.faces.util.MessageUtils.CONVERSION_ERROR_MESSAGE_ID;
import static com.sun.faces.util.MessageUtils.getExceptionMessage;
import static com.sun.faces.util.ReflectionUtils.lookupMethod;
import static com.sun.faces.util.RequestStateManager.TARGET_COMPONENT_ATTRIBUTE_NAME;
import static com.sun.faces.util.Util.getConverterForClass;
import static com.sun.faces.util.Util.isAllNull;
import static java.lang.Integer.MIN_VALUE;
import static java.lang.reflect.Array.get;
import static java.lang.reflect.Array.getLength;
import static java.lang.reflect.Array.set;
import static java.lang.reflect.Modifier.isAbstract;
import static java.util.Arrays.stream;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINER;
import static java.util.logging.Level.SEVERE;
import static java.util.stream.Collectors.joining;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.el.ELException;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectMany;
import javax.faces.component.UISelectOne;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;
import com.sun.faces.io.FastStringWriter;
import com.sun.faces.renderkit.Attribute;
import com.sun.faces.renderkit.AttributeManager;
import com.sun.faces.renderkit.SelectItemsIterator;
import com.sun.faces.util.RequestStateManager;
import com.sun.faces.util.Util;
/**
* <B>MenuRenderer</B> is a class that renders the current value of
* <code>UISelectOne<code> or <code>UISelectMany<code> component as a list of menu
* options.
*/
public class MenuRenderer extends HtmlBasicInputRenderer {
private static final Attribute[] ATTRIBUTES = AttributeManager.getAttributes(AttributeManager.Key.SELECTMANYMENU);
// ---------------------------------------------------------- Public Methods
public Object convertSelectManyValue(FacesContext context, UISelectMany uiSelectMany, String[] newValues) throws ConverterException {
// If we have no local value, try to get the valueExpression.
ValueExpression valueExpression = uiSelectMany.getValueExpression("value");
Object convertedValue = newValues; // default case, set local value
// If we have a ValueExpression
if (valueExpression != null) {
Class<?> modelType = valueExpression.getType(context.getELContext());
// Does the valueExpression resolve properly to something with a type?
if (modelType != null) {
convertedValue = convertSelectManyValuesForModel(context, uiSelectMany, modelType, newValues);
}
// If it could not be converted, as a fall back try the type of
// the valueExpression's current value covering some edge cases such
// as where the current value came from a Map.
if (convertedValue == null) {
Object value = valueExpression.getValue(context.getELContext());
if (value != null) {
convertedValue = convertSelectManyValuesForModel(context, uiSelectMany, value.getClass(), newValues);
}
}
if (convertedValue == null) {
Object[] params = { (newValues == null) ? "" : stream(newValues).collect(joining("")), valueExpression.getExpressionString() };
throw new ConverterException(
getExceptionMessage(CONVERSION_ERROR_MESSAGE_ID, params));
}
} else {
// No ValueExpression, just use Object array.
convertedValue = convertSelectManyValuesForArray(context, uiSelectMany, Object.class, newValues);
}
// At this point, result is ready to be set as the value
if (logger.isLoggable(FINE)) {
logger.fine("SelectMany Component " + uiSelectMany.getId() + " convertedValues " + convertedValue);
}
return convertedValue;
}
public Object convertSelectOneValue(FacesContext context, UISelectOne uiSelectOne, String newValue) throws ConverterException {
if (isNoValueOrNull(newValue, uiSelectOne)) {
return null;
}
Object convertedValue = super.getConvertedValue(context, uiSelectOne, newValue);
if (logger.isLoggable(FINE)) {
logger.fine("SelectOne Component " + uiSelectOne.getId() + " convertedValue " + convertedValue);
}
return convertedValue;
}
@Override
public void decode(FacesContext context, UIComponent component) {
rendererParamsNotNull(context, component);
if (!shouldDecode(component)) {
return;
}
String clientId = decodeBehaviors(context, component);
if (clientId == null) {
clientId = component.getClientId(context);
}
// Currently we assume the model type to be of type string or
// convertible to string and localized by the application.
if (component instanceof UISelectMany) {
decodeUISelectMany(context, (UISelectMany) component, clientId);
} else {
decodeUISelectOne(context, component, clientId);
}
}
@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
rendererParamsNotNull(context, component);
}
@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
rendererParamsNotNull(context, component);
if (!shouldEncode(component)) {
return;
}
renderSelect(context, component);
}
@Override
public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue) throws ConverterException {
if (component instanceof UISelectMany) {
// Need to set the 'TARGET_COMPONENT_ATTRIBUTE_NAME' request attr so the
// coerce-value call in the jsf-api UISelectMany.matchValue will work
// (need a better way to determine the currently processing UIComponent ...)
RequestStateManager.set(context, TARGET_COMPONENT_ATTRIBUTE_NAME, component);
return convertSelectManyValue(context, ((UISelectMany) component), (String[]) submittedValue);
} else {
return convertSelectOneValue(context, ((UISelectOne) component), (String) submittedValue);
}
}
// ------------------------------------------------------- Protected Methods
/*
* Converts the provided string array and places them into the correct provided model
* type.
*/
@SuppressWarnings("unchecked")
protected Object convertSelectManyValuesForModel(FacesContext context, UISelectMany uiSelectMany, Class<?> modelType, String[] newValues) {
if (modelType.isArray()) {
return convertSelectManyValuesForArray(context, uiSelectMany, modelType.getComponentType(), newValues);
}
if (Collection.class.isAssignableFrom(modelType)) {
return convertSelectManyValuesForCollection(context, uiSelectMany, (Class<? extends Collection<Object>>) modelType, newValues);
}
if (Object.class.equals(modelType)) {
return convertSelectManyValuesForArray(context, uiSelectMany, modelType, newValues);
}
throw new FacesException("Target model Type is no a Collection or Array");
}
protected Object convertSelectManyValuesForArray(FacesContext context, UISelectMany uiSelectMany, Class<?> elementType, String[] newValues) throws ConverterException {
Object array;
Converter<?> converter;
int length = newValues != null ? newValues.length : 0;
// Optimization: If the elementType is String, we don't need conversion. Just
// return newValues.
if (elementType.equals(String.class)) {
return newValues;
}
try {
array = Array.newInstance(elementType, length);
} catch (Exception e) {
throw new ConverterException(e);
}
// Bail out now if we have no new values, returning our oh-so-useful zero-length
// array.
if (newValues == null) {
return array;
}
// Obtain a converter.
converter = uiSelectMany.getConverter();
if (converter == null) {
// Look for a by-type converter based on model value.
converter = getConverterForClass(elementType, context);
if (converter == null) {
// If that also fails, and the attached values are of Object type, we
// don't need conversion.
if (elementType.equals(Object.class)) {
return newValues;
}
Object[] params = { stream(newValues).collect(joining(" ")), "null Converter" };
throw new ConverterException(getExceptionMessage(CONVERSION_ERROR_MESSAGE_ID, params));
}
}
for (int i = 0; i < length; i++) {
Object converted = converter.getAsObject(context, uiSelectMany, newValues[i]);
set(array, i, converted);
if (!elementType.isPrimitive() && logger.isLoggable(FINE)) {
logger.fine("String value: " + newValues[i] + " converts to : " + converted);
}
}
return array;
}
protected Collection<Object> convertSelectManyValuesForCollection(FacesContext context, UISelectMany uiSelectMany, Class<? extends Collection<Object>> collectionType, String[] newValues) {
Collection<Object> collection = null;
Converter<?> converter;
int length = (null != newValues) ? newValues.length : 0;
// See if the collectionType hint is available, if so, use that.
Object collectionTypeHint = uiSelectMany.getAttributes().get("collectionType");
if (collectionTypeHint != null) {
collection = createCollectionFromHint(collectionTypeHint);
} else {
// Try to get a new Collection to store the values based by trying to create a
// clone.
@SuppressWarnings("unchecked")
Collection<Object> currentValue = (Collection<Object>) uiSelectMany.getValue();
if (currentValue != null) {
collection = cloneValue(currentValue);
}
// No cloned instance so if the modelType happens to represent a concrete type
// (probably not the norm).
// Try to reflect a no-argument constructor and invoke if available.
if (collection == null) {
collection = createCollection(currentValue, collectionType);
}
// No suitable instance to work with, make our best guess based on the type.
if (collection == null) {
collection = bestGuess(collectionType, length);
}
}
// Bail out now if we have no new values, returning our oh-so-useful empty
// collection.
if (newValues == null) {
return collection;
}
// Obtain a converter.
converter = uiSelectMany.getConverter();
if (converter != null) {
// Convert the usual way.
for (String newValue : newValues) {
Object converted = converter.getAsObject(context, uiSelectMany, newValue);
if (logger.isLoggable(FINE)) {
logger.fine("String value: " + newValue + " converts to : " + converted);
}
collection.add(converted);
}
} else {
// First collect all available object items as string.
SelectItemsIterator<SelectItem> iterator = new SelectItemsIterator<>(context, uiSelectMany);
Map<String, Object> availableItems = new HashMap<>();
while (iterator.hasNext()) {
SelectItem item = iterator.next();
if (item instanceof SelectItemGroup) {
for (SelectItem groupItem : ((SelectItemGroup) item).getSelectItems()) {
String asString = getFormattedValue(context, uiSelectMany, groupItem.getValue());
availableItems.put(asString, groupItem.getValue());
}
} else {
String asString = getFormattedValue(context, uiSelectMany, item.getValue());
availableItems.put(asString, item.getValue());
}
}
// Then "convert" submitted value to object based on collected available
// object items.
for (String newValue : newValues) {
collection.add(availableItems.containsKey(newValue) ? availableItems.get(newValue) : newValue);
}
}
return collection;
}
protected boolean renderOption(FacesContext context, UIComponent component, UIComponent selectComponent, Converter<?> converter, SelectItem curItem,
Object currentSelections, Object[] submittedValues, OptionComponentInfo optionInfo) throws IOException {
Object valuesArray;
Object itemValue;
String valueString = getFormattedValue(context, component, curItem.getValue(), converter);
boolean containsValue;
if (submittedValues != null) {
containsValue = containsaValue(submittedValues);
if (containsValue) {
valuesArray = submittedValues;
itemValue = valueString;
} else {
valuesArray = currentSelections;
itemValue = curItem.getValue();
}
} else {
valuesArray = currentSelections;
itemValue = curItem.getValue();
}
boolean isSelected = isSelected(context, component, itemValue, valuesArray, converter);
if (optionInfo.isHideNoSelection() && curItem.isNoSelectionOption() && currentSelections != null && !isSelected) {
return false;
}
ResponseWriter writer = context.getResponseWriter();
assert (writer != null);
writer.writeText("\t", component, null);
writer.startElement("option", (null != selectComponent) ? selectComponent : component);
writer.writeAttribute("value", valueString, "value");
if (isSelected) {
writer.writeAttribute("selected", true, "selected");
}
// if the component is disabled, "disabled" attribute would be rendered
// on "select" tag, so don't render "disabled" on every option.
if (!optionInfo.isDisabled() && curItem.isDisabled()) {
writer.writeAttribute("disabled", true, "disabled");
}
String labelClass;
if (optionInfo.isDisabled() || curItem.isDisabled()) {
labelClass = optionInfo.getDisabledClass();
} else {
labelClass = optionInfo.getEnabledClass();
}
if (labelClass != null) {
writer.writeAttribute("class", labelClass, "labelClass");
}
if (curItem.isEscape()) {
String label = curItem.getLabel();
if (label == null) {
label = valueString;
}
writer.writeText(label, component, "label");
} else {
writer.write(curItem.getLabel());
}
writer.endElement("option");
writer.writeText("\n", component, null);
return true;
}
protected void writeDefaultSize(ResponseWriter writer, int itemCount) throws IOException {
// if size is not specified default to 1.
writer.writeAttribute("size", "1", "size");
}
protected boolean containsaValue(Object valueArray) {
if (valueArray != null) {
int len = getLength(valueArray);
for (int i = 0; i < len; i++) {
Object value = Array.get(valueArray, i);
if (value != null && !value.equals(NO_VALUE)) {
return true;
}
}
}
return false;
}
@SuppressWarnings("unchecked")
protected Object getCurrentSelectedValues(UIComponent component) {
if (component instanceof UISelectMany) {
UISelectMany select = (UISelectMany) component;
Object value = select.getValue();
if (value == null) {
return null;
} if (value instanceof Collection) {
return ((Collection<Object>) value).toArray();
} else if (value.getClass().isArray()) {
if (getLength(value) == 0) {
return null;
}
} else if (!value.getClass().isArray()) {
logger.warning("The UISelectMany value should be an array or a collection type, the actual type is " + value.getClass().getName());
}
return value;
}
UISelectOne select = (UISelectOne) component;
Object val = select.getValue();
if (val != null) {
return new Object[] { val };
}
return null;
}
// To derive a selectOne type component from this, override
// these methods.
protected String getMultipleText(UIComponent component) {
if (component instanceof UISelectMany) {
return " multiple ";
}
return "";
}
protected Object[] getSubmittedSelectedValues(UIComponent component) {
if (component instanceof UISelectMany) {
UISelectMany select = (UISelectMany) component;
return (Object[]) select.getSubmittedValue();
}
UISelectOne select = (UISelectOne) component;
Object val = select.getSubmittedValue();
if (val != null) {
return new Object[] { val };
}
return null;
}
protected boolean isSelected(FacesContext context, UIComponent component, Object itemValue, Object valueArray, Converter<?> converter) {
if (isAllNull(itemValue, valueArray)) {
return true;
}
if (valueArray != null) {
if (!valueArray.getClass().isArray()) {
logger.warning("valueArray is not an array, the actual type is " + valueArray.getClass());
return valueArray.equals(itemValue);
}
int len = getLength(valueArray);
for (int i = 0; i < len; i++) {
Object value = get(valueArray, i);
if (isAllNull(itemValue, value)) {
return true;
}
if ((value == null) ^ (itemValue == null)) {
continue;
}
Object compareValue;
if (converter == null) {
compareValue = coerceToModelType(context, itemValue, value.getClass());
} else {
compareValue = itemValue;
if (compareValue instanceof String && !(value instanceof String)) {
// type mismatch between the time and the value we're
// comparing. Invoke the Converter.
compareValue = converter.getAsObject(context, component, (String) compareValue);
}
}
if (value.equals(compareValue)) {
return true;
}
}
}
return false;
}
protected int renderOptions(FacesContext context, UIComponent component, SelectItemsIterator<SelectItem> items) throws IOException {
ResponseWriter writer = context.getResponseWriter();
Converter<?> converter = null;
if (component instanceof ValueHolder) {
converter = ((ValueHolder) component).getConverter();
}
int count = 0;
Object currentSelections = getCurrentSelectedValues(component);
Object[] submittedValues = getSubmittedSelectedValues(component);
OptionComponentInfo optionInfo = new OptionComponentInfo(component);
RequestStateManager.set(context, TARGET_COMPONENT_ATTRIBUTE_NAME, component);
while (items.hasNext()) {
SelectItem item = items.next();
UIComponent selectComponent = items.currentSelectComponent();
if (item instanceof SelectItemGroup) {
// render OPTGROUP
writer.startElement("optgroup", (null != selectComponent) ? selectComponent : component);
writer.writeAttribute("label", item.getLabel(), "label");
// if the component is disabled, "disabled" attribute would be rendered
// on "select" tag, so don't render "disabled" on every option.
if ((!optionInfo.isDisabled()) && item.isDisabled()) {
writer.writeAttribute("disabled", true, "disabled");
}
count++;
// render options of this group.
SelectItem[] itemsArray = ((SelectItemGroup) item).getSelectItems();
for (SelectItem element : itemsArray) {
if (renderOption(context, component, selectComponent, converter, element, currentSelections, submittedValues, optionInfo)) {
count++;
}
}
writer.endElement("optgroup");
} else {
if (renderOption(context, component, selectComponent, converter, item, currentSelections, submittedValues, optionInfo)) {
count++;
}
}
}
return count;
}
// Render the "select" portion..
//
protected void renderSelect(FacesContext context, UIComponent component) throws IOException {
ResponseWriter writer = context.getResponseWriter();
if (logger.isLoggable(FINER)) {
logger.log(FINER, "Rendering 'select'");
}
writer.startElement("select", component);
writeIdAttributeIfNecessary(context, writer, component);
writer.writeAttribute("name", component.getClientId(context), "clientId");
// Render styleClass attribute if present.
String styleClass;
if ((styleClass = (String) component.getAttributes().get("styleClass")) != null) {
writer.writeAttribute("class", styleClass, "styleClass");
}
if (!getMultipleText(component).equals("")) {
writer.writeAttribute("multiple", true, "multiple");
}
// Determine how many option(s) we need to render, and update
// the component's "size" attribute accordingly; The "size"
// attribute will be rendered as one of the "pass thru" attributes
SelectItemsIterator<SelectItem> items = getSelectItems(context, component);
// Render the options to a buffer now so that we can determine
// the size
FastStringWriter bufferedWriter = new FastStringWriter(128);
context.setResponseWriter(writer.cloneWithWriter(bufferedWriter));
int count = renderOptions(context, component, items);
context.setResponseWriter(writer);
// If "size" is *not* set explicitly, we have to default it correctly
Integer size = (Integer) component.getAttributes().get("size");
if (size == null || size == MIN_VALUE) {
size = count;
}
writeDefaultSize(writer, size);
renderPassThruAttributes(context, writer, component, ATTRIBUTES, getNonOnChangeBehaviors(component));
renderXHTMLStyleBooleanAttributes(writer, component);
renderOnchange(context, component, false);
// Now, write the buffered option content
writer.write(bufferedWriter.toString());
writer.endElement("select");
}
protected Object coerceToModelType(FacesContext ctx, Object value, Class<?> itemValueType) {
Object newValue;
try {
ExpressionFactory ef = ctx.getApplication().getExpressionFactory();
newValue = ef.coerceToType(value, itemValueType);
} catch (ELException | IllegalArgumentException ele) {
// If coerceToType fails, per the docs it should throw
// an ELException, however, GF 9.0 and 9.0u1 will throw
// an IllegalArgumentException instead (see GF issue 1527).
newValue = value;
}
return newValue;
}
/**
* @param collection
* a Collection instance
*
* @return a new <code>Collection</code> instance or null if the instance cannot be
* created
*/
protected Collection<Object> createCollection(Collection<Object> collection, Class<? extends Collection<Object>> fallBackType) {
@SuppressWarnings("unchecked")
Class<? extends Collection<Object>> lookupClass = collection != null ? (Class<? extends Collection<Object>>) collection.getClass() : fallBackType;
if (!lookupClass.isInterface() && !isAbstract(lookupClass.getModifiers())) {
try {
return lookupClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
if (logger.isLoggable(SEVERE)) {
logger.log(SEVERE, "Unable to create new Collection instance for type " + lookupClass.getName(), e);
}
}
}
return null;
}
/**
* <p>
* Utility method to invoke the the <code>clone</code> method on the provided value.
* </p>
*
* @param value
* the value to clone
* @return the result of invoking <code>clone()</code> or <code>null</code> if the
* value could not be cloned or does not implement the {@link Cloneable}
* interface
*/
protected Collection<Object> cloneValue(Object value) {
if (value instanceof Cloneable) {
// Even though Clonable marks an instance of a Class as being
// safe to call .clone(), .clone() by default is protected.
// The Collection classes that do implement Clonable do so at variable
// locations within the class hierarchy, so we're stuck having to
// use reflection.
Method cloneMethod = lookupMethod(value, "clone");
if (cloneMethod != null) {
try {
@SuppressWarnings("unchecked")
Collection<Object> clonedCollected = (Collection<Object>) cloneMethod.invoke(value);
clonedCollected.clear();
return clonedCollected;
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
if (logger.isLoggable(SEVERE)) {
logger.log(SEVERE, "Unable to clone collection type: {0}", value.getClass().getName());
logger.log(SEVERE, e.toString(), e);
}
}
} else {
// No public clone method
if (logger.isLoggable(FINE)) {
logger.log(FINE, "Type {0} implements Cloneable, but has no public clone method.", value.getClass().getName());
}
}
}
return null;
}
/**
* @param type
* the target model type
* @param initialSize
* the initial size of the <code>Collection</code>
* @return a <code>Collection</code> instance that best matches <code>type</code>
*/
protected Collection<Object> bestGuess(Class<? extends Collection<Object>> type, int initialSize) {
if (SortedSet.class.isAssignableFrom(type)) {
return new TreeSet<>();
}
if (Queue.class.isAssignableFrom(type)) {
return new LinkedList<>();
}
if (Set.class.isAssignableFrom(type)) {
return new HashSet<>(initialSize);
}
// This covers the where type is List or Collection
return new ArrayList<>(initialSize);
}
/**
* <p>
* Create a collection from the provided hint.
*
* @param collectionTypeHint
* the Collection type as either a String or Class
* @return a new Collection instance
*/
@SuppressWarnings("unchecked")
protected Collection<Object> createCollectionFromHint(Object collectionTypeHint) {
Class<? extends Collection<Object>> collectionType;
if (collectionTypeHint instanceof Class) {
collectionType = (Class<? extends Collection<Object>>) collectionTypeHint;
} else if (collectionTypeHint instanceof String) {
try {
collectionType = Util.loadClass((String) collectionTypeHint, this);
} catch (ClassNotFoundException cnfe) {
throw new FacesException(cnfe);
}
} else {
throw new FacesException("'collectionType' should resolve to type String or Class. Found: " + collectionTypeHint.getClass().getName());
}
Collection<Object> createdCollection = createCollection(null, collectionType);
if (createdCollection == null) {
throw new FacesException("Unable to create collection type " + collectionType);
}
return createdCollection;
}
protected static boolean isHideNoSelection(UIComponent component) {
Object result = component.getAttributes().get("hideNoSelectionOption");
return result != null ? (Boolean) result : false;
}
// ------------------------------------------------------- Private Methods
private void decodeUISelectMany(FacesContext context, UISelectMany component, String clientId) {
Map<String, String[]> requestParameterValuesMap = context.getExternalContext()
.getRequestParameterValuesMap();
if (requestParameterValuesMap.containsKey(clientId)) {
String newValues[] = requestParameterValuesMap.get(clientId);
setSubmittedValue(component, newValues);
if (logger.isLoggable(FINE)) {
logger.fine("submitted values for UISelectMany component " + component.getId() + " after decoding " + Arrays.toString(newValues));
}
} else {
// Use the empty array, not null, to distinguish between an deselected UISelectMany and a disabled one
setSubmittedValue(component, new String[0]);
if (logger.isLoggable(FINE)) {
logger.fine("Set empty array for UISelectMany component " + component.getId() + " after decoding ");
}
}
}
private void decodeUISelectOne(FacesContext context, UIComponent component, String clientId) {
Map<String, String> requestParameterMap = context.getExternalContext()
.getRequestParameterMap();
if (requestParameterMap.containsKey(clientId)) {
String newValue = requestParameterMap.get(clientId);
setSubmittedValue(component, newValue);
if (logger.isLoggable(FINE)) {
logger.fine("submitted value for UISelectOne component " + component.getId() + " after decoding " + newValue);
}
} else {
// there is no value, but this is different from a null value.
setSubmittedValue(component, NO_VALUE);
}
}
private boolean isNoValueOrNull(String newValue, UIComponent component) {
if (NO_VALUE.equals(newValue)) {
return true;
}
if (newValue == null) {
if (logger.isLoggable(FINE)) {
logger.fine(
"No conversion necessary for SelectOne Component " +
component.getId() + " since the new value is null ");
}
return true;
}
return false;
}
}