com.flexive.faces.FxJsfComponentUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.faces.FxJsfComponentUtils.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.faces;

import com.flexive.faces.renderer.FxSelectRenderer;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.el.ELException;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.component.*;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;
import java.lang.reflect.Array;
import java.util.*;

/**
 * Utility functions for JSF/Facelets components.
 *
 * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @version $Rev$
 */
public class FxJsfComponentUtils {

    private static final Log LOG = LogFactory.getLog(FxJsfComponentUtils.class);
    /**
     * Attributed to be passed through
     */
    public final static List<String> SELECTLIST_ATTRIBUTES = Collections.unmodifiableList(
            Arrays.asList("disabled", "readonly", "accesskey", "dir", "lang", "onblur", "onchange", "onclick",
                    "ondblclick", "onfocus", "onkeydown", "onkeypress", "onkeyup", "onmousedown", "onmousemove",
                    "onmouseout", "onmouseover", "onmouseup", "onselect", "style", "tabindex", "title"));

    /**
     * Private constructor to avoid instantiation.
     */
    private FxJsfComponentUtils() {
    }

    /**
     * Evaluate the string attribute of a component.
     *
     * @param component     a JSF component
     * @param attributeName the attribute name to be evaluated, e.g. "title"
     * @return the bound value, or null if no value is bound
     */
    public static String getStringValue(UIComponent component, String attributeName) {
        return (String) getValue(component, attributeName);
    }

    /**
     * Evaluate the string attribute of a component.
     *
     * @param component     a JSF component
     * @param attributeName the attribute name to be evaluated, e.g. "title"
     * @param defaultValue  the default value to be used if no value is bound
     * @return the bound value, or null if no value is bound
     */
    public static String getStringValue(UIComponent component, String attributeName, String defaultValue) {
        return StringUtils.defaultIfEmpty((String) getValue(component, attributeName), defaultValue);
    }

    /**
     * Evaluate the integer attribute of a component.
     *
     * @param component     a JSF component
     * @param attributeName the attribute name to be evaluated, e.g. "title"
     * @return the bound value, or null if no value is bound
     */
    public static Integer getIntegerValue(UIComponent component, String attributeName) {
        return (Integer) getValue(component, attributeName);
    }

    /**
     * Evaluate the long attribute of a component.
     *
     * @param component     a JSF component
     * @param attributeName the attribute name to be evaluated, e.g. "title"
     * @param defaultValue  the default value to be used if no value is bound
     * @return the bound value, or <code>defaultValue</code> if no value is bound
     */
    public static int getIntegerValue(UIComponent component, String attributeName, int defaultValue) {
        final Integer intValue = (Integer) getValue(component, attributeName);
        return intValue != null ? intValue : defaultValue;
    }

    /**
     * Evaluate the long attribute of a component.
     *
     * @param component     a JSF component
     * @param attributeName the attribute name to be evaluated, e.g. "title"
     * @return the bound value, or null if no value is bound
     */
    public static Long getLongValue(UIComponent component, String attributeName) {
        return (Long) getValue(component, attributeName);
    }

    /**
     * Evaluate the long attribute of a component.
     *
     * @param component     a JSF component
     * @param attributeName the attribute name to be evaluated, e.g. "title"
     * @param defaultValue  the default value to be used if no value is bound
     * @return the bound value, or <code>defaultValue</code> if no value is bound
     */
    public static long getLongValue(UIComponent component, String attributeName, long defaultValue) {
        final Long longValue = (Long) getValue(component, attributeName);
        return longValue != null ? longValue : defaultValue;
    }

    /**
     * Evaluate the boolean attribute of a component.
     *
     * @param component     a JSF component
     * @param attributeName the attribute name to be evaluated, e.g. "title"
     * @return the bound value, or null if no value is bound
     */
    public static Boolean getBooleanValue(UIComponent component, String attributeName) {
        return (Boolean) getValue(component, attributeName);
    }

    /**
     * Evaluate the boolean attribute of a component.
     *
     * @param component     a JSF component
     * @param attributeName the attribute name to be evaluated, e.g. "title"
     * @param defaultValue  the default value to be used when the attribute is null
     * @return the bound value, or null if no value is bound
     */
    public static boolean getBooleanValue(UIComponent component, String attributeName, boolean defaultValue) {
        final Boolean value = (Boolean) getValue(component, attributeName);
        return value != null ? value : defaultValue;
    }

    public static Object getValue(UIComponent component, String attributeName) {
        ValueExpression ve = component.getValueExpression(attributeName);
        if (ve != null) {
            return ve.getValue(FacesContext.getCurrentInstance().getELContext());
        }
        return null;
    }

    /**
     * Throws a FacesException if the given value is not set.
     *
     * @param attributeName the attribute name, e.g. "var"
     * @param componentName the component name, e.g. "fx:value"
     * @param value         the value set on the component
     * @throws FacesException if value is null or a blank string
     */
    public static void requireAttribute(String componentName, String attributeName, Object value)
            throws FacesException {
        if (value == null || (value instanceof String && StringUtils.isBlank((String) value))) {
            throw new FacesException("Attribute '" + attributeName + "' of " + componentName + " not set.");
        }
    }

    /**
     * <p>Return a List of {@link javax.faces.model.SelectItem}
     * instances representing the available options for this component,
     * assembled from the set of {@link javax.faces.component.UISelectItem}
     * and/or {@link javax.faces.component.UISelectItems} components that are
     * direct children of this component.  If there are no such children, an
     * empty <code>Iterator</code> is returned.</p>
     * <p/>
     * Portions of the code are taken and adapted from the JSF RI 1.2
     *
     * @param context   The {@link javax.faces.context.FacesContext} for the current request.
     *                  If null, the UISelectItems behavior will not work.
     * @param component the component
     * @return a List of the select items for the specified component
     * @throws IllegalArgumentException if <code>context</code> is <code>null</code>
     */
    public static List<SelectItem> getSelectItems(FacesContext context, UIComponent component) {
        if (context == null)
            throw new IllegalArgumentException("context was null");

        ArrayList<SelectItem> list = new ArrayList<SelectItem>();
        for (UIComponent child : component.getChildren()) {
            if (child instanceof UISelectItem) {
                UISelectItem item = (UISelectItem) child;
                Object value = item.getValue();
                if (value == null) {
                    list.add(new SelectItem(item.getItemValue(), item.getItemLabel(), item.getItemDescription(),
                            item.isItemDisabled(), item.isItemEscaped()));
                } else if (value instanceof SelectItem)
                    list.add((SelectItem) value);
                else
                    throw new IllegalArgumentException(
                            "value class can not be converted to SelectItem: " + value.getClass().getName());
            } else if (child instanceof UISelectItems) {
                Object value = ((UISelectItems) child).getValue();
                if (value instanceof SelectItem) {
                    list.add((SelectItem) value);
                } else if (value instanceof SelectItem[]) {
                    SelectItem[] items = (SelectItem[]) value;
                    // we manually copy the elements so that the list is modifiable.
                    // Arrays.asList() returns a non-mutable list.
                    //noinspection ManualArrayToCollectionCopy
                    for (SelectItem item : items)
                        list.add(item);
                } else if (value instanceof Collection) {
                    for (Object element : ((Collection) value))
                        if (SelectItem.class.isInstance(element))
                            list.add((SelectItem) element);
                        else
                            throw new IllegalArgumentException("value class can not be converted to SelectItem: "
                                    + value.getClass().getName());
                } else if (value instanceof Map) {
                    Map optionMap = (Map) value;
                    for (Object o : optionMap.entrySet()) {
                        Map.Entry entry = (Map.Entry) o;
                        Object key = entry.getKey();
                        Object val = entry.getValue();
                        if (key == null || val == null)
                            continue;
                        list.add(new SelectItem(val, key.toString()));
                    }
                } else
                    throw new IllegalArgumentException(
                            "Child is not of expected type UISelectItem/UISelectItems. Family:"
                                    + component.getFamily() + ", id:" + component.getId() + ", value class:"
                                    + (value != null ? value.getClass().getName() : "null"));
            }
        }
        return (list);
    }

    /**
     * Get the converted value of a component making sure that validators are processed.
     * Portions of the code are taken and adapted from HtmlBasicInputRenderer of the JSF RI 1.2
     *
     * @param context        faces context
     * @param component      the component affected
     * @param submittedValue the submitted value
     * @return the converted value
     * @throws ConverterException on errors
     */
    public static Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue)
            throws ConverterException {
        String newValue = (String) submittedValue;
        // if we have no local value, try to get the valueExpression.
        ValueExpression valueExpression = component.getValueExpression("value");
        Converter converter = null;

        // If there is a converter attribute, use it to to ask application
        // instance for a converter with this identifer.
        if (component instanceof ValueHolder) {
            converter = ((ValueHolder) component).getConverter();
        }

        if (null == converter && null != valueExpression) {
            Class converterType = valueExpression.getType(context.getELContext());
            // if converterType is null, assume the modelType is "String".
            if (converterType == null || converterType == Object.class) {
                //no conversion necessary
                return newValue;
            }

            // If the converterType is a String, and we don't have a
            // converter-for-class for java.lang.String, assume the type is "String".
            if (converterType == String.class
                    && !(null != context.getApplication().createConverter(String.class))) {
                //no conversion necessary
                return newValue;
            }
            // if getType returns a type for which we support a default

            try {
                Application application = context.getApplication();
                converter = application.createConverter(converterType);
            } catch (Exception e) {
                LOG.error("Could not instantiate converter for type " + converterType + ": " + e.toString());
                return (null);
            }
        } else if (converter == null) {
            // if there is no valueExpression and converter attribute set,
            // assume the modelType as "String" since we have no way of
            // figuring out the type. So for the selectOne and
            // selectMany, converter has to be set if there is no
            // valueExpression attribute set on the component.

            return newValue;
        }
        if (converter != null) {
            // If the conversion eventually falls to needing to use EL type coercion,
            // make sure our special ConverterPropertyEditor knows about this value.
            return converter.getAsObject(context, component, newValue);
        } else {
            // throw converter exception.
            throw new ConverterException("No converter for " + newValue + " available!");
        }
    }

    /**
     * Get a converter for the given class
     *
     * @param converterClass class to get a converter for
     * @param context        faces context
     * @return converter
     */
    public static Converter getConverterForClass(Class converterClass, FacesContext context) {
        if (converterClass == null)
            return null;
        try {
            Application application = context.getApplication();
            return (application.createConverter(converterClass));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Coerce a value to its type
     *
     * @param context       faces context
     * @param value         value to coerce
     * @param itemValueType value class
     * @return coerced value
     */
    public static Object coerceToModelType(FacesContext context, Object value, Class itemValueType) {
        Object newValue;
        try {
            ExpressionFactory ef = context.getApplication().getExpressionFactory();
            newValue = ef.coerceToType(value, itemValueType);
        } catch (ELException ele) {
            newValue = value;
        } catch (IllegalArgumentException iae) {
            newValue = value;
        }
        return newValue;
    }

    /**
     * Get the values currently selected in a Select[Many|One]Listbox
     *
     * @param component the component
     * @return selected values as Object array
     */
    public static Object[] getCurrentSelectedValues(UIComponent component) {
        if (component instanceof UISelectMany) {
            UISelectMany select = (UISelectMany) component;
            Object value = select.getValue();
            if (value instanceof Collection)
                return ((Collection) value).toArray();
            else if (value != null && !value.getClass().isArray())
                LOG.warn("The UISelectMany value should be an array or a collection type, the actual type is "
                        + value.getClass().getName());
            return (Object[]) value;
        } else if (component instanceof UISelectOne) {
            //select one
            UISelectOne select = (UISelectOne) component;
            Object val = select.getValue();
            if (val != null)
                return new Object[] { val };
        }
        return new Object[0];
    }

    /**
     * Get the submitted values that are selected
     *
     * @param component the component
     * @return submitted values that are selected
     */
    public static 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;
    }

    /**
     * Get the number of options to render depending on the type of the items (eg item groups require more items to be rendered)
     *
     * @param selectItems the select items to inspect
     * @return number of options to be rendered
     */
    public static int getSelectlistOptionCount(List<SelectItem> selectItems) {
        int itemCount = 0;
        if (!selectItems.isEmpty()) {
            for (Object selectItem : selectItems) {
                itemCount++;
                if (selectItem instanceof SelectItemGroup)
                    itemCount += ((SelectItemGroup) selectItem).getSelectItems().length;
            }
        }
        return itemCount;

    }

    /**
     * Check if an item is selected
     *
     * @param context    faces context
     * @param component  our component
     * @param itemValue  the value to check for selection
     * @param valueArray all values to compare against
     * @param converter  the converter
     * @return selected
     */
    public static boolean isSelectItemSelected(FacesContext context, UIComponent component, Object itemValue,
            Object valueArray, Converter converter) {
        if (itemValue == null && valueArray == null)
            return true;
        if (null != valueArray) {
            if (!valueArray.getClass().isArray()) {
                LOG.warn("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; //selected
                }
            }
        }
        return false;
    }

    /**
     * Check if the given array contains an actual value
     *
     * @param valueArray value array
     * @return if an actual value is set in the array
     */
    public static 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(FxSelectRenderer.NO_VALUE))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Resets the cache client ID for the given component and its children.
     *
     * @param component the component
     * @since 3.1
     */
    public static void clearCachedClientIds(UIComponent component) {
        component.setId(component.getId());
        final Iterator<UIComponent> iter = component.getFacetsAndChildren();
        while (iter.hasNext()) {
            clearCachedClientIds(iter.next());
        }
    }
}