/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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 org.apache.myfaces.trinidadinternal.ui.laf.base.xhtml;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Stack;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.skin.Icon;
import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.AutoSubmitUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.FormRenderer;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.OutputUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.PartialPageUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.XhtmlUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.jsLibs.Scriptlet;
import org.apache.myfaces.trinidadinternal.share.config.Configuration;
import org.apache.myfaces.trinidadinternal.share.data.ServletRequestParameters;
import org.apache.myfaces.trinidadinternal.share.url.FormEncoder;
import org.apache.myfaces.trinidadinternal.style.ParsedPropertyKey;
import org.apache.myfaces.trinidadinternal.style.Style;
import org.apache.myfaces.trinidadinternal.style.StyleMap;
import org.apache.myfaces.trinidadinternal.style.util.StyleUtils;
import org.apache.myfaces.trinidadinternal.ui.AttributeKey;
import org.apache.myfaces.trinidadinternal.ui.MutableUINode;
import org.apache.myfaces.trinidadinternal.ui.NodeUtils;
import org.apache.myfaces.trinidadinternal.ui.UIConstants;
import org.apache.myfaces.trinidadinternal.ui.UINode;
import org.apache.myfaces.trinidadinternal.ui.UIXRenderingContext;
import org.apache.myfaces.trinidadinternal.ui.laf.base.BaseLafUtils;
import org.apache.myfaces.trinidadinternal.ui.laf.base.MutableProperty;


/**
 * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/ui/laf/base/xhtml/XhtmlLafUtils.java#0 $) $Date: 10-nov-2005.18:54:20 $
 * @deprecated This class comes from the old Java 1.2 UIX codebase and should not be used anymore.
 */
@Deprecated
public class XhtmlLafUtils extends BaseLafUtils
{
  protected XhtmlLafUtils()
  {
  }


  /**
   * Copies an attribute from a source node to a destination UINode
   */
  public static void copyAttr(
    UIXRenderingContext context,
    UINode           sourceNode,
    AttributeKey     attrKey,
    MutableUINode    destNode
    )
  {
    Object value = sourceNode.getAttributeValue(context, attrKey);

    if (value != null)
    {
      destNode.setAttributeValue(attrKey, value);
    }
  }

  /**
   * Registers a scriptlet.
   */
  public static  void registerScriptlet(Object key, Scriptlet scriptlet)
  {
    XhtmlUtils.registerScriptlet(key, scriptlet);
  }

  /**
   * Encodes an Iterator key value pairs as a single Javascript Object
   * initializer, creating any needed form values.
   */
  public static String encodeJSEventObject(
    UIXRenderingContext context,
    String           formName,
    Iterator<Object> keyValues,
    int              keyValueSize
    )
  {
    if ((keyValues == null) || !keyValues.hasNext())
      return null;

    if (keyValueSize < 20)
      keyValueSize = 20;

    // add 2 for starting and ending curly quotes
    StringBuilder jsObject = new StringBuilder(keyValueSize + 2);

    // append start of javascript property
    jsObject.append('{');

    // BUG 3557710 - FORM ENCODER AND POSTBACK HANDLING
    FormEncoder formEncoder = context.getFormEncoder();

    boolean isFirstKeyValue = true;

    try
    {
      do
      {
        if (isFirstKeyValue)
        {
          isFirstKeyValue = false;
        }
        else
        {
          jsObject.append(',');
        }

        Object key   = keyValues.next();
        Object value = keyValues.next();

        if (key != null)
        {
          String keyString = key.toString();

          jsObject.append('\'');
          jsObject.append(keyString);
          jsObject.append('\'');

          // add hidden field
          FormValueRenderer.addNeededValue(context, formName, keyString);

          jsObject.append(':');
          jsObject.append('\'');

          if (value != null)
          {
            // BUG 3557710 - FORM ENCODER AND POSTBACK HANDLING
            String encodedValue =
              XhtmlLafUtils.getFormEncodedParameter(formEncoder, formName,
                                                    keyString,
                                                    value);
            jsObject.append(encodedValue);
          }

          jsObject.append('\'');
        }

      } while (keyValues.hasNext());
    }
    catch (NoSuchElementException e)
    {
      throw new IllegalArgumentException(_LOG.getMessage(
        "NUMBER_OF_KEYS_AND_VALUES_MUCH_MATCH"));
    }

    // append end of javascript property
    jsObject.append('}');

    return jsObject.toString();
  }

  /**
   * Encodes an Iterator key value pairs as a single Javascript Object
   * initializer, creating any needed form values.
   */
  public static String encodeJSEventObject(
    UIXRenderingContext context,
    String           formName,
    Object[]         keyValues
    )
  {
    // overhead of ',' plus single quotes around keys and values is 5
    int keyValueSize = getKeyValueArraySize(keyValues, 5);

    if (keyValueSize > 0)
    {
      return encodeJSEventObject(context,
                                 formName,
                                 Arrays.asList(keyValues).iterator(),
                                 keyValueSize);
    }
    else
    {
      return null;
    }
  }


  /**
   * Resolves the class name to the appropriate Style object
   */
  public static Style getClassStyle(
    UIXRenderingContext context,
    Object           className
    )
  {
    if (className != null)
    {
      StyleMap map = context.getStyleContext().getStyleMap();
      if (map != null)
      {
        return map.getStyleByClass(context.getStyleContext(),
                                   className.toString());
      }
    }

    return null;
  }


  /**
   * Resolves the class name to the appropriate Style object,
   * then get a property from the Style
   */
  public static String getClassStyleProperty(
    UIXRenderingContext context,
    Object           className,
    String           propertyName
    )
  {
      Style classStyle = getClassStyle(context,
                                       className);
      if (classStyle != null)
      {
        return classStyle.getProperty(propertyName);
      }

      return null;
  }

  /**
   * Returns the short version of the specified style class.
   */
  public static Object getShortStyleClass(
    UIXRenderingContext context,
    Object           styleClass
    )
  {
    if (styleClass == null)
      return null;

    // =-=jmw @todo for now, map the keys here, but I want to do this
    // differently very soon.
    styleClass = context.getStyleClass(styleClass.toString());

    Object styleClasses = context.getProperty(UIConstants.MARLIN_NAMESPACE,
                                              _STYLE_CLASSES_PROPERTY);

    // If we didn't get the style classes Dictionary yet,
    // try to get it now
    if (styleClasses == null)
    {
      RenderingContext arc = RenderingContext.getCurrentInstance();
      styleClasses = context.getSkin().getStyleClassMap(arc);

      if (styleClasses == null)
        styleClasses = _NULL_STYLE_CLASSES;

      context.setProperty(UIConstants.MARLIN_NAMESPACE,
                          _STYLE_CLASSES_PROPERTY,
                          styleClasses);
    }

    // If we've got a non-null Dictionary, use it to get the
    // short style class
    Object shortStyleClass = null;
    if (styleClasses != _NULL_STYLE_CLASSES)
    {
      String styleClassKey = styleClass.toString();
      shortStyleClass = ((Map)styleClasses).get(styleClassKey);
      if (shortStyleClass != null)
        styleClass = shortStyleClass;
    }
    if (shortStyleClass == null)
    {
      // if we didn't shorten the style classes, then make sure the
      // namespace character '|' is not in the name.
      // we do the same thing in CSSUtils when we write the full selector
      // to the CSS file.
      styleClass = StyleUtils.convertToValidSelector(styleClass.toString());
    }

    return styleClass;
  }

  /**
   * Returns the mapping of a css vertical-align to a valign attribute value.
   */
  public static String mapVerticalAlignToVAlign(
    UIXRenderingContext context,
    String           verticalAlign
    )
  {
    if (verticalAlign != null)
    {
      return _sSupportedVAligns.get(verticalAlign);
    }
    else
    {
      return null;
    }
  }


  /**
   * Closes any tags started by startRenderingStyleElements
   */
  @SuppressWarnings("unchecked")
  public static void endRenderingStyleElements(
    UIXRenderingContext context
    ) throws IOException
  {
    Stack[] styleInfo = _getStyleInfo(context);

    Stack<Integer> styleFlagsStack = styleInfo[_STACK_FLAGS_INDEX];

    // get the flags of the elements started, popping the
    // current styleFlags
    int styleFlags = styleFlagsStack.pop().intValue();
    if (styleFlags != 0)
    {
      ResponseWriter writer = context.getResponseWriter();

      boolean fontEnded = false;

      //
      // close the started elements in reverse order, popping their
      // values off of the stack
      //
      for (int i = _STYLE_INFO_STACK_COUNT - 1; i > 0; i--)
      {
        if ((styleFlags & (1 << i)) != 0)
        {
          // pop off the
          styleInfo[i].pop();

          if (!fontEnded)
          {
            writer.endElement(_STYLE_ELEMENTS[i]);
            // only end font on the first info attribute that needs it
            if (i <= _FOREGROUND_INDEX)
            {
              fontEnded = true;
            }
          }
        }
      }
    }
  }

  /**
   * Renders the combination of inline and class style attributes
   * as elements
   */
  public static void startRenderingStyleElements(
    UIXRenderingContext context,
    Style            inlineStyle,
    Style            classStyle
    ) throws IOException
  {
    Stack[] styleInfo = _getStyleInfo(context);

    int flags = 0;

    //
    // if at least one Style object is present begin outputting
    // style elements
    //
    if ((inlineStyle != null) || (classStyle != null))
    {
      // handle BACKGROUND_KEY
      Object fontStyle = _getParsedStyleProperty(inlineStyle,
                                                 classStyle,
                                                 Style.FONT_STYLE_KEY);

      Boolean isItalic = (Style.ITALIC_FONT_STYLE == fontStyle)
                           ? Boolean.TRUE
                           : null;


      Object fontWeight = _getParsedStyleProperty(inlineStyle,
                                                  classStyle,
                                                  Style.FONT_WEIGHT_KEY);

      Boolean isBold = (Style.BOLD_FONT_WEIGHT == fontWeight)
                         ? Boolean.TRUE
                         : null;

      String fontFamilies = null;
      String foreground = null;

      String fontSize = getStyleProperty(inlineStyle,
                                         classStyle,
                                         "font-size");

      foreground = getStyleProperty(inlineStyle,
                                    classStyle,
                                    "color");


      fontFamilies = getStyleProperty(inlineStyle,
                                      classStyle,
                                      "font-family");


      boolean hasFontElement = (fontSize != null)   ||
                               (foreground != null) ||
                               (fontFamilies != null);

      ResponseWriter writer = context.getResponseWriter();

      if (hasFontElement)
      {
        int fontFlags = 0;

        // push on foreground property
        // was calling _pushStackPropertyIfDifferent, but inside table
        // only pushing if different doesn't work. See bug 2944365
        fontFlags |= _pushStackProperty(styleInfo,
                                        _FOREGROUND_INDEX,
                                        foreground);

        // push on font faces property
        // was calling _pushStackPropertyIfDifferent, but inside table
        // only pushing if different doesn't work. See bug 2944365
        fontFlags |= _pushStackProperty(styleInfo,
                                        _FONT_FACE_INDEX,
                                        fontFamilies);

        Object sizeAttr = fontSize;

        if (fontSize != null)
        {
          // try to map the raw css attribute to a font element
          // size attribute
          sizeAttr = _sSizeNameMap.get(fontSize);

          if (sizeAttr == null)
          {
            // try and map the parsed pixel value to a font
            // element
            Integer pixelSize = (Integer)_getParsedStyleProperty(
                                               inlineStyle,
                                               classStyle,
                                               Style.FONT_SIZE_KEY);

            if (pixelSize != null)
            {
              int pixSize = pixelSize.intValue();

              sizeAttr = (pixSize > _SIZE_MAPPING.length)
                           ? _SIZE_MAPPING[_SIZE_MAPPING.length - 1]
                           : _SIZE_MAPPING[pixSize];
            }
          }

          // push on font size property
          // was calling _pushStackPropertyIfDifferent, but inside table
          // only pushing if different doesn't work. See bug 2944365
          fontFlags |= _pushStackProperty(styleInfo,
                                          _FONT_SIZE_INDEX,
                                          sizeAttr);
        }

        //
        // write out the attribute values
        //
        if (fontFlags != 0)
        {
          writer.startElement("font", null);

          if ((fontFlags & (1 << _FOREGROUND_INDEX)) != 0)
          {
            writer.writeAttribute("color", foreground, null);
          }

          if ((fontFlags & (1 << _FONT_FACE_INDEX)) != 0)
          {
            writer.writeAttribute("face", fontFamilies, null);
          }

          if ((fontFlags & (1 << _FONT_SIZE_INDEX)) != 0)
          {
            writer.writeAttribute("size", sizeAttr, null);
          }

          // merge in the font flags
          flags |= fontFlags;
        }
      }

      //
      // handle italic element
      // was calling _pushStackPropertyIfDifferent, but inside table
      // only pushing if different doesn't work. See bug 2944365
      int currFlag = _pushStackProperty(styleInfo,
                                        _ITALIC_INDEX,
                                        isItalic);
      if (currFlag != 0)
      {
        flags |= currFlag;
        writer.startElement(_STYLE_ELEMENTS[_ITALIC_INDEX], null);
      }

      //
      // handle bold element
      //
      // was calling _pushStackPropertyIfDifferent, but inside table
      // only pushing if different doesn't work. See bug 2944365
      currFlag = _pushStackProperty(styleInfo, _BOLD_INDEX, isBold);

      if (currFlag != 0)
      {
        flags |= currFlag;
        writer.startElement(_STYLE_ELEMENTS[_BOLD_INDEX], null);
      }
    }

    // push the flags of the styles actually written
    Stack<Object> stackInfo = _getStyleInfoStack(styleInfo, _STACK_FLAGS_INDEX);
    stackInfo.push(flags);
  }



  /**
   * Returns the value of a parsed style property asking Style1 and then
   * Style2, if Style1 doesn't return a value.
   */
  private static Object _getParsedStyleProperty(
    Style             style1,
    Style             style2,
    ParsedPropertyKey stylePropertyKey
    )
  {
    Object value = (style1 != null)
                     ? style1.getParsedProperty(stylePropertyKey)
                     : null;

    if (value != null)
    {
      return value;
    }
    else
    {
      if (style2 != null)
      {
        return style2.getParsedProperty(stylePropertyKey);
      }
      else
      {
        return null;
      }
    }
  }

  /**
   * Returns the value of a style property asking Style1 and then
   * Style2, if Style1 doesn't return a value.
   */
  public static String getStyleProperty(
    Style  style1,
    Style  style2,
    String stylePropertyName
    )
  {
    String value = (style1 != null)
                     ? style1.getProperty(stylePropertyName)
                     : null;

    if (value != null)
    {
      return value;
    }
    else
    {
      if (style2 != null)
      {
        return style2.getProperty(stylePropertyName);
      }
      else
      {
        return null;
      }
    }
  }


  public static String createCompoundName(
    UIXRenderingContext context,
    String           formName,
    Object           keyValues
    )
  {
    return createCompoundName(context, formName, keyValues, null);

  }

  public static String createCompoundName(
    UIXRenderingContext context,
    String           formName,
    Object           keyValues,
    Object           keyExchanges
    )
  {
    String compoundName = encodeCompoundKeyValues(
                                (Object[]) keyValues,
                                (Object[]) keyExchanges);

    if (compoundName != null)
    {
      // record that we have encoded compound name
      FormValueRenderer.addNeededValue(
                                context,
                                formName,
                                ServletRequestParameters.HAS_COMPOUND_NAME);
    }

    return compoundName;
  }


   /**
   * Return the chained JavaScript
   */
  public static Object getChainedJS(
    Object evh1,
    Object evh2,
    boolean shortCircuit
    )
  {
    String evh1String = (evh1 == null) ? null : evh1.toString();
    String evh2String = (evh2 == null) ? null : evh2.toString();

    return XhtmlUtils.getChainedJS(evh1String, evh2String, shortCircuit);
  }


  /**
   * Handle escaping '/', and single quotes, plus escaping text inside of
   * quotes with just a String for input.  If a String in and a String out is
   * all that is required, this version is more efficient if the String
   * does not need to be escaped.
   */
  public static String escapeJS(
    String inString
    )
  {
    return XhtmlUtils.escapeJS(inString);
  }



  /**
   * Handle escaping '/', and single quotes, plus escaping text inside of
   * quotes with just a String for input.  If a String in and a String out is
   * all that is required, this version is more efficient if the String
   * does not need to be escaped.
   */
  public static String escapeJS(
    String  inString,
    boolean inQuotes
    )
  {
    return XhtmlUtils.escapeJS(inString, inQuotes);
  }


  /**
   * Handle escaping '/', and single quotes, plus escaping text inside of
   * quotes.
   */
  public static void escapeJS(
    StringBuilder outBuffer,
    String       inString
    )
  {
    XhtmlUtils.escapeJS(outBuffer, inString);
  }


  /**
   * Handle escaping '/', and single quotes, plus escaping text inside of
   * quotes.
   */
  public static void escapeJS(
    StringBuilder outBuffer,
    String       inString,
    boolean      inQuotes)
  {
    XhtmlUtils.escapeJS(outBuffer, inString, inQuotes);
  }

  /**
   * Handle escaping '/', and single quotes, plus escaping text inside of
   * quotes.
   */
  public static void escapeJS(
    StringBuilder outBuffer,
    String       inString,
    boolean      inQuotes,
    int          escapeCount
    )
  {
    XhtmlUtils.escapeJS(outBuffer, inString, inQuotes, escapeCount);
  }

  public static void addOnSubmitRequiredValidator(
    UIXRenderingContext context,
    UINode           node,
    String           requiredMessageKey,
    Object           nodeName
  )throws IOException
  {

    boolean requiredField = Boolean.TRUE.equals(
                         node.getAttributeValue(context, REQUIRED_ATTR));

    if ( requiredField )
    {

      // Bug 2748146: Don't do validation of a disabled field! If the field is
      // disabled, the user can't have updated it (there is one way for the
      // client to hurt themselves here: by changing the disabled state as part
      // of a PPR update after the user has updated the field).
      Object disabled = node.getAttributeValue(context, DISABLED_ATTR);

      if (!Boolean.TRUE.equals(disabled))
      {
        if (nodeName != null)
        {
          UIComponent component = NodeUtils.getUIComponent(context, node);

          if (component == null)
          {
            _LOG.warning("NULL_COMPONENT_FOR_NODE", node.getLocalName());
          }

          boolean unvalidated =
                   Boolean.TRUE.equals(node.getAttributeValue(context,
                                                UIConstants.UNVALIDATED_ATTR));

          FormRenderer.addOnSubmitConverterValidators(component,
                                                      null, //converter
                                                      null, // validator
                                                      nodeName.toString(),
                                                      unvalidated,
                                                      true,  // required
                                                      requiredMessageKey);
        }
        else
          _LOG.warning("NULL_NODE_NAME_NO_VALIDATOR_ADDED", node.getLocalName());
      }
    }
  }

  /**
   * Adds an import of a Javascript library if necessary.
   * Also imports any and all dependencies of that library (again,
   * if needed).
   */
  public static void addLib(
    UIXRenderingContext context,
    Object           libKey
    ) throws IOException
  {
    XhtmlUtils.addLib(context.getFacesContext(),
                      RenderingContext.getCurrentInstance(),
                      libKey);
  }


  /**
   * Write out a script element importing a library.
   */
  public static void writeLibImport(
    UIXRenderingContext context,
    Object           libURL) throws IOException
  {
    XhtmlUtils.writeLibImport(context.getFacesContext(),
                              RenderingContext.getCurrentInstance(),
                              libURL);
  }

  /**
   * Returns the partial targets associated with the specified node
   */
  public static String[] getPartialTargets(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return getPartialTargets(context, node, null);
  }

  /**
   * Returns the partial targets associated with the specified node,
   * including the specified ID.
   */
  public static String[] getPartialTargets(
    UIXRenderingContext context,
    UINode           node,
    Object           id
    )
  {
    // Make sure partial page rendering is supported
    if (!XhtmlLafRenderer.supportsPartialRendering(context))
      return null;

    if (!PartialPageUtils.isPPRActive(context.getFacesContext()))
      return null;

    // If the ID is null, get the ID from the node
    if (id == null)
      id = node.getAttributeValue(context, ID_ATTR);

    // Make sure partial page rendering is enabled
    Object mode = node.getAttributeValue(context, PARTIAL_RENDER_MODE_ATTR);
    if (PARTIAL_RENDER_MODE_SELF.equals(mode))
    {
      // If we don't have an ID, we don't have any targets
      if (id == null)
        return null;

      // We're all set... Use the node's ID as the partial target
      return new String[] { id.toString() };
    }

    return null;
  }

  /**
   * Returns a String value which can be used as the onclick handler for
   * an element which fires partial change events.
   *
   * @param destination The destination URL, which contains any
   *   event information, including the partialTargets parameter.
   */
  public static String getFirePartialChangeHandler(String destination)
  {
    return AutoSubmitUtils.getPartialGetScript(destination);
  }


  /**
   * Returns true if the agent supports transparent images.
   */
  public static boolean supportsTransparentImages(
    UIXRenderingContext context
    )
  {
    TrinidadAgent agent = context.getAgent();

    // =-=ags Temporarily disabling the use of transparent images
    // on ICE due to problems with tiling transparent images that
    // are affecting UIXVE.  This code should be removed once the
    // underlying ICE bug is fixed (supposedly ICE 5.4.1)
    if (TrinidadAgent.APPLICATION_ICE == agent.getAgentApplication())
      return false;

    //int encodings = ((Integer)agent.getCapability(
    //                                  AdfFacesAgent.CAP_IMAGE_ENCODINGS)).intValue();

    // If the Agent suports transparent PNG, we are good to go...
    //if ((encodings & AdfFacesAgent.IMAGE_ENCODINGS_CAP_TRANSPARENT_PNG) != 0)
    //  return true;

    if (agent.getCapability(TrinidadAgent.CAP_TRANSPARENT_PNG_TYPE_IMAGE) == Boolean.TRUE)
       return true;

    // Otherwise, check GIF suport...
    Configuration config = context.getConfiguration();

    //return (((encodings & AdfFacesAgent.IMAGE_ENCODINGS_CAP_GIF) != 0) &&
    //          !Boolean.FALSE.equals(config.getProperty(_GIF_ENABLED)));
    return ((agent.getCapability(TrinidadAgent.CAP_GIF_TYPE_IMAGE) == Boolean.TRUE) &&
               !Boolean.FALSE.equals(config.getProperty(_GIF_ENABLED)));

  }

  /**
   * Renders the specified Icon with the provided attributes.
   */
  public static void renderIcon(
    UIXRenderingContext context,
    Icon             icon,
    Object           shortDesc,
    Object           align
    ) throws IOException
  {
    renderIcon(context, icon, shortDesc, align, false);
  }

  /**
   * Renders the specified Icon with the provided attributes.
   */
  public static void renderIcon(
    UIXRenderingContext context,
    Icon             icon,
    Object           shortDesc,
    Object           align,
    boolean          embedded
    ) throws IOException
  {
    RenderingContext arc = RenderingContext.getCurrentInstance();
    FacesContext fContext = context.getFacesContext();
    OutputUtils.renderIcon(fContext, arc, icon, shortDesc, align, embedded);
  }

  /**
   * Returns the valign vAlign value for aligning image icons
   * vertically with text on the same line.
   */
  public static Object getMiddleIconAlignment(UIXRenderingContext context)
  {
    // =-= AEW I haven't been able to find an image alignment
    // that works well for all browsers.  "absmiddle" looks
    // great in IE, but that's a nonstandard hack. "middle"
    // should work OK everywhere, but looks terrible in both
    // IE and Netscape (but OK in Mozilla) "top"'s OK in Netscape.
    // For now, "top" in Netscape, "absmiddle" everywhere else

    String align = null;
    int agentApplication = context.getAgent().getAgentApplication();

    if (agentApplication == TrinidadAgent.APPLICATION_NETSCAPE)
    {
      align = UIConstants.V_ALIGN_TOP;
    }
    else
    {
      // Previously we used "middle" for all other browsers except
      // for Safari, where "absmiddle" was required for reasonable
      // results.  However, as far as I can tell, for images which
      // are evenly padded on top/bottom, absmiddle also looks
      // best on IE and Mozilla.  So, let's use absmiddle for
      // these browsers too.
      // =-= MLL Update: to address Bug # 3426092, alignment has been set to
      // "middle" to comply with HTML 4.01 Transitional Spec.

      align = UIConstants.V_ALIGN_MIDDLE;
    }

    return align;
  }

  /**
   * Encodes a form value using the supplied transformed name for the
   * currently scoped form.
   */
  public static Object getFormEncodedValue(
    UIXRenderingContext context,
    Object           transName,
    Object           value)
  {
    // BUG 3557710 - FORM ENCODER AND POSTBACK HANDLING
    FormEncoder formEncoder = context.getFormEncoder();
    return formEncoder.encodeFormValue(transName, value);
  }

  /**
   * Encodes a client parameter using the supplied transformed name for the
   * specified form.
   */
  public static String getFormEncodedParameter(
    FormEncoder      formEncoder,
    Object           formName,
    Object           transName,
    Object           value)
  {
    // BUG 3557710 - FORM ENCODER AND POSTBACK HANDLING
    Object encoded =
      formEncoder.encodeClientParameter(formName, transName, value);
    return (encoded != null) ? encoded.toString() : null;
  }

  /**
   *  Return true if readOnly attribute exists and set to true
   */
  public static boolean isReadOnly(UIXRenderingContext context, UINode node)
  {
    Object readOnly = node.getAttributeValue(context, READ_ONLY_ATTR);
    return Boolean.TRUE.equals(readOnly);
  }

  // This utility method is used to strip /**/ style comments out of
  // JavaScript code.  We strip comments out of scripts that are
  // included in the partial page response, since we actually comment
  // out all of these scripts to prevent them from being executed in
  // the hidden iframe.  If no comments are found, returns the provided
  // buffer.
  // Note: This method is only called by BodyRenderer and
  // ScriptBufferingResponseWriter, so we're leaving in
  // package-private.
  static StringBuilder __stripJSComments(StringBuilder buffer)
  {
    // We avoid reallocating the buffer until we actually
    // find a comment.  Actually, we should never find any
    // comments in production code.  This method really shouldn't
    // be needed, but we do all of this work just to be extra safe.
    StringBuilder strippedBuffer = null;

    // We use a simple state machine to track whether or not
    // we are inside a comment or opening/closing a comment.
    int state = _STRIP_STATE_START;

    // The start index of the portion of the string to copy
    int startIndex = 0;

    // The total buffer length
    int length = buffer.length();

    for (int i = 0; i < length; i++)
    {
      char c = buffer.charAt(i);

      switch (state)
      {
        case _STRIP_STATE_START:
          // Check for the opening '/'
          if (c == '/')
            state = _STRIP_STATE_SLASH;
          break;

        case _STRIP_STATE_SLASH:
          // We've seen a potential comment opening '/'.  Check
          // to see if this is really the start of a comment.
          if (c == '*')
          {
            state = _STRIP_STATE_COMMENT;

            // Copy the contents up to the start of the
            // comment into the strippedBuffer.
            if (strippedBuffer == null)
              strippedBuffer = new StringBuilder(length);

            strippedBuffer.append(buffer.substring(startIndex, i - 1));
          }
          else
          {
            state = _STRIP_STATE_START;
          }
          break;

        case _STRIP_STATE_COMMENT:
          // We're inside a comment.  Just for a closing '*'.
          if (c == '*')
            state = _STRIP_STATE_STAR;
          break;

        case _STRIP_STATE_STAR:
          // We've seen a potential comment closing '*'.  Check
          // to see if this is really the end of the comment.
          if (c == '/')
          {
            state = _STRIP_STATE_START;
            startIndex = i + 1;
          }
          else
          {
            state = _STRIP_STATE_COMMENT;
          }
          break;
      }
    }

    // We should never end in any state other than start.  Anything
    // else would indicate an invalid script!
    assert (state == _STRIP_STATE_START);

    // Check for anything left in the pipeline
    if (strippedBuffer != null)
    {
      if (state == _STRIP_STATE_START)
        strippedBuffer.append(buffer.substring(startIndex, length));

      return strippedBuffer;
    }

    // If there were no comments, just return the original buffer.
    return buffer;
  }

  /**
   * Stores the current RenderingContext away so that it can be
   * accessed a RenderingContext that is wrapped by the current
   * RenderingContext.  This method is meant to serve a very
   * specific and limited purpose related to partial page rendering.
   * Our partial page rendering implementation needs to get the
   * ID of each UINode as each node is rendered in order to check
   * whether the node is a partial target.  This code lives in
   * a RenderingContext.pushRenderedChild() implementation in the
   * BodyRenderer$PartialRenderingContext class.  We are able to
   * get IDs within this method by calling UINode.getAttributeValue(),
   * but since we don't have access to the current RenderingContext,
   * we end up passing the PartialRenderingContext.  This works fine
   * in most cases, but fails for UINodes in tables where the client
   * is using RenderingContext.transformName() to generate unique
   * IDs.  The problem is that the client's BoundValue needs access
   * to the TableRenderingContext - not the PartialRenderingContext -
   * in order to generate a row-specific ID.  In order to support
   * this specific case, we stash away the TableRenderingContext
   * so that the PartialRenderingContext - can get at it and
   * pass it to client BoundValues.
   *
   * Note: This is a short-term solution only.  Clients should not
   * have to explicitly call transformName() to generate unique
   * IDs.  The real solution is to support automatic ID transformation -
   * and to add a generic mechanism for tracking the rendering traversal
   * which provides access to the current RenderingContext.
   */
  @SuppressWarnings("unchecked")
  static void __pushCurrentRenderingContext(
    UIXRenderingContext context
    )
  {
    assert (context != null);

    // We use a Stack to store the RenderingContext, but...
    //
    // - Do we really support nested tables?! (In detail disclosure maybe?)
    // - We really don't need synchronization!
    // - Initial stack capacity is 10 - larger than we need!
    //
    // But I don't think performance is going to be an issue -
    // so let's just make sure this code is safe...

    Stack<UIXRenderingContext> stack = 
      (Stack<UIXRenderingContext>)context.getProperty(MARLIN_NAMESPACE,
                                                      _CURRENT_RENDERING_CONTEXT_PROPERTY);
    if (stack == null)
    {
      stack = new Stack<UIXRenderingContext>();
      context.setProperty(MARLIN_NAMESPACE,
                          _CURRENT_RENDERING_CONTEXT_PROPERTY,
                          stack);
    }

    stack.push(context);
  }

  /**
   * Reverses the last call to __pushCurrentRenderingContext()
   * by popping the current RenderingContext off of the stack.
   */
  @SuppressWarnings("unchecked")
  static void __popCurrentRenderingContext(
    UIXRenderingContext context
    )
  {
    Stack<UIXRenderingContext> stack = 
      (Stack<UIXRenderingContext>)context.getProperty(MARLIN_NAMESPACE,
                                                      _CURRENT_RENDERING_CONTEXT_PROPERTY);

    // Null stack here is a programmer error...
    assert (stack != null);

    stack.pop();
  }

  /**
   * Retrieves the current RenderingContext as specified
   * by the last call to __pushCurrentRenderingContext().
   */
  @SuppressWarnings("unchecked")
  static UIXRenderingContext __getCurrentRenderingContext(
    UIXRenderingContext context
    )
  {
    Stack<UIXRenderingContext> stack = 
      (Stack<UIXRenderingContext>)context.getProperty(MARLIN_NAMESPACE,
                                                      _CURRENT_RENDERING_CONTEXT_PROPERTY);

    if ((stack == null) || (stack.empty()))
      return null;

    return stack.peek();
  }


  // maps fonts size names to font element size strings
  static private HashMap<String, String> _sSizeNameMap = 
    new HashMap<String, String>(13);

  //
  // mapping of size names to font element size strings
  //
  static private final String[] _FONT_SIZE_NAME_MAPPING = new String[]
  {
    "xx-small", "1",
    "x-small",  "2",
    "small",    "3",
    "medium",   "4",
    "large",    "5",
    "x-large",  "6",
    "xx-large", "7",
    "smaller",  "-1",
    "larger",   "+1",
  };


  //
  // Supportyed vertical align css values
  //
  static private final String[] _VALIGNS_SUPPORTED = new String[]
  {
    "baseline",
    "top",
    "center",
    "middle",
    "bottom",
  };


  //
  // mapping of point sizes to font element size attributes
  //
  private static final String[] _SIZE_MAPPING =  new String[]
  {
    "1", // 0 pt
    "1", // 1 pt
    "1", // 2 pt
    "1", // 3 pt
    "1", // 4 pt
    "1", // 5 pt
    "1", // 6 pt
    "1", // 7 pt
    "1", // 8 pt
    "2", // 9 pt
    "3", // 10 pt
    "4", // 11 pt
    "4", // 12 pt
    "4", // 13 pt
    "5", // 14 pt
    "5", // 15 pt
    "6", // 16 pt
    "6", // 17 pt
    "7", // 18 pt
  };

  /**
   * Pushes a value onto a stack on the RenderingContext, returning the
   * mask value to OR into teh result.
   */
  // =-=gc You'd think you could only bother pushing if when the value
  // is different, but that didn't work inside tables.  See bug 2944365
  private static int _pushStackProperty(
    Stack[] styleInfo,
    int     stackIndex,
    Object  value
    )
  {
    if (value != null)
    {
      Stack<Object> styleInfoStack = _getStyleInfoStack(styleInfo, stackIndex);

      // push new value
      styleInfoStack.push(value);

      // set flag bit
      return 1 << stackIndex;
    }

    // same value
    return 0;
  }

  /**
   * Returns the Stack for a Style info, creating it, if necessary.
   */
  @SuppressWarnings("unchecked")
  private static Stack<Object> _getStyleInfoStack(
    Stack[] styleInfo,
    int     stackIndex
    )
  {
    Stack<Object> styleInfoStack = styleInfo[stackIndex];

    if (styleInfoStack == null)
    {
      // create new stack
      styleInfoStack = new Stack<Object>();

      // push on initial default
      styleInfoStack.push(_STYLE_DEFAULTS[stackIndex]);

      // save away new stack
      styleInfo[stackIndex] = styleInfoStack;
    }

    return styleInfoStack;
  }


  /**
   * Returns the style info array of style Stacks, creating the object if
   * necessary.
   */
  private static Stack[] _getStyleInfo(
    UIXRenderingContext context
    )
  {
    XhtmlLafUtils.StyleInfo styleInfo = (StyleInfo)
       getRenderingProperty(context, _STYLE_INFO_PROPERTY);

    if (styleInfo == null)
    {
      styleInfo = new XhtmlLafUtils.StyleInfo();
      setRenderingProperty(context, _STYLE_INFO_PROPERTY, styleInfo);
    }

    return styleInfo.getValue();
  }

  //
  // MutableProperty implementation that wraps up the array-of-stacks
  //
  /**
   * @deprecated This class comes from the old Java 1.2 UIX codebase and should not be used anymore.
   */
  @Deprecated
  private final static class StyleInfo implements MutableProperty
  {
    public StyleInfo()
    {
      this(new Stack[_STYLE_INFO_STACK_COUNT]);
    }

    public StyleInfo(Stack[] value)
    {
      _value = value;
    }

    public Stack[] getValue()
    {
      return _value;
    }

    @Override
    public Object clone()
    {
      int length = _value.length;
      Stack[] newValue = new Stack[length];
      for (int i = 0; i < length; i++)
      {
        if (_value[i] != null)
        {
          newValue[i] = (Stack) _value[i].clone();
        }
      }

      return new XhtmlLafUtils.StyleInfo(newValue);
    }

    private final Stack[] _value;
  }


  // Constants used by __stripJSComments()
  private static final int _STRIP_STATE_START   = 0;  // Start state
  private static final int _STRIP_STATE_SLASH   = 1;  // Open '/' is seen
  private static final int _STRIP_STATE_COMMENT = 2;  // Inside comment
  private static final int _STRIP_STATE_STAR    = 3;  // Closing '*' is seen


  //
  // Keys for RenderingContext stack properties
  //
  private static final Object _STYLE_INFO_PROPERTY = new Object();

  private static final int _STACK_FLAGS_INDEX = 0;
  private static final int _FONT_FACE_INDEX = 1;
  private static final int _FONT_SIZE_INDEX = 2;
  private static final int _FOREGROUND_INDEX = 3;
  private static final int _ITALIC_INDEX = 4;
  private static final int _BOLD_INDEX = 5;

  private static final int _STYLE_INFO_STACK_COUNT = _BOLD_INDEX + 1;

  private static final String[] _STYLE_ELEMENTS =
  {
    null,   // flags (dummy)
    "font", // font face
    "font", // font size
    "font", // foreground
    "i",    // italic
    "b",    // bold
  };

  private static final Object[] _STYLE_DEFAULTS =
  {
    null,           // flags (dummy)
    "",             // font face (dummy)
    "3",            // font size
    "#000000",      // foreground
    Boolean.FALSE,  // italic
    Boolean.FALSE,  // bold
  };

  /** HashMap mapping css vertical-align to the valign attribute values */
  private static Map<String, String> _sSupportedVAligns = null;

  /**
   * Initialize the library information.
   */
  static
  {
    //
    // initialize mapping of font size names to font element sizes
    //
    for (int i = 0; i < _FONT_SIZE_NAME_MAPPING.length; i += 2)
    {
      _sSizeNameMap.put(_FONT_SIZE_NAME_MAPPING[i],
                       _FONT_SIZE_NAME_MAPPING[i+1]);
    }


    //
    // initialize the set of supported css vertical alignments
    //
    _sSupportedVAligns = new HashMap<String, String>(13);

    for (int i = 0; i < _VALIGNS_SUPPORTED.length; i++)
    {
      _sSupportedVAligns.put(_VALIGNS_SUPPORTED[i], _VALIGNS_SUPPORTED[i]);
    }
  }

  // Configuration property to test whether GIF support is enabled.
  private static final String _GIF_ENABLED = "gifEnabled";

  // Key for storing current RenderingContext
  private static final Object _CURRENT_RENDERING_CONTEXT_PROPERTY =
    new Object();

  // Key for storing the short style classes Dictionary
  private static final Object _STYLE_CLASSES_PROPERTY = new Object();

  // Value for indicating that we have a null short style classes Dictionary
  private static final Object _NULL_STYLE_CLASSES = new Object();

  private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(XhtmlLafUtils.class);
}
