Mercurial > hg > ismi-richfaces
diff src/main/java/com/sun/faces/renderkit/html_basic/MenuRenderer.java @ 1:2e911857a759
(none)
author | jurzua |
---|---|
date | Wed, 29 Oct 2014 14:00:28 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/com/sun/faces/renderkit/html_basic/MenuRenderer.java Wed Oct 29 14:00:28 2014 +0000 @@ -0,0 +1,1047 @@ +/* + * 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. + */ + +/* + * (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 java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.HashSet; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.Queue; +import java.util.LinkedList; +import java.util.logging.Level; + +import javax.el.ELException; +import javax.el.ValueExpression; +import javax.el.ExpressionFactory; +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 javax.faces.FacesException; + +import org.apache.commons.lang.StringUtils; + +import com.sun.faces.RIConstants; +import com.sun.faces.io.FastStringWriter; +import com.sun.faces.renderkit.Attribute; +import com.sun.faces.renderkit.AttributeManager; +import com.sun.faces.renderkit.RenderKitUtils; +import com.sun.faces.util.MessageUtils; +import com.sun.faces.util.Util; +import com.sun.faces.util.RequestStateManager; +import com.sun.faces.util.ReflectionUtils; + +/** + * <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 result = newValues; // default case, set local value + boolean throwException = false; + + // If we have a ValueExpression + if (null != valueExpression) { + Class modelType = valueExpression.getType(context.getELContext()); + // Does the valueExpression resolve properly to something with + // a type? + if (modelType != null) { + result = 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(result == null) { + Object value = valueExpression.getValue(context.getELContext()); + if(value != null) { + result = convertSelectManyValuesForModel(context, + uiSelectMany, + value.getClass(), + newValues); + } + } + if(result == null) { + throwException = true; + } + } else { + // No ValueExpression, just use Object array. + result = convertSelectManyValues(context, uiSelectMany, + Object[].class, + newValues); + } + if (throwException) { + StringBuffer values = new StringBuffer(); + if (null != newValues) { + for (int i = 0; i < newValues.length; i++) { + if (i == 0) { + values.append(newValues[i]); + } else { + values.append(' ').append(newValues[i]); + } + } + } + Object[] params = { + values.toString(), + valueExpression.getExpressionString() + }; + throw new ConverterException + (MessageUtils.getExceptionMessage(MessageUtils.CONVERSION_ERROR_MESSAGE_ID, + params)); + } + + // At this point, result is ready to be set as the value + if (logger.isLoggable(Level.FINE)) { + logger.fine("SelectMany Component " + uiSelectMany.getId() + + " convertedValues " + result); + } + return result; + + } + + + public Object convertSelectOneValue(FacesContext context, + UISelectOne uiSelectOne, + String newValue) + throws ConverterException { + + if (RIConstants.NO_VALUE.equals(newValue)) { + return null; + } + if (newValue == null) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("No conversion necessary for SelectOne Component " + + uiSelectOne.getId() + + " since the new value is null "); + } + return null; + } + + Object convertedValue = + super.getConvertedValue(context, uiSelectOne, newValue); + if (logger.isLoggable(Level.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); + } + assert(clientId != null); + // currently we assume the model type to be of type string or + // convertible to string and localized by the application. + if (component instanceof UISelectMany) { + Map<String, String[]> requestParameterValuesMap = + context.getExternalContext(). + getRequestParameterValuesMap(); + if (requestParameterValuesMap.containsKey(clientId)) { + String newValues[] = requestParameterValuesMap. + get(clientId); + setSubmittedValue(component, newValues); + if (logger.isLoggable(Level.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(Level.FINE)) { + logger.fine("Set empty array for UISelectMany component " + + component.getId() + " after decoding "); + } + } + } else { + // this is a UISelectOne + Map<String, String> requestParameterMap = + context.getExternalContext(). + getRequestParameterMap(); + if (requestParameterMap.containsKey(clientId)) { + String newValue = requestParameterMap.get(clientId); + setSubmittedValue(component, newValue); + if (logger.isLoggable(Level.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, RIConstants.NO_VALUE); + } + } + + } + + + @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, + RequestStateManager.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. + */ + protected Object convertSelectManyValuesForModel(FacesContext context, + UISelectMany uiSelectMany, + Class modelType, + String[] newValues) { + + if (modelType.isArray()) { + return convertSelectManyValues(context, + uiSelectMany, + modelType, + newValues); + } else if (Collection.class.isAssignableFrom(modelType)) { + Object[] values = (Object[]) convertSelectManyValues(context, + uiSelectMany, + Object[].class, + newValues); + + Collection targetCollection = null; + + // see if the collectionType hint is available, if so, use that + Object collectionTypeHint = uiSelectMany.getAttributes().get("collectionType"); + if (collectionTypeHint != null) { + targetCollection = createCollectionFromHint(collectionTypeHint); + } else { + // try to get a new Collection to store the values based + // by trying to create a clone + Collection currentValue = (Collection) uiSelectMany.getValue(); + if (currentValue != null) { + targetCollection = 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 (targetCollection == null) { + //noinspection unchecked + targetCollection = + createCollection(currentValue, modelType); + } + + // No suitable instance to work with, make our best guess + // based on the type. + if (targetCollection == null) { + //noinspection unchecked + targetCollection = bestGuess(modelType, values.length); + } + } + + //noinspection ManualArrayToCollectionCopy + for (Object v : values) { + //noinspection unchecked + targetCollection.add(v); + } + + return targetCollection; + } else if (Object.class.equals(modelType)) { + return convertSelectManyValues(context, + uiSelectMany, + Object[].class, + newValues); + } else { + throw new FacesException("Target model Type is no a Collection or Array"); + } + + } + + + + + protected Object convertSelectManyValues(FacesContext context, + UISelectMany uiSelectMany, + Class arrayClass, + String[] newValues) + throws ConverterException { + + Object result; + Converter converter; + int len = (null != newValues ? newValues.length : 0); + + Class elementType = arrayClass.getComponentType(); + + // Optimization: If the elementType is String, we don't need + // conversion. Just return newValues. + if (elementType.equals(String.class)) { + return newValues; + } + + try { + result = Array.newInstance(elementType, len); + } 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 (null == newValues) { + return result; + } + + // obtain a converter. + + // attached converter takes priority + if (null == (converter = uiSelectMany.getConverter())) { + // Otherwise, look for a by-type converter + if (null == (converter = Util.getConverterForClass(elementType, + context))) { + // if that fails, and the attached values are of Object type, + // we don't need conversion. + if (elementType.equals(Object.class)) { + return newValues; + } + StringBuffer valueStr = new StringBuffer(); + for (int i = 0; i < len; i++) { + if (i == 0) { + valueStr.append(newValues[i]); + } else { + valueStr.append(' ').append(newValues[i]); + } + } + Object[] params = { + valueStr.toString(), + "null Converter" + }; + + throw new ConverterException(MessageUtils.getExceptionMessage( + MessageUtils.CONVERSION_ERROR_MESSAGE_ID, params)); + } + } + + assert(null != result); + if (elementType.isPrimitive()) { + for (int i = 0; i < len; i++) { + if (elementType.equals(Boolean.TYPE)) { + Array.setBoolean(result, i, + ((Boolean) converter.getAsObject(context, + uiSelectMany, + newValues[i]))); + } else if (elementType.equals(Byte.TYPE)) { + Array.setByte(result, i, + ((Byte) converter.getAsObject(context, + uiSelectMany, + newValues[i]))); + } else if (elementType.equals(Double.TYPE)) { + Array.setDouble(result, i, + ((Double) converter.getAsObject(context, + uiSelectMany, + newValues[i]))); + } else if (elementType.equals(Float.TYPE)) { + Array.setFloat(result, i, + ((Float) converter.getAsObject(context, + uiSelectMany, + newValues[i]))); + } else if (elementType.equals(Integer.TYPE)) { + Array.setInt(result, i, + ((Integer) converter.getAsObject(context, + uiSelectMany, + newValues[i]))); + } else if (elementType.equals(Character.TYPE)) { + Array.setChar(result, i, + ((Character) converter.getAsObject(context, + uiSelectMany, + newValues[i]))); + } else if (elementType.equals(Short.TYPE)) { + Array.setShort(result, i, + ((Short) converter.getAsObject(context, + uiSelectMany, + newValues[i]))); + } else if (elementType.equals(Long.TYPE)) { + Array.setLong(result, i, + ((Long) converter.getAsObject(context, + uiSelectMany, + newValues[i]))); + } + } + } else { + for (int i = 0; i < len; i++) { + if (logger.isLoggable(Level.FINE)) { + Object converted = converter.getAsObject(context, + uiSelectMany, + newValues[i]); + logger.fine("String value: " + newValues[i] + + " converts to : " + converted); + } + Array.set(result, i, converter.getAsObject(context, + uiSelectMany, + newValues[i])); + } + } + return result; + + } + + + protected boolean renderOption(FacesContext context, + UIComponent component, + 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", 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"); + } + + + //jurzua + if(StringUtils.isNotEmpty(curItem.getStyle())){ + writer.writeAttribute("style", curItem.getStyle(), curItem.getStyle()); + } + + + 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 (null != valueArray) { + int len = Array.getLength(valueArray); + for (int i = 0; i < len; i++) { + Object value = Array.get(valueArray, i); + if (value != null && !(value.equals(RIConstants.NO_VALUE))) { + return true; + } + } + } + return false; + + } + + + protected Object getCurrentSelectedValues(UIComponent component) { + + if (component instanceof UISelectMany) { + UISelectMany select = (UISelectMany) component; + Object value = select.getValue(); + if (value == null) { + return null; + } else if (value instanceof Collection) { + return ((Collection) value).toArray(); + } else if (value.getClass().isArray()) { + if (Array.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 (itemValue == null && valueArray == null) { + return true; + } + if (null != valueArray) { + if (!valueArray.getClass().isArray()) { + logger.warning("valueArray is not an array, the actual type is " + + valueArray.getClass()); + return valueArray.equals(itemValue); + } + int len = Array.getLength(valueArray); + for (int i = 0; i < len; i++) { + Object value = Array.get(valueArray, i); + if (value == null && itemValue == null) { + return true; + } else { + 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, + Iterator<SelectItem> items) + throws IOException { + + ResponseWriter writer = context.getResponseWriter(); + assert(writer != null); + + Converter converter = null; + if(component instanceof ValueHolder) { + converter = ((ValueHolder)component).getConverter(); + } + int count = 0; + Object currentSelections = getCurrentSelectedValues(component); + Object[] submittedValues = getSubmittedSelectedValues(component); + Map<String,Object> attributes = component.getAttributes(); + boolean componentDisabled = Util.componentIsDisabled(component); + + OptionComponentInfo optionInfo = + new OptionComponentInfo((String) attributes.get("disabledClass"), + (String) attributes.get("enabledClass"), + componentDisabled, + isHideNoSelection(component)); + RequestStateManager.set(context, + RequestStateManager.TARGET_COMPONENT_ATTRIBUTE_NAME, + component); + while (items.hasNext()) { + SelectItem item = items.next(); + + if (item instanceof SelectItemGroup) { + // render OPTGROUP + writer.startElement("optgroup", 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 ((!componentDisabled) && item.isDisabled()) { + writer.writeAttribute("disabled", true, "disabled"); + } + count++; + // render options of this group. + SelectItem[] itemsArray = + ((SelectItemGroup) item).getSelectItems(); + for (int i = 0; i < itemsArray.length; ++i) { + if (renderOption(context, + component, + converter, + itemsArray[i], + currentSelections, + submittedValues, + optionInfo)) { + count++; + } + } + writer.endElement("optgroup"); + } else { + if (renderOption(context, + component, + 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(); + assert(writer != null); + + if (logger.isLoggable(Level.FINER)) { + logger.log(Level.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 (null != + (styleClass = + (String) component.getAttributes().get("styleClass"))) { + 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 + Iterator<SelectItem> items = RenderKitUtils.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 == Integer.MIN_VALUE) { + size = count; + } + writeDefaultSize(writer, size); + + RenderKitUtils.renderPassThruAttributes(context, + writer, + component, + ATTRIBUTES, + getNonOnChangeBehaviors(component)); + RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, + component); + + RenderKitUtils.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 ele) { + newValue = value; + } catch (IllegalArgumentException iae) { + // 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 createCollection(Collection collection, + Class<? extends Collection> fallBackType) { + + Class<? extends Collection> lookupClass = + ((collection != null) ? collection.getClass() : fallBackType); + + if (!lookupClass.isInterface() + && !Modifier.isAbstract(lookupClass.getModifiers())) { + try { + return lookupClass.newInstance(); + } catch (Exception e) { + if (logger.isLoggable(Level.SEVERE)) { + logger.log(Level.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 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 clone = + ReflectionUtils.lookupMethod(value.getClass(), "clone"); + if (clone != null) { + try { + Collection c = (Collection) clone.invoke(value); + c.clear(); + return c; + } catch (Exception e) { + if (logger.isLoggable(Level.SEVERE)) { + logger.log(Level.SEVERE, + "Unable to clone collection type: {0}", + value.getClass().getName()); + logger.log(Level.SEVERE, e.toString(), e); + } + } + } else { + // no public clone method + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.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 bestGuess(Class<? extends Collection> type, + int initialSize) { + + if (SortedSet.class.isAssignableFrom(type)) { + return new TreeSet(); + } else if (Queue.class.isAssignableFrom(type)) { + return new LinkedList(); + } else if (Set.class.isAssignableFrom(type)) { + return new HashSet(initialSize); + } else { + // 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 + */ + protected Collection createCollectionFromHint(Object collectionTypeHint) { + + Class<? extends Collection> collectionType; + if (collectionTypeHint instanceof Class) { + //noinspection unchecked + collectionType = (Class<? extends Collection>) collectionTypeHint; + } else if (collectionTypeHint instanceof String) { + try { + //noinspection unchecked + collectionType = Util.loadClass((String) collectionTypeHint, + this); + } catch (ClassNotFoundException cnfe) { + throw new FacesException(cnfe); + } + } else { + // RELEASE_PENDING (i18n) + throw new FacesException( + "'collectionType' should resolve to type String or Class. Found: " + + collectionTypeHint.getClass().getName()); + } + + Collection c = createCollection(null, collectionType); + if (c == null) { + // RELEASE_PENDING (i18n) + throw new FacesException("Unable to create collection type " + collectionType); + } + return c; + + } + + + protected boolean isHideNoSelection(UIComponent component) { + + Object result = component.getAttributes().get("hideNoSelectionOption"); + return ((result != null) ? (Boolean) result : false); + + } + +} // end of class MenuRenderer