Package org.apache.myfaces.trinidadinternal.renderkit.core.xhtml

Source Code of org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.XhtmlUtils

/*
* 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.renderkit.core.xhtml;

import java.io.IOException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.faces.component.UIComponent;
import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.behavior.ClientBehaviorContext;
import javax.faces.component.behavior.ClientBehaviorHint;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.apache.myfaces.trinidad.component.UIXComponentRef;
import org.apache.myfaces.trinidad.component.UIXIterator;
import org.apache.myfaces.trinidad.component.UIXSubform;
import org.apache.myfaces.trinidad.component.UIXSwitcher;
import org.apache.myfaces.trinidad.context.Agent;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.render.CoreRenderer;
import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.jsLibs.Scriptlet;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.jsLibs.XhtmlScriptletFactory;
import org.apache.myfaces.trinidadinternal.share.util.FastMessageFormat;


/**
* XHTML rendering utilities.
*/
public class XhtmlUtils
{
  /**
   * Skip over pure iteration components to find a "structural" parent.
   * This code is not guaranteed to work, but will work well enough.
   * @return a structural parent, or null if none exists
   */
  static public UIComponent getStructuralParent(UIComponent component)
  {
    while (true)
    {
      component = component.getParent();
      if (component == null)
        return null;

      if (_NON_STRUCTURAL_COMPONENT_FAMILIES.contains(component.getFamily()))
        continue;

      return component;
    }
  }

  /**
   * Returns true if the agent has enough support for Trinidad
   * to launch separate windows.  We require both multiple window
   * support and PPR support.
   */
  static public final boolean supportsSeparateWindow(
    Agent agent)
  {
    return (Boolean.TRUE.equals(
             agent.getCapabilities().get(TrinidadAgent.CAP_MULTIPLE_WINDOWS)) &&
            Boolean.TRUE.equals(
             agent.getCapabilities().get(TrinidadAgent.CAP_PARTIAL_RENDERING)));
  }

  /** Library key for the locale lib */
  public static final String CORE_LIB =
    XhtmlScriptletFactory.CORE_LIB;

  /**
   * Returns a composite ID based on a base and a suffix.
   */
  public static String getCompositeId(String baseid, String suffix)
  {
    int length = baseid.length() +
                 XhtmlConstants.COMPOSITE_ID_EXTENSION.length();
    if (suffix != null)
      length += suffix.length();
    StringBuilder compID
      = new StringBuilder(length);
    compID.append(baseid);
    compID.append(XhtmlConstants.COMPOSITE_ID_EXTENSION);
    if (suffix != null)
      compID.append(suffix);
    return compID.toString();
  }

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

  /**
   */
  static public void addLib(
    FacesContext     context,
    RenderingContext rc,
    Object           libKey
    ) throws IOException
  {
    if ((XhtmlRenderer.supportsScripting(rc)) && (libKey != null))
    {
      Scriptlet scriptlet = _sScriptletTable.get(libKey);
      if (scriptlet == null)
      {
        if (_LOG.isWarning())
          _LOG.warning("CANNOT_FIND_SCRIPTLET", libKey);
      }
      else
      {
        scriptlet.outputScriptlet(context, rc);
      }
    }
  }

  /**
   * Write out a script element importing a library.
   * The given URL will only be written once to the page.
   */
  public static void writeLibImport(
    FacesContext     context,
    RenderingContext rc,
    Object           libURL) throws IOException
  {
    // check if it's already been written
    Map<Object, Object> props = rc.getProperties();
    if (props.containsKey(libURL)) { return; }

    // put the lib name in the property map so it won't be written out again
    props.put(libURL, Boolean.TRUE);

    ResponseWriter writer = context.getResponseWriter();

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

    // For portlets, we want to make sure that we only import
    // each script once.  Employ document.write() to achieve this
    // effect.  (Note that on Netscape 4.x this caused major
    // problems when resizing windows - but we're done with Netscape 4)
    libURL = context.getExternalContext().encodeResourceURL(libURL.toString());

//    if (XhtmlConstants.FACET_PORTLET.equals(rc.getOutputMode()))
//    {
//      if (rc.getProperties().get(_PORTLET_LIB_TABLE_KEY) == null)
//      {
//        rc.getProperties().put(_PORTLET_LIB_TABLE_KEY, Boolean.TRUE);
//        writer.writeText("var _uixJSL;" +
//                         "if(!_uixJSL)_uixJSL={};" +
//                         "function _addJSL(u)" +
//                         "{" +
//                           "if(!_uixJSL[u])" +
//                           "{" +
//                             "_uixJSL[u]=1;" +
//                             "document.write(\"<scrip\"+" +
//                                            "\"t src=\\\"\"+u+" +
//                                            "\"\\\"></scrip\"+" +
//                                            "\"t>\")" +
//                           "}" +
//                         "}",
//             null);
//      }
//      writer.writeText("_addJSL(\"", null);
//      writer.writeText(libURL, null);
//      writer.writeText("\")", null);
//    }
//    else
//    {
      // The "safe" case: just write out the source
      writer.writeURIAttribute("src", libURL, null);
//    }

    writer.endElement("script");
  }

  /**
   * Return the chained JavaScript
   */
  public static String getChainedJS(
    String evh1,
    String evh2,
    boolean shortCircuit
    )
  {
    //
    // don't chain if one of the Strings is null or empty
    //
    if (evh1 == null)
      return evh2;

    if (evh2 == null)
      return evh1;

    int evh1Length = evh1.length();

    if (evh1Length == 0)
      return evh2;

    int evh2Length = evh2.length();

    if (evh2Length == 0)
      return evh1;

    //
    // Chain the results together
    //

    // allocate enough room for the constants plus double the length
    // of the possible-escaped strings
    //
    StringBuilder outBuilder = new StringBuilder(15 +
                                              evh1Length * 2 +
                                              3 +
                                              evh2Length * 2 +
                                              18);

    outBuilder.append("return _chain('");
    _escapeSingleQuotes(outBuilder, evh1);
    outBuilder.append("','");
    _escapeSingleQuotes(outBuilder, evh2);

    RenderingContext arc = RenderingContext.getCurrentInstance();
    boolean isDesktop = (arc.getAgent().getType().equals(Agent.TYPE_DESKTOP));

    if (isDesktop)
    {
      if ( shortCircuit )
        outBuilder.append("',this,event,true)");
      else
        outBuilder.append("',this,event)");
    }
    else
    {
      // Some mobile browsers do not support DOM Event object.
      // If event is passed, the script crushes before the function gains
      // control.
      if ( shortCircuit )
        outBuilder.append("',this,null,true)");
      else
        outBuilder.append("',this,null)");
    }

    return outBuilder.toString();
  }

  /**
   * Return the chained JavaScript
   */
  public static String getChainedJS(
    boolean   shortCircuit,
    String... scripts
    )
  {
    if (scripts.length == 0)
    {
      return null;
    }

    if (scripts.length <= 2)
    {
      // Use the more efficient code for two scripts, or less
      return getChainedJS(scripts[0], scripts.length == 2 ? scripts[1] : null, shortCircuit);
    }

    StringBuilder builder = new StringBuilder(100);
    builder.append("return _chainMultiple([");
    int firstNonNullScript = -1;
    int numScripts = 0;

    for (int i = 0, size = scripts.length; i < size; ++i)
    {
      String script = scripts[i];
      if (script == null) { continue; }
      script = script.trim();
      if (script.length() == 0) { continue; }
      ++numScripts;

      if (firstNonNullScript == -1)
      {
        builder.append('\'');
        firstNonNullScript = i;
      }
      else
      {
        builder.append(",'");
      }
      escapeJS(builder, script, true);
      builder.append('\'');
    }
    if (numScripts == 0) { return null; }
    if (numScripts == 1) { return scripts[firstNonNullScript]; }

    RenderingContext rc = RenderingContext.getCurrentInstance();
    if (rc.getAgent().getType().equals(Agent.TYPE_DESKTOP))
    {
      if (shortCircuit)
        builder.append("],this,event,true);");
      else
        builder.append("],this,event);");
    }
    else
    {
      // Some mobile browsers do not support DOM Event object.
      // If event is passed, the script crushes before the function gains
      // control.
      if (shortCircuit)
        builder.append("],this,null,true);");
      else
        builder.append("],this,null);");
    }

    return builder.toString();
  }

  /**
   * 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 escapeJS(inString, false /* inQuotes */);
  }

  /**
   * 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
    )
  {
    int charCount = inString.length();

    StringBuilder outBuilder = new StringBuilder(charCount * 2);

    escapeJS(outBuilder, inString, inQuotes);

    // since we only add characters, if the character count is different, we
    // will have a different output string, otherwise, reuse the input string,
    // as it is unchanged
    if (charCount != outBuilder.length())
    {
      return outBuilder.toString();
    }
    else
    {
      return inString;
    }
  }

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

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

  /**
   * Handle escaping '/', and single quotes, plus escaping text inside of
   * quotes.
   */
  public static void escapeJS(
    StringBuilder outBuilder,
    String       inString,
    boolean      inQuotes,
    int          escapeCount
    )
  {
    int leadSlashCount = (int)Math.pow(2, escapeCount) - 2;
    int charCount = inString.length();

    char    prevChar  = '\u0000';

    //
    // loop through the string escaping the single quotes at the \'s as
    // necessary
    //
    for (int i = 0; i < charCount; i++)
    {
      char currChar = inString.charAt(i);

      if (currChar == '\'')
      {
        if (!(inQuotes && (prevChar == '\\')))
        {
          // only toggle whetehr we are in quotes if the quote isn't escaped
          inQuotes = !inQuotes;
        }

        // handle double-escaping case
        // eg. "\'" + escapeJS(builder,"a'b",true,2) + "\'" -> "\'a\\\'b\'"
        for (int j=0; j < leadSlashCount; j++)
        {
          outBuilder.append('\\');
        }

        // always escape quotes
        outBuilder.append('\\');

        // output the current character
        outBuilder.append(currChar);
      }
      else
      {
        if (inQuotes)
        {
          if (currChar > 255)
          {
            outBuilder.append("\\u");
            _appendHexString(outBuilder, currChar, 4);
          }
          else
          {
            if ((currChar > 31) &&
                (currChar < 128))
            {
              if (currChar == '\\')
              {
                // escape all \'s in strings
                outBuilder.append('\\');
              }

              // output the current character
              outBuilder.append(currChar);
            }
            else
            {
              outBuilder.append("\\x");
              _appendHexString(outBuilder, currChar, 2);
            }
          }
        }
        else
        {
          // Double up backslashes (see bug 1676002)
          if (currChar == '\\')
            outBuilder.append('\\');

          // output the current character
          outBuilder.append(currChar);
        }
      }

      // keep track of the previous character to determine whether
      // single quotes are escaped
      prevChar = currChar;
    }
  }

  private static void _appendHexString(
    StringBuilder builder,
    int          number,
    int          minDigits
    )
  {
    String hexString = Integer.toHexString(number);

    int hexLength = hexString.length();

    int zeroPadding = minDigits - hexLength;

    if (zeroPadding > 0)
    {
      builder.append('0');

      while (zeroPadding > 1)
      {
        builder.append('0');
        zeroPadding--;
      }
    }
    else
    {
      if (zeroPadding < 0)
      {
        throw new IllegalArgumentException();
      }
    }

    builder.append(hexString);
  }

  private static void _escapeSingleQuotes(
    StringBuilder outBuilder,
    String       inString
    )
  {
    int     charCount = inString.length();
    char    prevChar  = '\u0000';
    boolean inQuotes  = false;

    //
    // loop through the string escaping the single quotes at the \'s as
    // necessary
    //
    for (int i = 0; i < charCount; i++)
    {
      char currChar = inString.charAt(i);

      if (currChar == '\'')
      {
        if (!(inQuotes && (prevChar == '\\')))
        {
          // only toggle whetehr we are in quotes if the quote isn't escaped
          inQuotes = !inQuotes;
        }

        // always escape quotes
        outBuilder.append('\\');
      }
      else if ((currChar == '\\') && inQuotes)
      {
        // escape all \'s in strings
        outBuilder.append('\\');
      }

      // output the current character
      outBuilder.append(currChar);

      // keep track of the previous character to determine whether
      // single quotes are escaped
      prevChar = currChar;
    }
  }
  public static String getJSIdentifier(String clientId)
  {
    if (clientId == null)
      return null;

    // Bug 3931544:  don't use colons in Javascript variable names.
    // We'll just replace colons with underscores;  not perfect, but adequate
    return clientId.replace(':','_');
  }

  public static String getFormattedString(String pattern, String[] parameters)
  {
    FastMessageFormat formatter = new FastMessageFormat(pattern);
    return formatter.format(parameters);
  }

  /*
   * This method returns the encoded parameter name or paramater value
   * for the Non-JavaScript browsers
   */
  public static String getEncodedParameter(String param)
  {
    return param + XhtmlConstants.NO_JS_PARAMETER_KEY;
  }

  /*
   * This method returns the name attribute of HTML elements for Non-JavaScript
   * browsers. It is encoded with parameter name and value pair.
   */
  public static String getEncodedNameAttribute(String param[])
  {
    // The incoming array(param[]) must contain parameter name and value pair
    // in the order of <<name1>>, <<value1>>, <<name2>>, <<value2>>,...
    // The encoded parameter name and value for the above would be
    // <<name1>><<encodingKey>><<value1>><<encodingKey>>
    // <<name2>><<encodingKey>><<value2>>

    int noOfParam = param.length;
    int bufferLen = 0;

    // Calculate what would be the length of the encoded param name and
    // value pair. We need it to initialize the buffer size of StringBuilder.
    for(int i = 0; i < noOfParam; i++)
    {
      bufferLen += param[i].length();
    }
    // If there are N parameter names and values, there would be N-1
    // encoding key so add its length also
    bufferLen  += (noOfParam -1) * XhtmlConstants.NO_JS_PARAMETER_KEY.length();

    StringBuilder nameAttri = new StringBuilder(bufferLen);

    //Encode all the parameter names and values except the last parameter value
    for(int i = 0; i < noOfParam-1; i++)
    {
      nameAttri.append(getEncodedParameter(param[i]));
    }

    nameAttri.append(param[noOfParam-1]);

    return(nameAttri.toString());
  }

  /**
   * Build a client event handler (onfocus for example) including any associated
   * client behaviors for the event.
   *
   * @param facesContext The faces context
   * @param component The component
   * @param eventName The event, without the "on*" prefix, to render
   * @param secondaryEventName If applicable, the secondary event name. For command components,
   * "click" and "action" behaviors are included together and for input components, "change" and
   * "valueChange" are included together.
   * @param eventHandlerScript Script to be executed after the behaviors. May be null
   * @param userHandlerScript user event handler to be executed before the event handler script and
   * any client behavior scripts. May be null.
   */
  public static String getClientEventHandler(
    FacesContext facesContext,
    UIComponent  component,
    String       eventName,
    String       secondaryEventName,
    String       userHandlerScript,
    String       eventHandlerScript
    )
  {
    BehaviorsData data = null;
    if (component instanceof ClientBehaviorHolder)
    {
      data = new BehaviorsData();
      _getBehaviorScripts(facesContext, component, eventName, data);

      if (secondaryEventName != null)
      {
        _getBehaviorScripts(facesContext, component, secondaryEventName, data);
      }
    }

    boolean hasHandler = eventHandlerScript != null && eventHandlerScript.length() > 0;
    boolean hasUserHandler = userHandlerScript != null && userHandlerScript.length() > 0;
    String script = null;
    boolean hasBehaviors = data != null && data.behaviorScripts != null &&
      !data.behaviorScripts.isEmpty();

    if (hasHandler && !hasBehaviors && !hasUserHandler)
    {
      script = eventHandlerScript;
    }
    else if (hasUserHandler && !hasBehaviors && !hasHandler)
    {
      script = userHandlerScript;
    }
    else if (!hasUserHandler && !hasHandler && hasBehaviors && data.behaviorScripts.size() == 1)
    {
      script = data.behaviorScripts.get(0);
    }
    else
    {
      // There are multiple scripts, we will need to chain the methods.
      int numBehaviorScripts = hasBehaviors ? data.behaviorScripts.size() : 0;
      int length = numBehaviorScripts;
      if (hasHandler) { ++length; }
      if (hasUserHandler) { ++length; }
      String[] scripts = new String[length];
      int index = 0;
      if (hasUserHandler)
      {
        scripts[0] = userHandlerScript;
        index = 1;
      }

      if (hasBehaviors)
      {
        System.arraycopy(data.behaviorScripts.toArray(), 0, scripts, index, numBehaviorScripts);
        index += numBehaviorScripts;
      }

      if (hasHandler)
      {
        scripts[index] = eventHandlerScript;
      }

      script = getChainedJS(true, scripts);
    }

    return script;
  }

  /**
   * Gather the behavior scripts for a client behavior holder
   *
   * @param facesContext the faces context
   * @param component the behavior holder (must implement ClientBehaviorHolder)
   * @param eventName the event of the behaviors to get
   * @param data the data to populate, which may have data from a previous invokation of this
   * function
   * @return the data collected while getting the behaviors (used for performance to avoid
   * duplicate lookup and allow for lazy loading of the parameters)
   */
  private static void _getBehaviorScripts(
    FacesContext  facesContext,
    UIComponent   component,
    String        eventName,
    BehaviorsData data)
  {
    ClientBehaviorHolder behaviorHolder = (ClientBehaviorHolder)component;

    List<ClientBehavior> behaviors = behaviorHolder.getClientBehaviors().get(eventName);
    if (behaviors != null && !behaviors.isEmpty())
    {
      // if params are not null, a submitting behavior was found in a previous call to this
      // function, so we do not need to check for submitting here
      if (data.params == null && _hasSubmittingBehavior(behaviors))
      {
        // We only need to gather the parameters if there is a submitting behavior, so do
        // not incur the performance overhead if not needed
        data.params = CoreRenderer.getBehaviorParameters(component);
      }

      ClientBehaviorContext behaviorContext = ClientBehaviorContext.createClientBehaviorContext(
        facesContext, component, eventName, component.getClientId(facesContext), data.params);

      if (data.behaviorScripts == null)
      {
        data.behaviorScripts = new ArrayList<String>(behaviors.size());
      }

      for (ClientBehavior behavior : behaviors)
      {
        String behaviorScript = behavior.getScript(behaviorContext);
        if (data.params != null && // if there are no params, there are no submitting behaviors,
          // so do not check
          behavior.getHints().contains(ClientBehaviorHint.SUBMITTING)
          && ("action".equals(eventName) || "click".equals(eventName)))
        {
          behaviorScript += ";return false"; // prevent any further JS execution
        }
        data.behaviorScripts.add(behaviorScript);
      }
    }
  }

  private static boolean _hasSubmittingBehavior(
    Iterable<ClientBehavior> behaviors)
  {
    for (ClientBehavior behavior : behaviors)
    {
      if (behavior.getHints().contains(ClientBehaviorHint.SUBMITTING))
      {
        return true;
      }
    }
    return false;
  }

  private static class BehaviorsData
  {
    Collection<ClientBehaviorContext.Parameter> params;
    List<String>                                behaviorScripts;
  }

  private static final Object _CLIENT_BEHAVIORS_KEY = new Object();

  /** HashMap mapping names to their scriptlets */
  private static Map<Object, Scriptlet> _sScriptletTable =
    Collections.synchronizedMap(new HashMap<Object, Scriptlet>(37));

  // Key for storing whether we've written out the script
  // for storing loaded libraries
  static private final Object _PORTLET_LIB_TABLE_KEY = new Object();
  static private final Set<String> _NON_STRUCTURAL_COMPONENT_FAMILIES;

  static
  {
    _NON_STRUCTURAL_COMPONENT_FAMILIES = new HashSet<String>();
    _NON_STRUCTURAL_COMPONENT_FAMILIES.add(UIXIterator.COMPONENT_FAMILY);
    _NON_STRUCTURAL_COMPONENT_FAMILIES.add(UIXComponentRef.COMPONENT_FAMILY);
    _NON_STRUCTURAL_COMPONENT_FAMILIES.add(UIXSubform.COMPONENT_FAMILY);
    _NON_STRUCTURAL_COMPONENT_FAMILIES.add(UIXSwitcher.COMPONENT_FAMILY);
  }

  static
  {
    XhtmlScriptletFactory.registerAllScriptlets();
  }

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

Related Classes of org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.XhtmlUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.