com.icesoft.faces.renderkit.dom_html_basic.DomBasicRenderer.java Source code

Java tutorial

Introduction

Here is the source code for com.icesoft.faces.renderkit.dom_html_basic.DomBasicRenderer.java

Source

/*
 * Copyright 2004-2013 ICEsoft Technologies Canada Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS
 * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

package com.icesoft.faces.renderkit.dom_html_basic;

import com.icesoft.faces.component.PORTLET_CSS_DEFAULT;
import com.icesoft.faces.context.DOMContext;
import com.icesoft.faces.context.effects.CurrentStyle;
import com.icesoft.faces.context.effects.JavascriptContext;
import com.icesoft.faces.util.CoreUtils;
import com.icesoft.faces.util.Debug;
import com.icesoft.util.pooling.ClientIdPool;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import org.w3c.dom.Element;

import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.component.NamingContainer;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIInput;
import javax.faces.component.UIMessage;
import javax.faces.component.UIParameter;
import javax.faces.component.ValueHolder;
import javax.faces.component.html.HtmlMessage;
import javax.faces.component.html.HtmlMessages;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.render.Renderer;
import java.beans.Beans;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public abstract class DomBasicRenderer extends Renderer {
    private static final Log log = LogFactory.getLog(DomBasicRenderer.class);
    public static final String ATTRIBUTES_THAT_ARE_SET_KEY = "javax.faces.component.UIComponentBase.attributesThatAreSet";

    // iceSubmitPartial
    public final static String ICESUBMITPARTIAL = "iceSubmitPartial(form, this, event);";
    // iceSubmit
    public final static String ICESUBMIT = "iceSubmit(form,this,event);";

    // component family constants for UIForm and WebUIForm
    public static final String WEB_UIFORM = "com.sun.rave.web.ui.Form";
    public static final String UIFORM = "javax.faces.form";
    public static final String WEB_UIJSFFORM = "com.sun.webui.jsf.Form";

    public void decode(FacesContext facesContext, UIComponent uiComponent) {
        CurrentStyle.decode(facesContext, uiComponent);
        validateParameters(facesContext, uiComponent, null);
        // only need to decode input components
        if (!(uiComponent instanceof UIInput)) {
            return;
        }
        // only need to decode enabled, writable components
        if (isStatic(uiComponent)) {
            return;
        }
        // extract component value from the request map
        String clientId = uiComponent.getClientId(facesContext);
        Debug.assertTrue(clientId != null, "Client id is not defined for decoding");
        Map requestMap = facesContext.getExternalContext().getRequestParameterMap();
        if (requestMap.containsKey(clientId)) {
            String decodedValue = (String) requestMap.get(clientId);
            setSubmittedValue(uiComponent, decodedValue);
        }
    }

    /**
     * This method should be overridden by renderers for components who subclass
     * UIInput
     *
     * @param uiComponent
     * @param value
     */
    public void setSubmittedValue(UIComponent uiComponent, Object value) {
    }

    /**
     * Delegate rendering to the renderEnd(..) method after validating
     * parameters and before maintaining the cursor position. The renderEnd
     * method should be overridden by subclasses of this class so that the
     * common infrastructure of parameter validation and cursor maintenance are
     * provided here.
     */
    public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException {
        validateParameters(facesContext, uiComponent, null);
        CoreUtils.recoverFacesMessages(facesContext, uiComponent);
        renderEnd(facesContext, uiComponent, getValue(facesContext, uiComponent));
        DOMContext domContext = DOMContext.getDOMContext(facesContext, uiComponent);

        domContext.stepOver();
        JavascriptContext.fireEffect(uiComponent, facesContext);
    }

    /**
     * Get the submitted value from the UIComponent argument. If the UIComponent
     * is not an instance of UIInput, or its <code>getSubmittedValue()</code>
     * method returns null or a non-String value, then an attempt is made to
     * obtain the value from the UIComponent's renderer. Conversion is performed
     * on a value obtained from the renderer.
     *
     * @param facesContext
     * @param uiComponent
     * @return String the submitted value
     */
    public String getValue(FacesContext facesContext, UIComponent uiComponent) {
        // for input components, get the submitted value
        if (uiComponent instanceof UIInput) {
            Object submittedValue = ((UIInput) uiComponent).getSubmittedValue();
            if (submittedValue != null && submittedValue instanceof String) {
                return (String) submittedValue;
            }
        }
        return formatComponentValue(facesContext, uiComponent, getValue(uiComponent));
    }

    Object getValue(UIComponent uiComponent) {
        return null;
    }

    /**
     * The common infrastructure of parameter validation and cursor management
     * will be provided by the encodeEnd method and rendering is delegated to
     * this method. Renderers should override this method instead of encodeEnd
     * to provide rendering at the time of execution of the encodeEnd method.
     *
     * @param facesContext
     * @param uiComponent
     * @param currentValue
     * @throws IOException
     */
    protected void renderEnd(FacesContext facesContext, UIComponent uiComponent, String currentValue)
            throws IOException {
    }

    /**
     * If the parameter UIComponent instance is a ValueHolder, return the
     * currentValue parameter. If there is a converter registered with the
     * component then use the converter to obtain a String value.
     *
     * @param facesContext
     * @param uiComponent
     * @param currentValue
     * @return
     * @throws ConverterException
     */
    protected String formatComponentValue(FacesContext facesContext, UIComponent uiComponent, Object currentValue)
            throws ConverterException {
        return converterGetAsString(facesContext, uiComponent, currentValue);
    }

    public static String converterGetAsString(FacesContext facesContext, UIComponent uiComponent,
            Object currentValue) {
        if (!(uiComponent instanceof ValueHolder)) {
            if (currentValue != null) {
                return currentValue.toString();
            } else {
                return null;
            }
        }

        // look to see whether there is a converter registered with the component
        Converter converter = ((ValueHolder) uiComponent).getConverter();

        // if there is a converter, use it
        if (converter != null) {
            return converter.getAsString(facesContext, uiComponent, currentValue);
        }

        // if there was no converter registered with the component then
        // look for the converter associated with the class of the currentValue
        String ret = "";
        if (currentValue != null) {
            converter = getConverterForClass(currentValue.getClass());
            if (converter != null) {
                ret = converter.getAsString(facesContext, uiComponent, currentValue);
            } else {
                ret = currentValue.toString();
            }
        }
        return ret;
    }

    /**
     * Find the UIComponent whose id is given by the for attribute of the
     * UIComponent parameter.
     *
     * @param facesContext
     * @param uiComponent
     * @return the UIComponent associated with the component id indicated by the
     *         value of the for attribute of the UIComponent parameter.
     */
    public static UIComponent findForComponent(FacesContext facesContext, UIComponent uiComponent) {

        String forComponentId = null;
        if (uiComponent instanceof UIMessage) {
            forComponentId = ((UIMessage) uiComponent).getFor();
        } else {
            forComponentId = (String) uiComponent.getAttributes().get(HTML.FOR_ATTR);
        }

        if (forComponentId == null) {
            return null;
        }
        if (forComponentId.length() == 0) {
            return null;
        }

        // Look for the 'for' component in the nearest parental naming container 
        // of the UIComponent (there's actually a bit more to this search - see
        // the docs for the findComponent method
        UIComponent forComponent = uiComponent.findComponent(forComponentId);

        // Since the nearest naming container may be nested, search the 
        // next-to-nearest parental naming container in a recursive fashion, 
        // until we get to the view root
        if (forComponent == null) {
            UIComponent nextParent = uiComponent;
            while (true) {
                nextParent = nextParent.getParent();
                // avoid extra searching by going up to the next NamingContainer
                // (see the docs for findComponent for an information that will
                // justify this approach)
                while (nextParent != null && !(nextParent instanceof NamingContainer)) {
                    nextParent = nextParent.getParent();
                }
                if (nextParent == null) {
                    break;
                } else {
                    forComponent = nextParent.findComponent(forComponentId);
                }
                if (forComponent != null) {
                    break;
                }
            }
        }

        // There is one other situation to cover: if the 'for' component 
        // is not situated inside a NamingContainer then the algorithm above
        // will not have found it. We need, in this case, to search for the 
        // component from the view root downwards
        if (forComponent == null) {
            forComponent = searchDownwardsForChildComponentWithId(facesContext.getViewRoot(), forComponentId);
        }
        return forComponent;
    }

    /**
     * Retrieve the array of excluded attributes. This array should be
     * constructed in the renderer class and then passed in to the
     * PassThruAttributeRenderer.
     *
     * @return a String array of excluded attributes.
     */
    public static String[] getExcludesArray(Set excludes) {
        String[] excludesArray = new String[excludes.size()];
        excludes.toArray(excludesArray);
        return excludesArray;
    }

    private static UIComponent searchDownwardsForChildComponentWithId(UIComponent parent, String searchChildId) {
        UIComponent foundChild = null;
        if (parent.getChildCount() == 0)
            return foundChild;
        Iterator children = parent.getChildren().iterator();
        UIComponent nextChild = null;
        while (children.hasNext()) {
            nextChild = (UIComponent) children.next();
            if (nextChild instanceof NamingContainer) {
                foundChild = nextChild.findComponent(searchChildId);
            }
            if (foundChild == null) {
                searchDownwardsForChildComponentWithId(nextChild, searchChildId);
            }
            if (foundChild != null) {
                break;
            }
        }
        return foundChild;
    }

    /**
     * Recursively render the parent UIComponent instance and its children.
     *
     * @param facesContext
     * @param parent
     * @throws IOException
     */
    public static void encodeParentAndChildren(FacesContext facesContext, UIComponent parent) throws IOException {
        parent.encodeBegin(facesContext);
        if (parent.getRendersChildren()) {
            parent.encodeChildren(facesContext);
        } else {
            if (parent.getChildCount() > 0) {
                Iterator children = parent.getChildren().iterator();
                while (children.hasNext()) {
                    UIComponent nextChild = (UIComponent) children.next();
                    if (nextChild.isRendered()) {
                        encodeParentAndChildren(facesContext, nextChild);
                    }
                }
            }
        }
        parent.encodeEnd(facesContext);
    }

    protected static UIComponent getFacetByName(UIComponent uiComponent, String name) {
        UIComponent facet = uiComponent.getFacet(name);
        if (facet == null) {
            return null;
        }
        if (!facet.isRendered()) {
            return null;
        }
        return facet;
    }

    static boolean idNotNull(UIComponent uiComponent) {
        return (uiComponent.getId() != null);
    }

    /**
     * Set the id of the root element of the DOMContext associated with the
     * UIComponent parameter.
     *
     * @param facesContext
     * @param rootElement
     * @param uiComponent
     */
    public static void setRootElementId(FacesContext facesContext, Element rootElement, UIComponent uiComponent) {
        if (idNotNull(uiComponent)) {
            rootElement.setAttribute("id", uiComponent.getClientId(facesContext));
            rootElement.setIdAttribute("id", true);
        }
    }

    /**
     * <p/>
     * Sets a non-null, non-empty-string, UIComponent property to the
     * corresponding DOM Element
     * <p/>
     *
     * @param uiComponent         the source of the attribute value
     * @param targetElement       the DOM Element that will receive the
     *                            attribute
     * @param attrNameInComponent the property name in the UIComponent object
     * @param attrNameInDom       the attribute name in the DOM Element
     */
    public static void renderAttribute(UIComponent uiComponent, Element targetElement, String attrNameInComponent,
            String attrNameInDom) {
        Object attrValue = uiComponent.getAttributes().get(attrNameInComponent);
        if (attrValue != null && !attrValue.equals("")) {
            if (attrValue.toString().equalsIgnoreCase("true") || attrValue.toString().equalsIgnoreCase("false")) {
                boolean trueValue = new Boolean(attrValue.toString()).booleanValue();
                if (!trueValue) {
                    targetElement.removeAttribute(attrNameInDom.toString());
                    return;
                }
            }
            targetElement.setAttribute(attrNameInDom.toString(), attrValue.toString());
        }
    }

    /**
     * Due to the behaviour of the UIParameter class, the names in the
     * name-value pairs of the Map returned by this method are guaranteed to be
     * Strings
     *
     * @param uiComponent
     * @return Map the parameterMap
     */
    protected static Map getParameterMap(UIComponent uiComponent) {
        Map parameterMap = new HashMap();
        if (uiComponent.getChildCount() > 0) {
            Iterator children = uiComponent.getChildren().iterator();
            while (children.hasNext()) {
                UIComponent nextChild = (UIComponent) children.next();
                if (nextChild instanceof UIParameter) {
                    UIParameter uiParam = (UIParameter) nextChild;
                    parameterMap.put(uiParam.getName(), uiParam.getValue());
                }
            }
        }
        return parameterMap;
    }

    /**
     * Validates that the facesContext is not null, the uiComponent is not null,
     * and that uiComponent is assignment-compatible with the
     * validComponentType. Pass a null parameter for validComponentType to avoid
     * any type checking.
     *
     * @param facesContext
     * @param uiComponent
     * @param validComponentType
     * @throws NullPointerException if either of the facesContext or the
     *                              uiComponent parameters are null or
     *                              if a parent form is not
     *                              found when the given UIComponent
     *                              is a UIInput or UICommand,
     *                              IllegalArgumentException if the
     *                              validComponentType is not null and the
     *                              uiComponent is not assignable to the given
     *                              type.
     */
    public static void validateParameters(FacesContext facesContext, UIComponent uiComponent,
            Class validComponentType) {

        if (facesContext == null) {
            throw new NullPointerException("Invalid Parameter - FacesContext instance must not be null");
        }
        if (uiComponent == null) {
            throw new NullPointerException("Invalid Parameter - UIComponent instance must not be null");
        }
        if (!Beans.isDesignTime() && validComponentType != null && !(validComponentType.isInstance(uiComponent))) {
            throw new IllegalArgumentException("Invalid Parameter - UIComponent class should be ["
                    + validComponentType + "] but it is an instance of [" + uiComponent.getClass() + "]");
        }

        if (log.isDebugEnabled()) {
            if ((uiComponent instanceof UIInput) || (uiComponent instanceof UICommand)) {
                if (findForm(uiComponent) == null) {
                    log.debug("Missing Form - the UIComponent of type [" + uiComponent.getClass()
                            + "] requires a containing form.");
                }
            }
        }

    }

    /**
     * A component is static if it is disabled or readonly.
     *
     * @param uiComponent
     * @return true if the component is disabled or readonly
     */
    public static boolean isStatic(UIComponent uiComponent) {
        // the algorithm here is to return true as soon as we get affirmation that
        // the component is static
        boolean isStatic = false;
        Object disabled = uiComponent.getAttributes().get("disabled");
        Object readonly = uiComponent.getAttributes().get("readonly");
        if (disabled != null) {
            if (disabled instanceof Boolean) {
                isStatic = ((Boolean) disabled).booleanValue();
            } else if (disabled instanceof String) {
                isStatic = ((String) disabled).equalsIgnoreCase("true");
            }
        }
        if (isStatic) {
            return isStatic;
        }
        if (readonly != null) {
            if (readonly instanceof Boolean) {
                return ((Boolean) readonly).booleanValue();
            }
            if (readonly instanceof String) {
                return ((String) readonly).equalsIgnoreCase("true");
            }
        }
        return isStatic;
    }

    /**
     * <p/>
     * Given a UIComponent instance, recursively examine the heirarchy of parent
     * UIComponents until the first NamingContainer is found. </p>
     *
     * @param uiComponent
     * @return the nearest parent NamingContainer or null if none exist.
     */
    public static UIComponent findNamingContainer(UIComponent uiComponent) {
        UIComponent parent = uiComponent.getParent();
        while (parent != null) {
            if (parent instanceof NamingContainer) {
                break;
            }
            parent = parent.getParent();
        }
        return parent;
    }

    /**
     * <p/>
     * Given a UIComponent instance, recursively examine the heirarchy of parent
     * NamingContainers until a Form is found. </p>
     *
     * @param uiComponent the UIComponent instance
     * @return form as the UIComponent instance
     */
    public static UIComponent findForm(UIComponent uiComponent) {
        UIComponent parent = uiComponent.getParent();
        while (parent != null && !(parent instanceof UIForm)) {
            parent = findNamingContainer(parent);
        }
        UIComponent form = null;
        // check family 
        if (parent != null
                && (parent.getFamily().equalsIgnoreCase(WEB_UIFORM) || parent.getFamily().equalsIgnoreCase(UIFORM)
                        || parent.getFamily().equalsIgnoreCase(WEB_UIJSFFORM))) {
            form = (UIComponent) parent;
        }

        if (form == null && Beans.isDesignTime()) {
            form = uiComponent.getParent();
        }
        return form;
    }

    /**
     * This method fabricates the clientId of a component. It should be used
     * only when the clientId of the uiComponent is required in advance of the
     * component existing. The uiComponentId may be provided by, for example, a
     * label element with a 'for' attribute defined. The for attribute will be
     * the id of the component that will eventually be created.
     * <p/>
     * Determine the id of the nearest parental naming container and prepend it
     * to the id of the component's id.
     *
     * @param uiComponent
     * @param facesContext
     * @param uiComponentId
     * @return
     */
    String fabricateClientId(UIComponent uiComponent, FacesContext facesContext, String uiComponentId) {
        UIComponent parentNamingContainer = findNamingContainer(uiComponent);
        String parentNamingContainerClientId = null;
        if (parentNamingContainer == null) {
            return uiComponentId;
        } else {
            parentNamingContainerClientId = parentNamingContainer.getClientId(facesContext);
        }
        return parentNamingContainerClientId + NamingContainer.SEPARATOR_CHAR + uiComponentId;
    }

    protected String[] getColumnStyleClasses(UIComponent uiComponent) {
        return getStyleClasses(uiComponent, "columnClasses");
    }

    /**
     * This method, given a component, will return an array of the component's
     * row classes.
     *
     * @param uiComponent
     * @return a String array of row classes defined in a tag attribute or
     *         defined by default, depending on the component. Can be a
     *         zero-length array
     */
    public String[] getRowStyleClasses(UIComponent uiComponent) {
        return getStyleClasses(uiComponent, "rowClasses");
    }

    public String[] getStyleClasses(UIComponent uiComponent, String styleClassAttributeName) {
        String allStyleClasses = (String) uiComponent.getAttributes().get(styleClassAttributeName);
        if (allStyleClasses == null) {
            return (new String[0]);
        }

        String separator = ",";
        if (allStyleClasses.indexOf(separator) <= 0) {
            separator = " ";
        }
        String[] styleClassesArray = allStyleClasses.trim().split(separator);
        int numberOfStyles = styleClassesArray.length;
        for (int i = 0; i < numberOfStyles; i++) {
            styleClassesArray[i] = styleClassesArray[i].trim();
        }
        return styleClassesArray;
    }

    /**
     * Get the style and style class associated with the severity of the
     * FacesMessage
     *
     * @param uiComponent
     * @param facesMessage
     * @return
     */
    static String[] getStyleAndStyleClass(UIComponent uiComponent, FacesMessage facesMessage) {
        // obtain the severity style and severity style class
        String severityStyle = null;
        String severityStyleClass = null;
        String baseStyle = null;
        Severity messageSeverity = facesMessage.getSeverity();
        if (messageSeverity == FacesMessage.SEVERITY_INFO) {
            severityStyle = (String) uiComponent.getAttributes().get("infoStyle");
            if (uiComponent instanceof HtmlMessage) {
                severityStyleClass = ((HtmlMessage) uiComponent).getInfoClass();
                baseStyle = "iceMsg";
            } else if (uiComponent instanceof HtmlMessages) {
                severityStyleClass = ((HtmlMessages) uiComponent).getInfoClass();
                baseStyle = "iceMsgs";
            }
            if (uiComponent.getRendererType().startsWith("com.icesoft.faces.Message")) {
                severityStyleClass = CoreUtils.addPortletStyleClassToQualifiedClass(severityStyleClass,
                        baseStyle + "Info", PORTLET_CSS_DEFAULT.PORTLET_MSG_INFO);
            }
        } else if (messageSeverity == FacesMessage.SEVERITY_WARN) {
            severityStyle = (String) uiComponent.getAttributes().get("warnStyle");
            if (uiComponent instanceof HtmlMessage) {
                severityStyleClass = ((HtmlMessage) uiComponent).getWarnClass();
                baseStyle = "iceMsg";
            } else if (uiComponent instanceof HtmlMessages) {
                severityStyleClass = ((HtmlMessages) uiComponent).getWarnClass();
                baseStyle = "iceMsgs";
            }
            if (uiComponent.getRendererType().startsWith("com.icesoft.faces.Message")) {
                severityStyleClass = CoreUtils.addPortletStyleClassToQualifiedClass(severityStyleClass,
                        baseStyle + "Warn", PORTLET_CSS_DEFAULT.PORTLET_MSG_ALERT);
            }
        } else if (messageSeverity == FacesMessage.SEVERITY_ERROR) {
            severityStyle = (String) uiComponent.getAttributes().get("errorStyle");
            if (uiComponent instanceof HtmlMessage) {
                severityStyleClass = ((HtmlMessage) uiComponent).getErrorClass();
                baseStyle = "iceMsg";
            } else if (uiComponent instanceof HtmlMessages) {
                severityStyleClass = ((HtmlMessages) uiComponent).getErrorClass();
                baseStyle = "iceMsgs";
            }
            if (uiComponent.getRendererType().startsWith("com.icesoft.faces.Message")) {
                severityStyleClass = CoreUtils.addPortletStyleClassToQualifiedClass(severityStyleClass,
                        baseStyle + "Error", PORTLET_CSS_DEFAULT.PORTLET_MSG_ERROR);
            }

        } else if (messageSeverity == FacesMessage.SEVERITY_FATAL) {
            severityStyle = (String) uiComponent.getAttributes().get("fatalStyle");
            if (uiComponent instanceof HtmlMessage) {
                severityStyleClass = ((HtmlMessage) uiComponent).getFatalClass();
            } else if (uiComponent instanceof HtmlMessages) {
                severityStyleClass = ((HtmlMessages) uiComponent).getFatalClass();
            }
        }

        String style = null;
        if (severityStyle != null) {
            style = severityStyle;
        } else {
            style = (String) uiComponent.getAttributes().get("style");
        }
        String styleClass = null;
        if (severityStyleClass != null) {
            styleClass = severityStyleClass;
        } else {
            if (uiComponent instanceof HtmlMessage) {
                styleClass = ((HtmlMessage) uiComponent).getStyleClass();
            } else if (uiComponent instanceof HtmlMessages) {
                styleClass = ((HtmlMessages) uiComponent).getStyleClass();
            }
        }
        return new String[] { style, styleClass };
    }

    /**
     * @param facesMessage
     * @return
     */
    String[] getSummaryAndDetail(FacesMessage facesMessage) {
        String summary = facesMessage.getSummary();
        if (summary == null) {
            summary = "";
        }
        String detail = facesMessage.getDetail();
        if (detail == null) {
            detail = "";
        }
        return new String[] { summary, detail };
    }

    /**
     * @param uiComponent
     * @return
     */
    boolean getToolTipAttribute(UIComponent uiComponent) {
        boolean tooltip = false;
        Object tooltipAttribute = uiComponent.getAttributes().get("tooltip");
        if (tooltipAttribute instanceof Boolean && ((Boolean) tooltipAttribute).booleanValue()) {
            tooltip = true;
        }
        return tooltip;
    }

    /**
     * @param converterClass
     * @return
     */
    static Converter getConverterForClass(Class converterClass) {
        if (converterClass == null) {
            return null;
        }
        try {
            FacesContext ctx = FacesContext.getCurrentInstance();
            Application application = ctx.getApplication();
            return (application.createConverter(converterClass));
        } catch (Exception e) {
            return (null);
        }
    }

    public static String getResourceURL(FacesContext context, String path) {
        return CoreUtils.resolveResourceURL(context, path);
    }

    /**
     * This is a utility method for concatenating two Strings, where passThru
     * is typically null or an empty String, and renderer is usually non-null,
     * but can in theory be null, and we want to minimise needless new String
     * creation.
     *
     * @param passThru The passthru attribute from the component
     * @param renderer The Javascript that the Renderer needs to output
     * @return A String concatenation of passThru + renderer
     */
    public static String combinedPassThru(String passThru, String renderer) {
        int passThruLen = (passThru == null) ? 0 : passThru.length();
        int rendererLen = (renderer == null) ? 0 : renderer.length();
        if (passThruLen == 0 && rendererLen == 0)
            return null;
        if (passThruLen == 0)
            return renderer;
        if (rendererLen == 0)
            return passThru;
        return passThru + renderer;
    }

    public String convertClientId(FacesContext context, String clientId) {
        return ClientIdPool.get(clientId);
    }
}