/*
 *  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.Iterator;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.validator.Validator;

import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
import org.apache.myfaces.trinidadinternal.image.ImageConstants;
import org.apache.myfaces.trinidadinternal.image.ImageContext;
import org.apache.myfaces.trinidadinternal.image.ImageProvider;
import org.apache.myfaces.trinidadinternal.image.ImageProviderRequest;
import org.apache.myfaces.trinidadinternal.image.ImageProviderResponse;
import org.apache.myfaces.trinidad.context.PartialPageContext;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.FormRenderer;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.SkinSelectors;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.XhtmlRenderer;
import org.apache.myfaces.trinidadinternal.style.Style;
import org.apache.myfaces.trinidadinternal.ui.AttributeKey;
import org.apache.myfaces.trinidadinternal.ui.NodeRole;
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.beans.MarlinBean;
import org.apache.myfaces.trinidadinternal.ui.laf.base.BaseLafRenderer;
import org.apache.myfaces.trinidadinternal.ui.laf.base.IconURIBoundValue;
import org.apache.myfaces.trinidadinternal.ui.laf.base.LafIconProvider;
import org.apache.myfaces.trinidadinternal.ui.laf.base.NodeRoleUtils;
import org.apache.myfaces.trinidadinternal.ui.partial.PartialPageRendererUtils;
import org.apache.myfaces.trinidadinternal.util.FormattedTextParser;
import org.apache.myfaces.trinidad.util.IntegerUtils;


/**
 * Base Rendering class for HTML renderers
 * <p>
 * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/ui/laf/base/xhtml/XhtmlLafRenderer.java#1 $) $Date: 11-nov-2005.14:59:38 $
 * @deprecated This class comes from the old Java 1.2 UIX codebase and should not be used anymore.
 */
@Deprecated
public class XhtmlLafRenderer extends BaseLafRenderer
                                   implements XhtmlLafConstants
{
  public static final String TRANSPARENT_GIF = "t.gif";

  /**
   * Override to handle case where this element isn't in a supported module
   * of the current user agent
   */
  @Override
  public void render(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    if (isSupportedNode(context, node))
    {
      // render the UINOde
      super.render(context, node);
    }
    else
    {
      // not supported so kick out warning
      if (_LOG.isWarning())
        _LOG.warning("UNSUPPORTED_UINODE", node.getLocalName());
    }
  }


  /**
   * Method returning true if the UINode is supported.  Designed to handle
   * cases where a particular node can't be supported because it relies
   * on a module of XHTML that isn't supported by the current user agent.
   */
  public boolean isSupportedNode(
    UIXRenderingContext context,
    UINode           node
    )
  {
    // assume that all UINodes are supported
    return true;
  }

  /**
   * @param node the structural role of this node is compared with expectedRole
   * @param expectedRole the role that must be satisfied
   * @return true iff the structural role of node satisfies the expectedRole.
   * */
  public static boolean structureSatisfiesRole(UIXRenderingContext context,
                                               UINode node,
                                               NodeRole expectedRole)
  {
    if (node != null)
    {
      NodeRole role = NodeRoleUtils.getStructuralRole(context, node);
      return  (role != null) && role.satisfiesRole(expectedRole);
    }
    return false;
  }

  /**
   * Returns true if the agent supports partial rendering of content.
   */
  public static boolean supportsPartialRendering(
    UIXRenderingContext context
    )
  {
    return PartialPageRendererUtils.supportsPartialRendering(context);
  }

  /**
   * Returns true if the agent supports the <code>button</code> tag.
   */
  public static boolean supportsAdvancedButtons(
    UIXRenderingContext context
    )
  {
    return getBooleanAgentCapability(context, TrinidadAgent.CAP_ADVANCED_BUTTONS);
  }

  /**
   * Returns true if the agent supports vertical alignment
   */
  public static boolean supportsVAlign(
    UIXRenderingContext context
    )
  {
    return getBooleanAgentCapability(context, TrinidadAgent.CAP_VALIGN);
  }

  /**
   * Returns true if the agent supports wrapping
   */
  public static boolean supportsWrappingDisabled(
    UIXRenderingContext context
    )
  {
    return getBooleanAgentCapability(context, TrinidadAgent.CAP_NOWRAP);
  }

  /**
   * Returns true if the agent supports alt as a tooltip on images
   */
  public static boolean supportsAltRendersTooltipOnImage(
    UIXRenderingContext context
    )
  {
    return getBooleanAgentCapability(context,
                                     TrinidadAgent.CAP_ALT_RENDERS_TOOLTIP_ON_IMAGE);
  }

  /**
   * Returns true if we should render the style elements instead of the
   * style attributes
   */
  public static boolean renderStyleElements(
    UIXRenderingContext context
    )
  {

    return !supportsStyleAttributes(context) &&
           supportsTextPresentation(context);
  }


  /**
   * Override to add support for rendering syle elements
   */
  @Override
  protected void prerender(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    super.prerender(context, node);

    if (doRenderStyleAttrs(context, node))
    {
      Object styleClass = _getLocalStyleClass(context, node);
      //Object inlineStyle = _getLocalInlineStyle(context, node);

      if (renderStyleElements(context))
      {
        startRenderingStyleElements(context,
                                    null,
                                    /*=-=AEW inlineStyle*/ styleClass);
      }

      // Push the style attrs onto the style attr stack
      XhtmlLafUtils.pushStyleAttrs(context,
                                   (styleClass != null)
                                     ? styleClass.toString()
                                     : null,
                                   null /*=-=AEW inlineStyle*/);
    }
  }

  /**
   * Override to add support for rendering syle elements
   */
  @Override
  protected void postrender(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    if (doRenderStyleAttrs(context, node))
    {
      if (renderStyleElements(context))
      {
        XhtmlLafUtils.endRenderingStyleElements(context);
      }

      XhtmlLafUtils.popStyleAttrs(context);
    }

    super.postrender(context, node);
  }

  protected void renderShortDesc(UIXRenderingContext context,
                                 UINode node) throws IOException
  {
    // there is a certain amount of controversy about writing this attribute.
    // see bug 1606882
    renderAttribute(context, node, "title", SHORT_DESC_ATTR);
  }


  protected void renderAttributesExceptID(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    renderShortDesc(context, node);

    // render the styles if the style attributes are supported
    if (supportsStyleAttributes(context) &&
        doRenderStyleAttrs(context, node))
    {
      renderStyleAttrs(context, node);
    }

    // render the events if the event attribtues are supported
    if (supportsIntrinsicEvents(context))
    {
      renderEventHandlers(context, node);
    }
  }

  @Override
  protected void renderAttributes(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    // render the id
    renderID(context, node);

    renderAttributesExceptID(context, node);
  }


  /**
   * Renders the id of the UINode for html elements that have deprecated name
   * in favor of id. These elements are: &lt;a&gt;, &lt;applet&gt;,
   * &lt;form&gt;, &lt;frame&gt;, &lt;iframe&gt;, &lt;img&gt;, &lt;map&gt;
   * <p>
   * This method is expected to be called by an override of
   * <code>renderID</code> in renderers that render the above elements.
   * Such renderers should also override <code>getID</code> to call
   * <code>getIDOrName</code>.
   * @see #getIDOrName
   * @see #getCachedIDOrName */
  protected final void renderNameAndID(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    Object id = getID(context, node);

    if (id != null)
    {
      ResponseWriter writer = context.getResponseWriter();

      // only ouput the id if the id is supported
      renderID( context, id, false);

      // only output the name if the name is supported
      if (supportsNameIdentification(context))
      {
        writer.writeAttribute("name", id, null);
      }
    }
  }

  public static void renderScriptOnce(
    UIXRenderingContext context,
    String script,
    Object key
    ) throws IOException
  {
    if (supportsScripting(context))
    {
      // check to see if we've written this already
      if (!isPreviouslyRendered(context, key))
      {
        // nope, write it out now
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement(UIConstants.SCRIPT_NAME, null);
        renderScriptDeferAttribute(context);

        // Bug #3426092:
        // render the type="text/javascript" attribute in accessibility mode
        XhtmlLafRenderer.renderScriptTypeAttribute(context);

        writer.writeText(script, null);
        writer.endElement(UIConstants.SCRIPT_NAME);
      }
    }
  }

  /**
   * Use this method to make sure that certain code is called only once per
   * render cycle.
   * @param key This key is used to set a property on the context to indicate
   * that this method has been previously called.
   * @return The first time this method is called with a specific context and
   * key, it returns false. On each subsequent call, it returns true.  */
  public static boolean isPreviouslyRendered(UIXRenderingContext context,
                                             Object key)
  {
    if (context.getProperty(MARLIN_NAMESPACE, key) == null)
    {
      context.setProperty(MARLIN_NAMESPACE, key, key);
      return false;
    }
    return true;
  }

  /**
   * Returns the id of the UINode for html elements that have deprecated name
   * in favor of id. These elements are: &lt;a&gt;, &lt;applet&gt;,
   * &lt;form&gt;, &lt;frame&gt;, &lt;iframe&gt;, &lt;img&gt;, &lt;map&gt;
   * <p>
   * This method is expected to be called by an override of
   * <code>getID</code> in renderers that render the above elements.
   * Such renderers should also override <code>renderID</code> to call
   * <code>renderNameAndID</code>.
   * @see #renderNameAndID
   * @see #getCachedIDOrName */
  protected final Object getIDOrName(
    UIXRenderingContext context,
    UINode           node
    )
  {
    Object id = node.getAttributeValue(context, ID_ATTR);

    if (id == null)
    {
      id = getTransformedName(context, node);
    }

    return id;
  }


  /**
   * Version of getIDOrName, using local property caching
   * <p>
   * @see #getIDOrName
   */
  protected final Object getCachedIDOrName(
    UIXRenderingContext context,
    UINode           node
    )
  {
    Object result = context.getLocalProperty(0, ID_ATTR, _DOES_NOT_EXIST);

    if (result == _DOES_NOT_EXIST)
    {
      // cache the attribute value for the next time
      result = node.getAttributeValue(context, ID_ATTR);

      if (result == null)
      {
        result = getTransformedName(context, node);
      }

      context.setLocalProperty(ID_ATTR, result);
    }

    return result;
  }


  /**
   * Returns the value associated with the text attribute
   */
  protected Object getText(UIXRenderingContext context,  UINode  node)
  {
    Object o = node.getAttributeValue(context, TEXT_ATTR);

    if (o == null)
      return null;

    return o.toString();
  }


  /**
   * Returns the StyleClass to use to render this node.
   */
  protected Object getStyleClass(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, STYLE_CLASS_ATTR);
  }


  /**
   * Returns the inline Style used to render this node.
   */
  protected Object getInlineStyle(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, INLINE_STYLE_ATTR);
  }


  /**
   * Returns true if the style attributes should be rendered for this node.
   * <p>
   * Clients should override this method if they need to move the rendering
   * of the style attributes to a different element, or if the the user agent
   * doesn't support style attributes.
   * <p>
   * @see #renderStyleAttrs
   */
  protected boolean doRenderStyleAttrs(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return true;
  }
  
  public static void renderStyleAndClass(
    UIXRenderingContext context,
    String           inlineStyle,
    Object           styleClass
    ) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();

    if (styleClass != null)
    {
      if (supportsClassAttribute(context))
      {
        _renderStyleClassAttributeImpl(context, styleClass);
      }
      else
      {
        // I'm assuming that the inline style doesn't happen to use
        // any of the same styles as the styleClass.  This is a
        // bad assumption which may eventually break, but it makes
        // this function much, much more efficient.
        Style inlineStyleClass = XhtmlLafUtils.getClassStyle(context,
                                                             styleClass);
        renderInlineStyleAttribute(context, inlineStyleClass);

        if (inlineStyle != null)
        {
          writer.writeAttribute("style", ";", null);
          writer.writeAttribute("style", inlineStyle, null);
        }

        // RETURN IMMEDIATELY
        return;
      }
    }

    writer.writeAttribute("style", inlineStyle, null);
  }
  /**
   * Actually renders the style attributes styleClass and inlineStyle on
   * the component, as well as the defaultStyleClass.
   * @see #doRenderStyleAttrs
   * @see #supportsStyleAttributes
   */
  protected void renderStyleAttrs(
    UIXRenderingContext context,
    UINode           node,
    String           defaultStyleClass
    ) throws IOException
  {
    // Get the style class and inline style
    Object styleClass = getStyleClass(context, node);
    if (defaultStyleClass != null)
    {
      if(styleClass!= null )
      {
        renderStyleClassAttributes(context,
        new Object[]{styleClass, defaultStyleClass});
      }
      else
      {
        renderStyleClassAttribute(context, defaultStyleClass);
      }
    }
    else if (styleClass != null)
    {
      renderStyleClassAttribute(context, styleClass);
    }

    Object inlineStyle = getInlineStyle(context, node);

    //render the inlineStyles
     renderInlineStyleAttribute(context, inlineStyle);
  }

  /**/

  /**
   * Actually renders the style attributes.
   * @see #doRenderStyleAttrs
   * @see #supportsStyleAttributes
   */
  protected void renderStyleAttrs(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    renderStyleAttrs(context, node, null);
  }


  /**
   * Renders the inline style attribute for the specified node
   */
  protected final void renderInlineStyleAttribute(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    renderInlineStyleAttribute(context, getInlineStyle(context, node));
  }

  /**
   * Renders the inline style attribute for the specified node
   */
  public static void renderInlineStyleAttribute(
    UIXRenderingContext context,
    Object            inlineStyle
    ) throws IOException
  {
    if (inlineStyle != null)
    {
      if (supportsStyleAttributes(context))
      {
        ResponseWriter writer = context.getResponseWriter();
        writer.writeAttribute("style", inlineStyle, null);
      }

      // Push the inline style onto the stack
      context.setLocalProperty(_INLINE_STYLE_KEY, inlineStyle);
    }
    else
    {
      // Push the lack of an inline style onto the stack
      context.setLocalProperty(_INLINE_STYLE_KEY, _DOES_NOT_EXIST);
    }
  }

 /**
   * Renders style class attributes, using a short style class
   * if one is available.
   * @param inlineStyleString this is appended to the end of any inline styles
   *   that are rendered. This is rendered regardless of whether any
   *   other inline style is rendered.
   */
  public static void renderStyleClassAttributes(
    UIXRenderingContext context,
    String           styleClass1,
    String           styleClass2,
    String           inlineStyleString
    ) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();
    if ( styleClass1 == null )
    {
      renderStyleClassAttribute(context, styleClass2);
      writer.writeAttribute("style", inlineStyleString, null);
      return;
    }

    if ( styleClass2 == null )
    {
      renderStyleClassAttribute(context, styleClass1);
      writer.writeAttribute("style", inlineStyleString, null);
      return;
    }

    // turn styleClass1 into an inline style at the end?
    boolean inlineStyleClass1 = true;
    // turn styleClass2 into an inline style at the end?
    boolean inlineStyleClass2 = true;


    if (supportsClassAttribute(context))
    {
      if (supportsMultipleCssSelectors(context))
      {
        inlineStyleClass1 = false;
        inlineStyleClass2 = false;

        StringBuffer classes = new StringBuffer(styleClass1.length() +
                                                styleClass2.length() +
                                                1);

        Object shortStyleClass1 = XhtmlLafUtils.getShortStyleClass(context,
                                                             styleClass1);

        Object shortStyleClass2 = XhtmlLafUtils.getShortStyleClass(context,
                                                             styleClass2);
        classes.append(shortStyleClass1);
        classes.append(' ');
        classes.append(shortStyleClass2);

        writer.writeAttribute("class", classes.toString(), null);
      }
      else
      {
        // render one style class, but render the rest as inline style below
        inlineStyleClass1 = false;
        renderStyleClassAttribute( context, styleClass1);

      }

    }

    int inlineLength = 0;

    if ( inlineStyleString != null)
      inlineLength = inlineStyleString.length();

    if ( inlineStyleClass1 )
      inlineLength = inlineLength + styleClass1.length();

    if ( inlineStyleClass2)
      inlineLength = inlineLength + styleClass2.length();

    StringBuffer inline = new StringBuffer(inlineLength);

    if (  inlineStyleClass1 )
      inline.append( XhtmlLafUtils.getClassStyle(context, styleClass1).toInlineString()).append(';');

    if (  inlineStyleClass2 )
      inline.append( XhtmlLafUtils.getClassStyle(context, styleClass2).toInlineString()).append(';');

    if ( inlineStyleString != null )
      inline.append(inlineStyleString);

    if ( inline.length() > 0 )
      writer.writeAttribute("style", inline.toString(), null);
  }


  /**
   * Renders style class attributes, using a short style class
   * if available.
   *
   * If multiple selectors not supported, renders as inline style
   */
  public static void renderStyleClassAttributes(
    UIXRenderingContext context,
    Object[]         styleClasses
    ) throws IOException
  {
    renderStyleClassAttributes( context, styleClasses, null);
  }

  /**
   * Renders style class attributes, using a short style class
   * if one is available.
   * @param renderInlineStyles if style classes aren't supported or
   *   multiple style selectors aren't supported,
   *   tells whether or not to render inline versions of styles
   * @param inlineStyleString this is appended to the end of any inline styles
   *   that are rendered. This is rendered regardless of whether any
   *   other inline style is rendered.
   */
  public static void renderStyleClassAttributes(
    UIXRenderingContext context,
    Object[]         styleClasses,
    String           inlineStyleString
    ) throws IOException
  {
    int j = 0;

    if (supportsClassAttribute(context))
    {
      if (supportsMultipleCssSelectors(context))
      {
        j = styleClasses.length;
        StringBuffer classes = new StringBuffer(20);

        for ( int i = 0; i < styleClasses.length; i++ )
        {
          Object shortStyleClass = XhtmlLafUtils.getShortStyleClass(context,
                                                               styleClasses[i]);

          Object styleClass = null;

          if (shortStyleClass != null)
            styleClass = shortStyleClass;

          if ( styleClass != null )
          {
            if ( i == 0 )
            {
              classes.append(styleClass);
            }
            else
            {

                classes.append(' ');
                classes.append(styleClass);
            }
          }
        }

        ResponseWriter writer = context.getResponseWriter();
        writer.writeAttribute("class", classes.toString(), null);
      }
      else
      {
        // render one style class, but render the rest as inline style below
        renderStyleClassAttribute( context, styleClasses[0]);
        j = 1;
      }

    }

    StringBuffer inline = new StringBuffer();

    for ( ; j < styleClasses.length; j++ )
    {
      Object styleClass = styleClasses[j];
      Style inlineStyle = XhtmlLafUtils.getClassStyle(context, styleClass);

      if (inlineStyle != null )
        inline.append(inlineStyle.toInlineString());
    }

    if ( inlineStyleString != null )
      inline.append( inlineStyleString);

    ResponseWriter writer = context.getResponseWriter();

    if ( inline.length() > 0 )
      writer.writeAttribute("style", inline.toString(), null);

  }


  /**
   * Renders the style class attribute, using a short style class
   * if one is available.
   */
  public static void renderStyleClassAttribute(
    UIXRenderingContext context,
    Object           styleClass
    ) throws IOException
  {
    if (supportsClassAttribute(context))
    {
      Object shortStyleClass = XhtmlLafUtils.getShortStyleClass(context,
                                                                styleClass);
      if (shortStyleClass != null)
        styleClass = shortStyleClass;

      ResponseWriter writer = context.getResponseWriter();
      writer.writeAttribute("class", styleClass, null);
    }
    else
    {
      renderInlineStyleAttribute(context,
                                 XhtmlLafUtils.getClassStyle(context,
                                                             styleClass));
    }
  }

  /**
   * Renders the style class attribute, using a short style class
   * if one is available.
   */
  private static void _renderStyleClassAttributeImpl(
    UIXRenderingContext context,
    Object           styleClass
    ) throws IOException
  {
    Object shortStyleClass = XhtmlLafUtils.getShortStyleClass(context,
                                                              styleClass);
    if (shortStyleClass != null)
      styleClass = shortStyleClass;

    ResponseWriter writer = context.getResponseWriter();
    writer.writeAttribute("class", styleClass, null);
  }

  /**
   * Renders a pre-shortened style class attribute.  This method
   * may only be called with a "shortStyleClass" that has already
   * been passed through getShortStyleClass.
   * @see XhtmlLafUtils#getShortStyleClass
   */
  public static void renderShortStyleClassAttribute(
    UIXRenderingContext context,
    Object           shortStyleClass
    ) throws IOException
  {
    /*
    // This method may only be called with an already abbreviated
    // style...
    if (Assert.DEBUG)
      Assert.assertion(shortStyleClass == getShortStyleClass(context, shortStyleClass);
     */
    if (supportsClassAttribute(context))
    {
      ResponseWriter writer = context.getResponseWriter();
      writer.writeAttribute("class", shortStyleClass, null);
    }
    else
    {
      renderInlineStyleAttribute(context,
                                 XhtmlLafUtils.getClassStyle(context,
                                                             shortStyleClass));
    }
  }

  /**
   * Method for rendering one pixel lines.
   * @param context the <code>RenderingContext</code>
   * @throws IOException
   */
  public static void renderOnePixelLine(UIXRenderingContext context) throws IOException {
      final String style = "p_OraOnePixelLine";
      ResponseWriter writer = context.getResponseWriter();
      writer.startElement("div",null);
      renderStyleClassAttribute(context,style);
      writer.endElement("div");
  }


  /**
   * Renders the "defer" attribute for a script element.
   * In order to support partial page rendering, scripts must
   * be rendered using the "defer" attribute to ensure that
   * they are executed in the appropriate context.  However,
   * some browsers (eg. IE) do not reliably load scripts
   * which are deferred.  This method detects whether the
   * target agent can handle the defer attribute correctly and
   * only renders this attribute when as appropriate.
   * <p>
   * Note: ResponseWriter.startElement("script", null) must be called
   * before calling this method.
   */
  public static void renderScriptDeferAttribute(UIXRenderingContext context)
    throws IOException
  {
    // At the moment we only render the defer attribute if
    // we are in the partial rendering pass.  This is to
    // avoid the "object expected" JavaScript errors that we
    // see (bug 2251656).  We need to do further browser
    // testing to determine whether we might enable deferred
    // scripts in more cases.

    // Note that we only want to defer scripts once we are actually
    // inside the body content - and writing content to the
    // ScriptBufferingResponseWriter.  Otherwise, we run into bug
    // 2466017.
    if (getRenderingProperty(context, __DEFER_SCRIPTS_KEY) != null)
      context.getResponseWriter().writeAttribute("defer", Boolean.TRUE, null);
  }


  /**
   * Checks whether in screen reader mode, and if so, renders "type" attribute
   * for a script element.
   * <p>
   *
   * Note: ResponseWriter.startElement("script", null) must be called
   * before calling this method.
   *
   * [ =-= mll added 20-Apr-04 to address bug 3426092 ]
   *
   */
  public static void renderScriptTypeAttribute(UIXRenderingContext context)
    throws IOException
  {
    if (isScreenReaderMode(context))
    {
      context.getResponseWriter().writeAttribute("type", _ACCESSIBILITY_SCRIPT_TYPE, null);
    }
  }


  /**
   * Renders the combination of inline and class style attributes
   * as elements
   */
  protected final void startRenderingStyleElements(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    startRenderingStyleElements(context,
                                null/*=-=AEW getInlineStyle(context, node)*/,
                                getStyleClass(context, node));
  }


  /**
   * Renders the combination of inline and class style attributes
   * as elements
   */
  protected final void startRenderingStyleElements(
    UIXRenderingContext context,
    Style            inlineStyle,
    Object           className
    ) throws IOException
  {
    XhtmlLafUtils.startRenderingStyleElements(
                                context,
                                inlineStyle,
                                XhtmlLafUtils.getClassStyle(context, className));
  }


  /**
   * Renders event handlers for the node.
   */
  protected void renderEventHandlers(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    // don't write onclick and ondblclick handlers for disabled nodes
    if (!isDisabled(context, node))
    {
      renderAttribute(context, "onclick",    getOnClick(context, node));
      renderAttribute(context, "ondblclick", getOnDoubleClick(context, node));
    }

    renderAttribute(context, "onmousedown", getOnMouseDown(context, node));
    renderAttribute(context, "onmouseup",   getOnMouseUp(context, node));
    renderAttribute(context, "onmouseover", getOnMouseOver(context, node));
    renderAttribute(context, "onmousemove", getOnMouseMove(context, node));
    renderAttribute(context, "onmouseout",  getOnMouseOut(context, node));

    renderAttribute(context, "onkeypress", getOnKeyPress(context, node));
    renderAttribute(context, "onkeydown",  getOnKeyDown(context, node));
    renderAttribute(context, "onkeyup",    getOnKeyUp(context, node));
  }


  protected Object getOnClick(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_CLICK_ATTR);
  }

  protected Object getOnDoubleClick(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_DOUBLE_CLICK_ATTR);
  }

  protected Object getOnKeyDown(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_KEY_DOWN_ATTR);
  }

  protected Object getOnKeyPress(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_KEY_PRESS_ATTR);
  }

  protected Object getOnKeyUp(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_KEY_UP_ATTR);
  }

  protected Object getOnMouseDown(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_MOUSE_DOWN_ATTR);
  }

  protected Object getOnMouseUp(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_MOUSE_UP_ATTR);
  }

  protected Object getOnMouseOver(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_MOUSE_OVER_ATTR);
  }

  protected Object getOnMouseMove(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_MOUSE_MOVE_ATTR);
  }

  protected Object getOnMouseOut(
    UIXRenderingContext context,
    UINode           node
    )
  {
    return node.getAttributeValue(context, ON_MOUSE_OUT_ATTR);
  }

  protected void renderSpacer(
    UIXRenderingContext      context,
    Object                width,
    Object                height,
    Object                id
    ) throws IOException
  {
    String widthString = _toString(width);
    String heightString = _toString(height);

    renderTransparent(context, widthString, heightString, true, id);
  }


  protected void renderSpacer(
    UIXRenderingContext      context,
    Object                width,
    Object                height
    ) throws IOException
  {
    renderSpacer(context, width, height, null);
  }

  protected void renderSpacer(
    UIXRenderingContext context,
    Integer          width,
    Integer          height
    ) throws IOException
  {
    int widthInt = (width != null)
                     ? width.intValue()
                     : -1;

    int heightInt = (height != null)
                     ? height.intValue()
                     : -1;

    renderSpacer(context, widthInt, heightInt);
  }


  protected void renderSpacer(
    UIXRenderingContext context,
    int              width,
    int              height
    ) throws IOException
  {
    // replace with more compact javascript representation
    String widthString = (width >= 0)
                           ? IntegerUtils.getString(width)
                           : null;

    String heightString = (height >= 0)
                           ? IntegerUtils.getString(height)
                           : null;

    renderTransparent(context, widthString, heightString, false);
  }

  /**
   *
   * @param context
   * @param width
   * @throws IOException
   */
  protected void renderHorizontalSpacer(UIXRenderingContext context, String width)
      throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();
    writer.startElement("b", null);
    writer.writeAttribute("style", "margin-left:" + width + "px", null);
    writer.endElement("b");
  }

  protected void renderVerticalSpacer(UIXRenderingContext context, Object height)
      throws IOException
  {
    if (height != null)
    {
      ResponseWriter writer = context.getResponseWriter();
      writer.startElement("div", null);
      writer.writeAttribute("style", "margin-top:" + height + "px", null);
      writer.endElement("div");
    }
  }

  /**
   * Renders a transparent gif using a script to save space.
   */
  protected void renderTransparent(
    UIXRenderingContext context,
    String           width,
    String           height,
    boolean          needsQuoting
    ) throws IOException
  {
    renderTransparent(context, width, height, needsQuoting, null);
  }

  /**
   * Renders a transparent gif using a script to save space.
   */
  protected void renderTransparent(
    UIXRenderingContext context,
    String           width,
    String           height,
    boolean          needsQuoting,
    Object           id
    ) throws IOException
  {
    Counter counter =
      (Counter) getRenderingProperty(context, _SCRIPT_SPACER_COUNT);
    if (counter == null)
    {
      counter = new Counter();
      setRenderingProperty(context, _SCRIPT_SPACER_COUNT, counter);
    }
    int count = counter.count++;

    // do not use the script spacer, if we have already rendered an enormous
    // number of spacers. bug 3786394:
    boolean useScript =
      ((count < 800)
       && (TrinidadAgent.SCRIPTING_SPEED_CAP_FAST ==
           getAgentCapability(context, TrinidadAgent.CAP_SCRIPTING_SPEED))
       && (null ==
           getAgentCapability(context, TrinidadAgent.CAP_IS_JDEV_VE)));
    _renderTransparent(context, width, height, needsQuoting, id, useScript);
  }

  /**
   * @param useScript use javascript to render the spacer. if this is false,
   * then an html IMG tag will be used.
   */
  private void _renderTransparent(
    UIXRenderingContext context,
    String           width,
    String           height,
    boolean          needsQuoting,
    Object           id,
    boolean          useScript
    ) throws IOException
  {
    PartialPageContext pContext = context.getPartialPageContext();

    // cannot use t() in MarlinCore.js on a partial rendering pass
    // just render the icon.
    if (!useScript || (pContext != null))
    {
      renderIcon(context, TRANSPARENT_GIF, width, height, id);
    }
    else
    {
      // IE has fast javascript, so render has a js function call
      ResponseWriter writer = context.getResponseWriter();

      Boolean isTransparentURLSet =
        (Boolean) getRenderingProperty(context,
                                      __TRANSPARENT_URL_KEY);

      if (isTransparentURLSet == null)
      {
        setRenderingProperty(context, __TRANSPARENT_URL_KEY, Boolean.TRUE);

        // determine the transparent image's URL
        String transparentURL = getBaseImageURI(context) + TRANSPARENT_GIF;

        // make sure the transparent image function is loaded
        XhtmlLafUtils.addLib(context, "t()");

        writer.startElement("script", null);
        // Bug #3426092:
        // render the type="text/javascript" attribute in accessibility mode
        XhtmlLafRenderer.renderScriptTypeAttribute(context);

        if (id != null)
          renderID(context, id, false);

        // store transparentURL as javascript variable
        // which is used in t()
        writer.write("var _tURL=\"" + transparentURL + "\";");

        // store accessibility mode as javascript variable
        // which is used in t()
        writer.write("var _axm");
        if (!isInaccessibleMode(context))
          writer.write("=1");
        writer.write(";");
      }
      else
      {
        writer.startElement("script", null);
        // Bug #3426092:
        // render the type="text/javascript" attribute in accessibility mode
        XhtmlLafRenderer.renderScriptTypeAttribute(context);
        if (id != null)
          renderID(context, id, false);
      }

      // write a reference to the transparent gif function
      writer.write("t(");

      if ((width != null) || (height != null))
      {
        String widthParam = "void 0";

        if (width != null)
        {
          widthParam = width;

          if (needsQuoting)
          {
            writer.write("'");
          }
        }

        writer.write(widthParam);

        if (needsQuoting && (width != null))
        {
          writer.write("'");
        }

        if (height != null)
        {
          writer.write(",");

          if (needsQuoting)
          {
            writer.write("'");
          }

          writer.write(height);

          if (needsQuoting)
          {
            writer.write("'");
          }
        }
      }

      writer.write(")");

      writer.endElement("script");
    }
  }



  /**
   * Renders an icon.  If the the specified iconURL is a relative URI,
   * it is appended to the value of the base Image URI from
   * the Configuration to form an absolute path.
   */
  protected void renderIcon(
    UIXRenderingContext context,
    Object           iconURL,
    Object           width,
    Object           height
    ) throws IOException
  {
    renderIcon(context, iconURL, width, height, null);
  }

  /**
   * Renders an icon.  If the the specified iconURL is a relative URI,
   * it is appended to the value of the base Image URI from
   * the Configuration to form an absolute path.
   */
  protected void renderIcon(
    UIXRenderingContext context,
    Object           iconURL,
    Object           width,
    Object           height,
    Object           id
    ) throws IOException
  {
    renderIcon(context, iconURL, width, height, id, null /*altText*/);
  }

  /**
   * Renders an icon.  If the the specified iconURL is a relative URI,
   * it is appended to the value of the base Image URI from
   * the Configuration to form an absolute path.
   */
  protected final void renderIcon(
    UIXRenderingContext context,
    Object           iconURL,
    Object           width,
    Object           height,
    Object           id,
    Object           altText
    ) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();

    writer.startElement("img", null);
    if (id != null)
      renderID(context, id, false);

    // Convert iconURL to an absolute uri
    writeAbsoluteImageURI(context, "src", iconURL.toString());

    if (altText != null)
      writer.writeAttribute("alt", altText, null);
    else if (!isInaccessibleMode(context))
      writer.writeAttribute("alt", "", null);

    if (width != null)
    {
      writer.writeAttribute("width", width, null);
    }

    if (height != null)
    {
      writer.writeAttribute("height", height, null);
    }

    writer.endElement("img");
  }


  protected void renderIcon(
    UIXRenderingContext context,
    Object           iconURL,
    int              width,
    int              height
    ) throws IOException
  {
    renderIcon(context,
               iconURL,
               (width  != -1) ?  getInteger(width) : null,
               (height != -1) ?  getInteger(height) : null);
  }


  /**
   * iconURI must be an absolute URI
 * @param isBlock wether the icon should be rendered in block mode
   */
  protected void renderIcon(
    UIXRenderingContext context,
    String           iconAbsoluteURI,
    String           altTextKey,
    Object           destination,
    Object           anchor,
    Object           onClick,
    Object           imgAlign,
    Object           targetFrame,
    boolean isBlock
    ) throws IOException
  {
    if (iconAbsoluteURI != null)
    {
      ResponseWriter writer = context.getResponseWriter();

      if ((destination != null) ||
          (onClick != null)     ||
          (anchor != null))
      {
        if (supportsNavigation(context))
        {
          writer.startElement("a", null);
          renderEncodedActionURI(context, HREF_ATTRIBUTE, destination);
          writer.writeAttribute("onclick", onClick, null);
          writer.writeAttribute("target", targetFrame, null);

          if (anchor != null)
          {
            writer.writeAttribute("name", anchor, null);
          }
        }
      }

      writer.startElement("img", null);

      Object altText = null;
      if (altTextKey != null)
        altText = getTranslatedValue(context, altTextKey);

      // Ensure that we're never rendering null;  see bug 4161181
      // why this logic is not in renderAltAndTooltipForImage().
      // This is, in essence, re-introducing a more restricted version
      // of that bug, but avoids needing to chase down
      // all the calls to XhtmlLafRenderer.renderIcon() at this late date.
      renderAltAndTooltipForImage(context, altText == null ? "" : altText);

      // get the correct alignment to use for the agent
      writer.writeAttribute("align", imgAlign, null);

      renderEncodedResourceURI(context, "src", iconAbsoluteURI);
      writer.writeAttribute("border", "0", null);
      if (isBlock)
      {
        XhtmlLafRenderer
            .renderStyleClassAttribute(context, "p_OraDisplayBlock");
      }
      writer.endElement("img");

      if ((destination != null) || (onClick != null) || (anchor != null))
      {
        if (supportsNavigation(context))
          writer.endElement("a");
      }
    }
  }

  protected void renderIcon(UIXRenderingContext context, Object iconURL,
      Object width, Object height, boolean isBlock) throws IOException
  {
    String fulluri = getBaseImageURI(context) + iconURL.toString();
    renderIcon(context,fulluri,null,null,null,null,null,null,isBlock);
  }



  protected void renderStretchedImage(
    UIXRenderingContext context,
    String           imageURL,
    int              height
    ) throws IOException
  {
    renderIcon(context, imageURL, "100%", getInteger(height));
  }


  public static void writeAbsoluteImageURI(
    UIXRenderingContext context,
    String           attribute,
    String           uri) throws IOException
  {
    if ((uri == null) || (uri.length() == 0))
      return;

    ResponseWriter writer = context.getResponseWriter();

    String encodedUri = getBaseImageURI(context) + uri;
    FacesContext facesContext = context.getFacesContext();
    if (facesContext != null)
      encodedUri = facesContext.getExternalContext().encodeResourceURL(encodedUri);
    writer.writeURIAttribute(attribute, encodedUri, null);
  }


  protected static void renderHAlign(
    UIXRenderingContext context,
    Object           hAlign) throws IOException
  {
    if (hAlign != null)
    {
      boolean rtl = isRightToLeft(context);

      if ("start".equals(hAlign))
      {
        hAlign = (rtl)
                  ? "right"
                  : "left";
      }
      else if ("end".equals(hAlign))
      {
        hAlign = (rtl)
                  ? "left"
                  : "right";
      }

      context.getResponseWriter().writeAttribute("align", hAlign, null);
    }
  }


  protected void renderHAlign(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    renderHAlign(context, node.getAttributeValue(context, H_ALIGN_ATTR));
  }


  /**
   * Renders the text with the access key highlighted as appropriate.
   */
  protected void renderAccessKeyText(
    UIXRenderingContext context,
    Object           textValue,
    int              keyIndex,
    String           accessKeyClass
    ) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();

    if (textValue != null)
    {
      // create the String containing the access key, if any.
      // we don't render access key indicators on Netscape because
      // Netscape doesn't support accesskeys
      if ((keyIndex != -1) && supportsAccessKeys(context))
      {
        String textString = textValue.toString();

        char[] textChars = textString.toCharArray();

        // write text before the mnemonic
        writer.writeText(textChars, 0, keyIndex);

        if (accessKeyClass != null && accessKeyClass.length() > 0){
        
          writer.startElement ("span", null);
          XhtmlRenderer.renderStyleClass (context.getFacesContext(), 
                                          RenderingContext.getCurrentInstance(),  
                                          accessKeyClass);                                          
          writer.writeText (textChars, keyIndex, 1);
          writer.endElement ("span");
          
          // write text after the mnemonic
          keyIndex++;
        }

        int charsLeft = textChars.length - keyIndex;

        if (charsLeft > 0)
        {
          writer.writeText(textChars, keyIndex, charsLeft);
        }
      }
      else
      {
        // output the text directly since we have no access key
        writer.writeText(textValue, null);
      }
    }
  }

  /**
   * Renders the text with the access key highlighted as appropriate.
   */
  protected void renderAccessKeyText(
    UIXRenderingContext context,
    UINode           node,
    Object           textValue,
    String           accesskeyClass
    ) throws IOException
  {
    Object textString = (textValue != null)
                          ? textValue.toString()
                          : null;

    renderAccessKeyText(context,
                        textString,
                        getAccessKeyIndex(context, node, textString),
                        accesskeyClass);
  }

  /**
   * Extension of XhtmlLafRenderer.getResolvedSelectedIndex().
   * Specialized renderers who need frequent access to the selected index are
   * expected to call this method. We cache the selected index as local context
   * property on first call, and retrieve from cache on subsequent calls.
   */
  protected int getResolvedSelectedIndexFromCache(
    UIXRenderingContext context,
    UINode node
    )
  {
    int resolvedSelectedIndexFromCache = 0;
    Integer cachedIndex = (Integer)context.getLocalProperty(0,
      _SELECTED_CHILD_INDEX_KEY, null);
    if (cachedIndex != null)
    {
      resolvedSelectedIndexFromCache = cachedIndex.intValue();
    }
    else
    {
      resolvedSelectedIndexFromCache = getResolvedSelectedIndex(context, node);
      context.setLocalProperty(_SELECTED_CHILD_INDEX_KEY,
        resolvedSelectedIndexFromCache);
    }
    return resolvedSelectedIndexFromCache;
  }

  /**
   *
   * determine the selected child index
   *
   */

  public int getResolvedSelectedIndex(
    UIXRenderingContext context,
    UINode node )
  {
    //
    // First check all of the children for the selected index.
    //
    int selectedIndex  = NO_CHILD_INDEX;
    int currChildIndex = NO_CHILD_INDEX;
    do
    {
      currChildIndex = getNextRenderedChildIndex(context,
                                                 node,
                                                 currChildIndex);

      if (currChildIndex != NO_CHILD_INDEX)
      {
        UINode currChildNode = node.getIndexedChild(context, currChildIndex);

        if (Boolean.TRUE.equals(
                   currChildNode.getAttributeValue(context,
                                                   UIConstants.SELECTED_ATTR)))
        {
          selectedIndex = currChildIndex;
          break;
        }
      }
      else
      {
        break;
      }
    } while (true);


    // try getting the selected index from the parent
    if (selectedIndex == NO_CHILD_INDEX)
    {
      Integer selected = (Integer)node.getAttributeValue(
                                               context,
                                               UIConstants.SELECTED_INDEX_ATTR);

      if (selected != null)
      {
        selectedIndex = selected.intValue();
      }
    }

    return selectedIndex;
  }



  /**
   * Returns the index of the access key in the specified node's text.
   */
  protected static int getAccessKeyIndex(
    UIXRenderingContext context,
    UINode           node,
    Object           textValue
    )
  {
    int keyIndex = -1;

    if (textValue != null)
    {
      char accessChar = XhtmlLafUtils.getCharacterAttr(context,
                                                       node,
                                                       ACCESS_KEY_ATTR);
      if (accessChar != XhtmlLafUtils.CHAR_UNDEFINED)
      {
        String textString = textValue.toString();

        // underline the first instance of the access key in the text
        keyIndex = textString.indexOf(accessChar);

        // try the key in the opposite case if there was no match
        if (keyIndex == -1)
        {
          char oppositeChar = Character.toLowerCase(accessChar);

          if (oppositeChar == accessChar)
          {
            oppositeChar = Character.toUpperCase(accessChar);
          }

          if (oppositeChar != accessChar)
          {
            keyIndex = textString.indexOf(oppositeChar);
          }
        }
      }
    }
    return keyIndex;
  }


  protected static void renderLayoutTableAttributes(
    UIXRenderingContext context,
    Object           cellspacing,
    Object           tableWidth
    ) throws IOException
  {
    renderLayoutTableAttributes(context, "0", cellspacing, tableWidth);
  }


  protected static void renderLayoutTableAttributes(
    UIXRenderingContext context,
    Object           cellpadding,
    Object           cellspacing,
    Object           tableWidth
    ) throws IOException
  {
    renderLayoutTableAttributes(context, cellpadding, cellspacing, "0",
                                tableWidth);
  }

  /**
   * All layout tables should call this method, so that a special summary tag
   * (which silences OAC) is rendered
   */
  protected static void renderLayoutTableAttributes(
    UIXRenderingContext context,
    Object           cellpadding,
    Object           cellspacing,
    Object           border,
    Object           tableWidth
    ) throws IOException
  {
    renderLayoutTableAttributes(context, cellpadding, cellspacing, border,
                                tableWidth, "" /* summary */ );
  }

  /**
   * all data tables should call this one, so that a summary tag is written
   * out
   */
  protected static void renderLayoutTableAttributes(
    UIXRenderingContext context,
    Object           cellpadding,
    Object           cellspacing,
    Object           border,
    Object           tableWidth,
    Object           summary
    ) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();
    writer.writeAttribute("cellpadding", cellpadding, null);
    writer.writeAttribute("cellspacing", cellspacing, null);
    writer.writeAttribute("border", border, null);
    writer.writeAttribute("width", tableWidth, null);

    if (!isInaccessibleMode(context))
    {
      writer.writeAttribute("summary", summary, null);
    }
  }

  protected static void renderLayoutTableHeader(
    UIXRenderingContext context,
    Object           cellspacing,
    Object           tableWidth
    ) throws IOException
  {
    context.getResponseWriter().startElement("table", null);
    renderLayoutTableAttributes(context, cellspacing, tableWidth);
  }

  /**
  * Renders only the alt attribute
  * if that can be used as a tooltip on an image.
  * Otherwise it renders both the alt and the title attributes.
  */
  public static void renderAltAndTooltipForImage(
     UIXRenderingContext context,
     Object           textValue
     ) throws IOException
  {
    if (textValue == null)
      return;

    ResponseWriter writer = context.getResponseWriter();
    boolean wroteTitle = false;

    if (!supportsAltRendersTooltipOnImage(context))
    {
      if (!"".equals(textValue))
      {
        writer.writeAttribute("title", textValue, null);
        wroteTitle = true;
      }
    }

    // only write out both title and alt if
    // we really need both
    if (!wroteTitle || !isInaccessibleMode(context))
    {
      writer.writeAttribute(ALT_ATTRIBUTE, textValue, null);
    }
  }

  protected boolean isTextFormatted(Object textValue)
  {
    if (textValue == null)
      return false;

    // =-=AEW Ugh.  Could we avoid "toString()"'ing.  OTOH,
    // this shouldn't be a big performance issue, since
    // if it's
    String string = textValue.toString();
    // =-=AEW Do we support "<HTML>" (caps)?
    return string.startsWith("<html>");
  }

  final protected void renderPossiblyFormattedText(
    UIXRenderingContext context,
    Object           textValue
    ) throws IOException
  {
    if (textValue != null)
    {
      if (isTextFormatted(textValue))
        renderFormattedText(context, textValue);
      else
        context.getResponseWriter().writeText(textValue, null);
    }
  }

  final protected void renderFormattedText(
    UIXRenderingContext context,
    Object           textValue) throws IOException
  {
    if (textValue != null)
    {
      String text = textValue.toString();
      getFormattedTextParser(context).writeFormattedText(
        context.getFacesContext(), text);
    }
  }

  /**
   * Starts a block wrapper to enable screen readers identify skippable
   * related link groups. Also renders the 'hidden' skip link as the first
   * element in the group.
   * @param blockTitleKey The resource key for this blocks title
   */
  protected final void renderRelatedLinksBlockStart(
    UIXRenderingContext context,
    String blockTitleKey
    ) throws IOException
  {
    if (isScreenReaderMode(context))
    {
      ResponseWriter writer = context.getResponseWriter();
      writer.startElement("map", null);
      String blockTitleText = getTranslatedString(context, blockTitleKey);
      writer.writeAttribute("title", blockTitleText, null);
      String navBlockEndAnchorName = XhtmlLafUtils.generateUniqueID(context);
      context.setLocalProperty(_NAV_BLOCK_END_ANCHOR_NAME_KEY,
                                navBlockEndAnchorName);
      String pattern = getTranslatedString(context,
                                           "SKIP_CURRENT_NAVIGATION_BLOCK");
      String skipLinkAltText = formatString(context,
                                            pattern,
                                            new String[]{blockTitleText});
      _renderSkipNavigationLink(context,
                                navBlockEndAnchorName,
                                skipLinkAltText);
    }
  }

  /**
   * Ends a block wrapper to enable screen readers identify skippable
   * related link groups. Also renders the anchor to where the jump should
   * happen.
   */
  protected final void renderRelatedLinksBlockEnd(
    UIXRenderingContext context
    ) throws IOException
  {
    if (isScreenReaderMode(context))
    {
      ResponseWriter writer = context.getResponseWriter();
      writer.endElement("map");
      String navBlockEndAnchorName = (String)context.getLocalProperty(0,
                                      _NAV_BLOCK_END_ANCHOR_NAME_KEY, null);
      MarlinBean navBlockEndAnchor = new MarlinBean(UIConstants.LINK_NAME);
      navBlockEndAnchor.setAttributeValue(UIConstants.NAME_ATTR, navBlockEndAnchorName);
      navBlockEndAnchor.render(context);
    }
  }

  protected FormattedTextParser getFormattedTextParser(
    UIXRenderingContext context)
  {
    return XhtmlFormattedText.getFormattedTextParser();
  }


  /**
   * Renders the node text using the relevant style information.
   */
  final protected void renderStyledText(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    renderStyledText(context, node, false, true);
  }

  /**
   * Renders the node text using the relevant style information.
   */
  protected void renderStyledText(
    UIXRenderingContext context,
    UINode           node,
    boolean          renderAccessKeys,
    boolean          renderID
    ) throws IOException
  {
    Object textValue = getText(context, node);

    if (textValue != null)
    {
      ResponseWriter writer = context.getResponseWriter();

      //
      // Determine the style class to use, and default the style depending
      // depending on whether we are disabled, or not.
      //
      Object styleClass = getStyleClass(context, node);

      // get the inline style to use
      Object inlineStyle = getInlineStyle(context, node);


      boolean hasElementStyle   = false;

      UIComponent component = NodeUtils.getUIComponent(context, node);

      writer.startElement(SPAN_ELEMENT, component);
      if (renderID)
        renderID( context, node);

      // write the style information
      if ((styleClass != null) || (inlineStyle != null))
      {
        if (supportsStyleAttributes(context))
        {
          if (styleClass != null)
          {
            renderStyleClassAttribute(context, styleClass);
          }

          if (inlineStyle != null)
          {
            renderInlineStyleAttribute(context, inlineStyle);
          }
        }
        else if (supportsTextPresentation(context))
        {
          hasElementStyle = true;

          startRenderingStyleElements(context, null/*=-=AEWinlineStyle*/, styleClass);
        }
      }

      if (renderAccessKeys && supportsAccessKeys(context))
      {
        // hightlight any access keys with an underline
        renderAccessKeyText(context, node, textValue, 
                            SkinSelectors.AF_ACCESSKEY_STYLE_CLASS);
      }
      else
      {
        // output the text directly since we have no access key
        writer.writeText(textValue, null);
      }

      // close any element styles
      if (hasElementStyle)
      {
        XhtmlLafUtils.endRenderingStyleElements(context);
      }

      writer.endElement(SPAN_ELEMENT);
    }
  }

  @SuppressWarnings("unchecked")
  protected void addOnSubmitConverterValidators(
    UIXRenderingContext context,
    UINode           node,
    String           requiredMessageKey
  )throws IOException
  {

   // 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))
    {
      boolean requiredField = Boolean.TRUE.equals(
                           node.getAttributeValue(context, REQUIRED_ATTR));
      Converter converter = (Converter)node.getAttributeValue(context,
                                                  UIConstants.CONVERTER_ATTR);
      Iterator<Validator> validators = 
        (Iterator<Validator>) node.getAttributeValue(context,
                                                     UIConstants.VALIDATORS_ATTR);
      if (requiredField ||
          (converter != null) ||
          ((validators != null) && validators.hasNext()))
      {
        Object nodeName = getNodeName(context, node);
        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,
                                                    converter,
                                                    validators,
                                                    nodeName.toString(),
                                                    unvalidated,
                                                    requiredField,
                                                    requiredMessageKey);

      }
    }
  }

  protected void addOnSubmitRequiredValidator(
    UIXRenderingContext context,
    UINode           node,
    String           requiredMessageKey
  )throws IOException
  {

    Object nodeName = getNodeName(context, node);
    XhtmlLafUtils.addOnSubmitRequiredValidator(context, node,
                                               requiredMessageKey, nodeName);

  }


  // Don't "toString()" Integers - use our cache
  static private String _toString(Object o)
  {
    if (o == null)
      return null;
    if (o.getClass() == Integer.class)
      return IntegerUtils.getString(((Integer) o).intValue());
    return o.toString();
  }

  /**
   * Returns an image from the ImageProvider
   */
  public static String getFlippedIconURI(
    UIXRenderingContext context,
    String           source
    )
  {
    ImageProviderResponse response = getFlippedIcon(context, source);

    if (response != null)
    {
      return LafIconProvider.getCacheImageURI(context) + response.getImageURI();
    }
    else
    {
      return null;
    }
  }

 /**
  * Get the URI of an image that might need to be flipped
  */
  protected Object getFlippableURI(
    UIXRenderingContext context,
    UINode           node,
    AttributeKey    attrKey
    )
  {
    // Get the original URI
    Object uri = node.getAttributeValue(context, attrKey);

    // if going right-to-left and autoflip turned on
    if ( isRightToLeft(context) &&
         Boolean.TRUE.equals(node.getAttributeValue( context, AUTOFLIP_ATTR)))
    {
      if ( uri != null )
      {
        // get the uri of the flipped image
        Object flippeduri = getFlippedIconURI(context, uri.toString() );

        // if uri of flipped image is null that means original
        // image couldn't be found to be flipped, so just
        // return original uri
        if ( flippeduri != null )
          uri = flippeduri;
      }
    }

    return uri;
  }

  /**
   * Returns an image from the ImageProvider
   */
  public static ImageProviderResponse getFlippedIcon(
    UIXRenderingContext context,
    String           sourceURI
    )
  {
    ImageProvider provider = (ImageProvider)
      context.getProperty(ImageConstants.TECATE_NAMESPACE,
                          ImageConstants.IMAGE_PROVIDER_PROPERTY);

    if (provider == null)
    {
      return null;
    }

    // Get the context and request objects
    ImageContext imageContext = context.getImageContext();
    ImageProviderRequest request = new FlippedIconRequest(
                                  context,
                                  sourceURI);

    // Make the request
    ImageProviderResponse response = provider.getImage(imageContext, request);

    // Log any problems
    if (response == null)
    {
      if (_LOG.isWarning())
        _LOG.warning("CANNOT_FLIP_ICON", sourceURI);
    }

    return response;
  }

  // Returns the style class, first looking for a local style class
  // property.
  private Object _getLocalStyleClass(
    UIXRenderingContext context,
    UINode           node
    )
  {
    Object prop = context.getLocalProperty(0, _STYLE_CLASS_KEY, null);

    // If the local property was explicitly set to _DOES_NOT_EXIST,
    // that means that the STYLE_CLASS_ATTR is not set on the UINode.
    if (prop == _DOES_NOT_EXIST)
      return null;

    return (prop == null) ? getStyleClass(context, node) : prop;
  }

  // Returns the inline style, first looking for a local inline style
  // property.
  /* =-=AEW UNUSED
  private Object _getLocalInlineStyle(
    RenderingContext context,
    UINode           node
    )
  {
    Object prop = context.getLocalProperty(0, _INLINE_STYLE_KEY, null);

    // If the local property was explicitly set to _DOES_NOT_EXIST,
    // that means that the INLINE_STYLE_ATTR is not set on the UINode.
    if (prop == _DOES_NOT_EXIST)
      return null;

    return (prop == null) ? getInlineStyle(context, node) : prop;
  }
  */

  /**
   * Renders a link which could be used to skip to an anchor ahead in the page.
   * @param skipNavigationAnchor The destination anchor name
   * @param altText Translated alt text value (ScreenReaders' speak this out)
   */
  private static void _renderSkipNavigationLink(
    UIXRenderingContext context,
    String skipNavigationAnchor,
    String altText
    ) throws IOException
  {
    MarlinBean skipLink = new MarlinBean(UIConstants.IMAGE_NAME);
    skipLink.setAttributeValue(UIConstants.DESTINATION_ATTR, "#"+skipNavigationAnchor);
    skipLink.setAttributeValue(UIConstants.SHORT_DESC_ATTR,
                               altText);
    skipLink.setAttributeValue(UIConstants.SOURCE_ATTR, new IconURIBoundValue(TRANSPARENT_GIF));
    //--pu-- Hack: Use the existing label hiding class for hiding images as well
    skipLink.setStyleClass(XhtmlLafConstants.HIDDEN_LABEL_STYLE_CLASS);
    skipLink.render(context);
  }

  private static final class Counter
  {
    public int count = 0;
  }

  // Rendering context key for transparent gif
  // This MUST REMAIN THE SAME VALUE as
  //                 XhtmlRenderer._TRANSPARENT_FUNCTION_WRITTEN_KEY
  static final String __TRANSPARENT_URL_KEY = "_t.gif";

  // This property is used to control whether or not scripts should
  // be deferred.  We only defer scripts if we are rendering a
  // partial page response (or full page response in response to
  // a partial page request).  We could just check for a PartialPageContext,
  // but we actually need to know whether or not we are currently buffering
  // scripts.  We don't want to defer scripts which are rendered before
  // the ScriptBufferingResponseWriter kicks in - such as Core.js -
  // since IE has problems with deferred scripts.  This property is
  // set to Boolean.TRUE by BodyRenderer when rendering the contents
  // of the body.
  static final String __DEFER_SCRIPTS_KEY = "_defer";

  // object indicating that the value does not exist
  private static final Object _DOES_NOT_EXIST = new Object();

  // Key to look up the number of times we have rendered the script spacer:
  private static final Object _SCRIPT_SPACER_COUNT = new Object();

  // Key to look up style class as local property
  private static final Object _STYLE_CLASS_KEY = new Object();

  // Key to look up inline style as local property
  private static final Object _INLINE_STYLE_KEY = new Object();

  // Key to look up the anchor outside of the current navigation block
  private static final Object _NAV_BLOCK_END_ANCHOR_NAME_KEY = new Object();

  // Key to lookup the selected child index
  private static final Object _SELECTED_CHILD_INDEX_KEY = new Object();

  // The value bound to the type attribute in script tags in accessibilty mode.
  private static final String _ACCESSIBILITY_SCRIPT_TYPE = "text/javascript";
  private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(XhtmlLafRenderer.class);
}
