You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@myfaces.apache.org by Cale Scholl <ca...@oracle.com> on 2008/11/01 11:28:37 UTC

[Fwd: [Trinidad] Resolving differences between client-side and server-side message formatter?]

Hello,

I'm sending out this message again this time in html as well so that 
hopefully my comments are easier to read. This time I also attached a 
patch with my suggested changes (I did a clean install and everything 
built successfully). The patch is also available here:
https://issues.apache.org/jira/browse/TRINIDAD-1231

Thank you,
Cale

-------- Original Message --------
Subject: 	[Trinidad] Resolving differences between client-side and 
server-side message formatter?
Date: 	Thu, 30 Oct 2008 10:50:16 -0700
From: 	Cale Scholl <ca...@oracle.com>
Reply-To: 	MyFaces Development <de...@myfaces.apache.org>
To: 	dev@myfaces.apache.org



Hello,

I'm pretty new to the Trinidad community, so let me apologize in advance 
if there's something I've misunderstood.

What I'd like to talk about are the differences between the client-side 
message formatter:

trinidad-impl\src\main\javascript\META-INF\adf\jsLibs\Core.js -> 
function _formatErrorString(errorFormat, tokens)

and the server-side message formatter:

trinidad-api\src\main\java\org\apache\myfaces\trinidad\util\FastMessageFormat.java 
-> public String format(Object[] source)

The problem occurs when the application developer provides a custom 
error message, such as messageDetailXYZ="Isn't {0} cool?". When this is 
formatted by the client formatter, everything works fine. But let's say, 
for whatever reason, client validation is now disabled, and therefore 
this error message is formatted by the server formatter. When it hits 
the apostrophe, the rest of the message is interpreted literally, so the 
resulting formatted error message is "Isnt {0} cool?". This is bound to 
confuse the app developer. If the app developer instead provides 
messageDetailXYZ="Isn''t {0} cool?", then both the client and server 
formatter return "Isn't token0 cool?" (that is, it's formatted 
correctly), but I think requiring special escaping complicates things as 
well. Ultimately, I think messageDetailXYZ="Isn't {0} cool?" should be 
formatted correctly by both the client and server formatter.

I realize that since the server formatter is part of the public api, it 
may be difficult to change now. However, if others think it might be 
possible to resolve the client and server formatter differences, then 
please see the code pasted below, where I have inserted several 
questions and comments of the form:

///
// my comment here
///

Thank you,
Cale

---

Let's start with the server formatter. Personally, I prefer the server 
formatter's method of replacing patterns with tokens. However, if a 
pattern doesn't have an associated token, I'd prefer the server 
formatter just copy the pattern to the output string (in other words, 
interpret it as literal text), instead of throwing an exception. This 
way, there's no need to use single quotes for special escaping behavior.

trinidad-api\src\main\java\org\apache\myfaces\trinidad\util\FastMessageFormat.java:

 /**
  * Formats the given array of strings based on the initial
  * pattern.   It is legal for this array to be shorter
  * than that indicated by the pattern, or to have null
  * entries - these will simply be ignored.
  * <p>
  * @param source an array of strings
  */
 public String format(Object[] source)
 {
   int formatLength = _formatText.length;
   int length = 0;
   int sourceCount = source.length;
   for (int i = 0; i < sourceCount; i++)
   {
     Object sourceString = source[i];
     if (sourceString != null)
     {
       length += sourceString.toString().length();
     }
   }

   StringBuffer buffer = new StringBuffer(length + formatLength);

*///
// The above buffer size assumes that each pattern is only replaced once, i.e.
// if {0} occurs two or more times, only one instance is replaced. If we are
// making this assumption, then it should be enforced when replacing patterns below.
// If it's perfectly legal for {0} to be replaced in several spots, then we should comment
// that the above buffer size is just a rough initial estimate.
// Furthermore, if this function is only accessed by a single thread, we should
// instead use StringBuilder for performance purposes. 
///*

   int lastStart = 0;
   boolean inQuote = false;
   for (int i = 0; i < formatLength; i++)
   {
     char ch = _formatText[i];
     if (inQuote)
     {
       if (ch == '\'')
       {
         buffer.append(_formatText, lastStart, i - lastStart);
         i++;
         lastStart = i;
         inQuote = false;
       }
     }
     else
     {
       if (ch == '\'')
       {
         buffer.append(_formatText, lastStart, i - lastStart);
         i++;
         lastStart = i;

         // Check for doubled-up quotes
         if ((i < formatLength) && (_formatText[i] == '\''))
         {
           // Do nothing;  we'll add the doubled-up quote later
           ;
         }
         else
         {
           inQuote = true;
         }
       }

*///
// I think using single quotes to escape text is unnecessary. We should only try to
// replace patters of the type "{number}" for which 'number' is a valid token index. 
///*

       else if (ch == '{')
       {
         buffer.append(_formatText, lastStart, i - lastStart);

         int sourceIndex = 0;
         int j = i + 1;
         for (; j < formatLength; j++)
         {
           char patternChar = _formatText[j];
           if (patternChar == '}')
           {
             break;
           }
           else
           {
             if ((patternChar < '0') || (patternChar > '9'))
               throw new 
IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString("FASTMESSAGEFORMAT_ONLY_SUPPORT_NUMERIC_ARGUMENTS"));
             sourceIndex = (sourceIndex * 10) + (patternChar - '0');
           }
         }

*///
// This seems overly complex. Can't we put a hard-coded limit on the pattern number?
// For example, are we ever going to have more than 10 tokens? It would be much simpler
// if we only had to look for a single digit number followed by a '}'.
///*

         if (j == formatLength)
           throw new 
IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString("END_OF_PATTERN_NOT_FOUND"));
         if (j == i + 1)
           throw new 
IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString("FASTMESSAGEFORMAT_FIND_EMPTY_ARGUMENT"));
        
*///
// I think for the above cases we should just interpret the text literally instead
// of throwing exceptions.
///*

         if (sourceIndex < sourceCount)
         {
           Object sourceString = source[sourceIndex];
           if (sourceString != null)
             buffer.append(sourceString.toString());
         }

*///
// If the pattern "{*}" contains anything besides a
// non-negative number, we throw an exception, but if it contains a number >= the
// number of tokens, the whole "{*}" is replaced by the empty string (I'm surprised
// that for consistency purposes that an exception isn't thrown instead). However,
// I think that in both of these cases the pattern should just be interpreted as literal text,
// and that the pattern should only be replaced with the empty string when it has an
// associated token and the token is null or empty.
///*

         i = j;
         lastStart = i + 1;
       }
       else
       {
         // Do nothing.  The character will be added in later
         ;
       }
     }
   }

   buffer.append(_formatText, lastStart, formatLength - lastStart);

   return new String(buffer);
 }

---

Now let's talk about the client formatter. Using regular expressions to 
match patterns is elegant, but personally I think it's overkill for what 
we need here. The server formatter's method of replacing patterns with 
tokens is faster, plus it completely avoids the issue of infinite 
pattern replacement, should the token itself contain "{0}" or something 
like that.

trinidad-impl\src\main\javascript\META-INF\adf\jsLibs\Core.js:

/**
* Performs token replacement on the the error format, replacing each
* token found in the token Object with the value for that token.
*/
function _formatErrorString(
 errorFormat, // error format string with embedded tokens to be replaced
 tokens       // tokens Object containin token names and values to be 
replaced
 )
{
 var currString = errorFormat;

*///
// We have a fundamental difference in behavior between this formatter and the
// server formatter. This formatter only tries to replace patterns for which we
// actually have a token; the server formatter throws exceptions.
///*

 // loop through all of the tokens, replacing them if they are present
 for (var currToken in tokens)
 {
   var currValue = tokens[currToken];

   // if the token has no value
   if (!currValue)
   {
     currValue = "";
   }

   // TRINIDAD-829:
   // we replace '{' and '}' to ensure, that tokens containing values
   // like {3} aren't parsed more than once...
   // Only do this if it is typeof string (see TRINIDAD-873)
   if (typeof currValue == "string")
   {
   currValue = currValue.replace("{","{'");
   currValue = currValue.replace("}","'}");
   }

*///
// 1) - footnote for above text
// This is a good way to avoid the infinite pattern replacement problem; however,
// even better would be to use the server formatter's replacement method :)
///*

   // the tokens are delimited by '{' before and '}' after the token
   var currRegExp = "{" + currToken + "}";

   // support tokens of the form %token% as well as {token}
   currString = currString.replace(new RegExp('%' + currToken + '%', 'g'),
                                   currRegExp);

*///
// Do we still need to support tokens of the type %number%? The server formatter
// only supports tokens of the type {number}. If one supports both types, so should
// the other.
///*

   // Replace the token.  Don't use String.replace, as the value may
   // include dollar signs, which leads Netscape astray (bug 2242675)
   var indexOf = currString.indexOf(currRegExp);

*///
// Doesn't footnote 1) make the following check unnecessary?
///*

   if (currValue.indexOf && currValue.indexOf(currRegExp) >= 0)
   {
    var b1 = '';
    for (i=0; i<currValue.length; i++)
    {
      b1 = b1 + 'placeHolderString';
    } 
 
    while (indexOf >= 0)
   {
     currString=(currString.substring(0,indexOf)
          + b1
          + currString.substring(indexOf+currRegExp.length));
     indexOf = currString.indexOf(currRegExp);  
   }   
 
   indexOf = currString.indexOf(b1);
 
   while (indexOf >= 0)
   { 
     currString =(currString.substring(0,indexOf)
          + currValue
          + currString.substring(indexOf+b1.length));     
     indexOf = currString.indexOf(b1);  
   }
 }
 else
   while (indexOf >= 0)
   {
     currString = (currString.substring(0, indexOf)
                     + currValue
                     + currString.substring(indexOf + currRegExp.length));
     indexOf = currString.indexOf(currRegExp);
   }
}

 // TRINIDAD-829:
 // we finally re-replace the '{' and '}'...
 while(currString.indexOf("{'")!=-1)
 {
   currString= currString.replace("{'","{");
   currString= currString.replace("'}","}");
 }

*///
// Having the above behavior means already existing "{'" will be replaced as well;
// if we must keep this behavior, it should be well documented.
///*

 // And now take any doubled-up single quotes down to one,
 // to handle escaping
 var twoSingleQuotes = /''/g;

*///
// Having the above behavior means already existing "''" will be replaced as well;
// if we must keep this behavior, it should be well documented.
///*

 return currString.replace(twoSingleQuotes, "'");
}


Re: [Fwd: [Trinidad] Resolving differences between client-side and server-side message formatter?]

Posted by Matthias Wessendorf <ma...@apache.org>.
I think this is OK.

let me give the patch tomorrow a shot, in case someone has an issue with this.

-M

On Sat, Nov 1, 2008 at 11:28 AM, Cale Scholl <ca...@oracle.com> wrote:
> Hello,
>
> I'm sending out this message again this time in html as well so that
> hopefully my comments are easier to read. This time I also attached a patch
> with my suggested changes (I did a clean install and everything built
> successfully). The patch is also available here:
> https://issues.apache.org/jira/browse/TRINIDAD-1231
>
> Thank you,
> Cale
>
> -------- Original Message --------
> Subject: [Trinidad] Resolving differences between client-side and
> server-side message formatter?
> Date: Thu, 30 Oct 2008 10:50:16 -0700
> From: Cale Scholl <ca...@oracle.com>
> Reply-To: MyFaces Development <de...@myfaces.apache.org>
> To: dev@myfaces.apache.org
>
> Hello,
>
> I'm pretty new to the Trinidad community, so let me apologize in advance
> if there's something I've misunderstood.
>
> What I'd like to talk about are the differences between the client-side
> message formatter:
>
> trinidad-impl\src\main\javascript\META-INF\adf\jsLibs\Core.js ->
> function _formatErrorString(errorFormat, tokens)
>
> and the server-side message formatter:
>
> trinidad-api\src\main\java\org\apache\myfaces\trinidad\util\FastMessageFormat.java
> -> public String format(Object[] source)
>
> The problem occurs when the application developer provides a custom
> error message, such as messageDetailXYZ="Isn't {0} cool?". When this is
> formatted by the client formatter, everything works fine. But let's say,
> for whatever reason, client validation is now disabled, and therefore
> this error message is formatted by the server formatter. When it hits
> the apostrophe, the rest of the message is interpreted literally, so the
> resulting formatted error message is "Isnt {0} cool?". This is bound to
> confuse the app developer. If the app developer instead provides
> messageDetailXYZ="Isn''t {0} cool?", then both the client and server
> formatter return "Isn't token0 cool?" (that is, it's formatted
> correctly), but I think requiring special escaping complicates things as
> well. Ultimately, I think messageDetailXYZ="Isn't {0} cool?" should be
> formatted correctly by both the client and server formatter.
>
> I realize that since the server formatter is part of the public api, it
> may be difficult to change now. However, if others think it might be
> possible to resolve the client and server formatter differences, then
> please see the code pasted below, where I have inserted several
> questions and comments of the form:
>
> ///
> // my comment here
> ///
>
> Thank you,
> Cale
>
> ---
>
> Let's start with the server formatter. Personally, I prefer the server
> formatter's method of replacing patterns with tokens. However, if a
> pattern doesn't have an associated token, I'd prefer the server
> formatter just copy the pattern to the output string (in other words,
> interpret it as literal text), instead of throwing an exception. This
> way, there's no need to use single quotes for special escaping behavior.
>
> trinidad-api\src\main\java\org\apache\myfaces\trinidad\util\FastMessageFormat.java:
>
>  /**
>   * Formats the given array of strings based on the initial
>   * pattern.   It is legal for this array to be shorter
>   * than that indicated by the pattern, or to have null
>   * entries - these will simply be ignored.
>   * <p>
>   * @param source an array of strings
>   */
>  public String format(Object[] source)
>  {
>    int formatLength = _formatText.length;
>    int length = 0;
>    int sourceCount = source.length;
>    for (int i = 0; i < sourceCount; i++)
>    {
>      Object sourceString = source[i];
>      if (sourceString != null)
>      {
>        length += sourceString.toString().length();
>      }
>    }
>
>    StringBuffer buffer = new StringBuffer(length + formatLength);
>
> ///
> // The above buffer size assumes that each pattern is only replaced once,
> i.e.
> // if {0} occurs two or more times, only one instance is replaced. If we are
> // making this assumption, then it should be enforced when replacing
> patterns below.
> // If it's perfectly legal for {0} to be replaced in several spots, then we
> should comment
> // that the above buffer size is just a rough initial estimate.
> // Furthermore, if this function is only accessed by a single thread, we
> should
> // instead use StringBuilder for performance purposes.
> ///
>
>    int lastStart = 0;
>    boolean inQuote = false;
>    for (int i = 0; i < formatLength; i++)
>    {
>      char ch = _formatText[i];
>      if (inQuote)
>      {
>        if (ch == '\'')
>        {
>          buffer.append(_formatText, lastStart, i - lastStart);
>          i++;
>          lastStart = i;
>          inQuote = false;
>        }
>      }
>      else
>      {
>        if (ch == '\'')
>        {
>          buffer.append(_formatText, lastStart, i - lastStart);
>          i++;
>          lastStart = i;
>
>          // Check for doubled-up quotes
>          if ((i < formatLength) && (_formatText[i] == '\''))
>          {
>            // Do nothing;  we'll add the doubled-up quote later
>            ;
>          }
>          else
>          {
>            inQuote = true;
>          }
>        }
>
> ///
> // I think using single quotes to escape text is unnecessary. We should only
> try to
> // replace patters of the type "{number}" for which 'number' is a valid
> token index.
> ///
>
>        else if (ch == '{')
>        {
>          buffer.append(_formatText, lastStart, i - lastStart);
>
>          int sourceIndex = 0;
>          int j = i + 1;
>          for (; j < formatLength; j++)
>          {
>            char patternChar = _formatText[j];
>            if (patternChar == '}')
>            {
>              break;
>            }
>            else
>            {
>              if ((patternChar < '0') || (patternChar > '9'))
>                throw new
> IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString("FASTMESSAGEFORMAT_ONLY_SUPPORT_NUMERIC_ARGUMENTS"));
>              sourceIndex = (sourceIndex * 10) + (patternChar - '0');
>            }
>          }
>
> ///
> // This seems overly complex. Can't we put a hard-coded limit on the pattern
> number?
> // For example, are we ever going to have more than 10 tokens? It would be
> much simpler
> // if we only had to look for a single digit number followed by a '}'.
> ///
>
>          if (j == formatLength)
>            throw new
> IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString("END_OF_PATTERN_NOT_FOUND"));
>          if (j == i + 1)
>            throw new
> IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString("FASTMESSAGEFORMAT_FIND_EMPTY_ARGUMENT"));
>
> ///
> // I think for the above cases we should just interpret the text literally
> instead
> // of throwing exceptions.
> ///
>
>          if (sourceIndex < sourceCount)
>          {
>            Object sourceString = source[sourceIndex];
>            if (sourceString != null)
>              buffer.append(sourceString.toString());
>          }
>
> ///
> // If the pattern "{*}" contains anything besides a
> // non-negative number, we throw an exception, but if it contains a number
>>= the
> // number of tokens, the whole "{*}" is replaced by the empty string (I'm
> surprised
> // that for consistency purposes that an exception isn't thrown instead).
> However,
> // I think that in both of these cases the pattern should just be
> interpreted as literal text,
> // and that the pattern should only be replaced with the empty string when
> it has an
> // associated token and the token is null or empty.
> ///
>
>          i = j;
>          lastStart = i + 1;
>        }
>        else
>        {
>          // Do nothing.  The character will be added in later
>          ;
>        }
>      }
>    }
>
>    buffer.append(_formatText, lastStart, formatLength - lastStart);
>
>    return new String(buffer);
>  }
>
> ---
>
> Now let's talk about the client formatter. Using regular expressions to
> match patterns is elegant, but personally I think it's overkill for what
> we need here. The server formatter's method of replacing patterns with
> tokens is faster, plus it completely avoids the issue of infinite
> pattern replacement, should the token itself contain "{0}" or something
> like that.
>
> trinidad-impl\src\main\javascript\META-INF\adf\jsLibs\Core.js:
>
> /**
> * Performs token replacement on the the error format, replacing each
> * token found in the token Object with the value for that token.
> */
> function _formatErrorString(
>  errorFormat, // error format string with embedded tokens to be replaced
>  tokens       // tokens Object containin token names and values to be
> replaced
>  )
> {
>  var currString = errorFormat;
>
> ///
> // We have a fundamental difference in behavior between this formatter and
> the
> // server formatter. This formatter only tries to replace patterns for which
> we
> // actually have a token; the server formatter throws exceptions.
> ///
>
>  // loop through all of the tokens, replacing them if they are present
>  for (var currToken in tokens)
>  {
>    var currValue = tokens[currToken];
>
>    // if the token has no value
>    if (!currValue)
>    {
>      currValue = "";
>    }
>
>    // TRINIDAD-829:
>    // we replace '{' and '}' to ensure, that tokens containing values
>    // like {3} aren't parsed more than once...
>    // Only do this if it is typeof string (see TRINIDAD-873)
>    if (typeof currValue == "string")
>    {
>    currValue = currValue.replace("{","{'");
>    currValue = currValue.replace("}","'}");
>    }
>
> ///
> // 1) - footnote for above text
> // This is a good way to avoid the infinite pattern replacement problem;
> however,
> // even better would be to use the server formatter's replacement method :)
> ///
>
>    // the tokens are delimited by '{' before and '}' after the token
>    var currRegExp = "{" + currToken + "}";
>
>    // support tokens of the form %token% as well as {token}
>    currString = currString.replace(new RegExp('%' + currToken + '%', 'g'),
>                                    currRegExp);
>
> ///
> // Do we still need to support tokens of the type %number%? The server
> formatter
> // only supports tokens of the type {number}. If one supports both types, so
> should
> // the other.
> ///
>
>    // Replace the token.  Don't use String.replace, as the value may
>    // include dollar signs, which leads Netscape astray (bug 2242675)
>    var indexOf = currString.indexOf(currRegExp);
>
> ///
> // Doesn't footnote 1) make the following check unnecessary?
> ///
>
>    if (currValue.indexOf && currValue.indexOf(currRegExp) >= 0)
>    {
>     var b1 = '';
>     for (i=0; i<currValue.length; i++)
>     {
>       b1 = b1 + 'placeHolderString';
>     }
>
>     while (indexOf >= 0)
>    {
>      currString=(currString.substring(0,indexOf)
>           + b1
>           + currString.substring(indexOf+currRegExp.length));
>      indexOf = currString.indexOf(currRegExp);
>    }
>
>    indexOf = currString.indexOf(b1);
>
>    while (indexOf >= 0)
>    {
>      currString =(currString.substring(0,indexOf)
>           + currValue
>           + currString.substring(indexOf+b1.length));
>      indexOf = currString.indexOf(b1);
>    }
>  }
>  else
>    while (indexOf >= 0)
>    {
>      currString = (currString.substring(0, indexOf)
>                      + currValue
>                      + currString.substring(indexOf + currRegExp.length));
>      indexOf = currString.indexOf(currRegExp);
>    }
> }
>
>  // TRINIDAD-829:
>  // we finally re-replace the '{' and '}'...
>  while(currString.indexOf("{'")!=-1)
>  {
>    currString= currString.replace("{'","{");
>    currString= currString.replace("'}","}");
>  }
>
> ///
> // Having the above behavior means already existing "{'" will be replaced as
> well;
> // if we must keep this behavior, it should be well documented.
> ///
>
>  // And now take any doubled-up single quotes down to one,
>  // to handle escaping
>  var twoSingleQuotes = /''/g;
>
> ///
> // Having the above behavior means already existing "''" will be replaced as
> well;
> // if we must keep this behavior, it should be well documented.
> ///
>
>  return currString.replace(twoSingleQuotes, "'");
> }
>
> Index:
> trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/FastMessageFormat.java
> ===================================================================
> ---
> trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/FastMessageFormat.java
>  (revision 708412)
> +++
> trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/FastMessageFormat.java
>  (working copy)
> @@ -6,9 +6,9 @@
>  *  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
> @@ -24,7 +24,7 @@
>  * The FastMessageFormat class is a greatly reduced version
>  * of the java.text.MessageFormat class.  It's also much faster
>  * and much less expensive to create, which is especially
> - * valuable when it is created and thrown away many times -
> + * valuable when it is created and thrown away many times -
>  * a common use case in web applications.
>  * <p>
>  * The only syntax supported by this class is simple index-based
> @@ -65,7 +65,7 @@
>    * <p>
>    * @param source an array of strings
>    */
> -  public String format(Object[] source)
> +  /*public String formatOld(Object[] source)
>   {
>     int formatLength = _formatText.length;
>     int length = 0;
> @@ -130,33 +130,29 @@
>             }
>             else
>             {
> -              if ((patternChar < '0') ||
> -                  (patternChar > '9'))
> -                throw new
> IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString(
> -                  "FASTMESSAGEFORMAT_ONLY_SUPPORT_NUMERIC_ARGUMENTS"));
> +              if ((patternChar < '0') || (patternChar > '9'))
> +                throw new
> IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString("FASTMESSAGEFORMAT_ONLY_SUPPORT_NUMERIC_ARGUMENTS"));
>               sourceIndex = (sourceIndex * 10) + (patternChar - '0');
>             }
>           }
>
>           if (j == formatLength)
> -            throw new
> IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString(
> -              "END_OF_PATTERN_NOT_FOUND"));
> +            throw new
> IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString("END_OF_PATTERN_NOT_FOUND"));
>           if (j == i + 1)
> -            throw new
> IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString(
> -              "FASTMESSAGEFORMAT_FIND_EMPTY_ARGUMENT"));
> +            throw new
> IllegalArgumentException(_LOG.getLogger().getResourceBundle().getString("FASTMESSAGEFORMAT_FIND_EMPTY_ARGUMENT"));
>           if (sourceIndex < sourceCount)
>           {
>             Object sourceString = source[sourceIndex];
>             if (sourceString != null)
>               buffer.append(sourceString.toString());
>           }
> -
> +
>           i = j;
>           lastStart = i + 1;
>         }
>         else
>         {
> -           // Do nothing.  The character will be added in later
> +          // Do nothing.  The character will be added in later
>           ;
>         }
>       }
> @@ -165,13 +161,67 @@
>     buffer.append(_formatText, lastStart, formatLength - lastStart);
>
>     return new String(buffer);
> -  }
> +  }*/
>
> -  private final char[] _formatText;
> -  private static final TrinidadLogger _LOG =
> TrinidadLogger.createTrinidadLogger(
> -    FastMessageFormat.class);
> -}
> +  /**
> +   * This formatter will only replace patterns of the type "{[0-9]}"
> +   * for which there is an associated token.
> +   * Any other use of '{}' will be interpreted as literal text.
> +   * This aims to have the same behavior as TrFastMessageFormatUtils.format
> +   * on the client.
> +   * <p>
> +   * @param source an array of strings (tokens)
> +   */
> +  public String format(Object[] source)
> +  {
> +    int formatLength = _formatText.length;
> +    int length = 0;
> +    int tokenCount = source.length;
> +    for (int i = 0; i < tokenCount; i++)
> +    {
> +      Object sourceString = source[i];
> +      if (sourceString != null)
> +      {
> +        length += sourceString.toString().length();
> +      }
> +    }
> +
> +    // The following buffer size is just an initial estimate. It is legal
> for
> +    // any given pattern, such as {0}, to occur more than once, in which
> case
> +    // the buffer size will expand automatically if need be.
> +    StringBuilder buffer = new StringBuilder(length + formatLength);
>
> +    int lastStart = 0;
> +    for (int i = 0; i < formatLength; i++)
> +    {
> +      char ch = _formatText[i];
> +      if (ch == '{')
> +      {
> +        // Only check for single digit patterns that have an associated
> token.
> +        if (i + 2 < formatLength && _formatText[i+2] == '}')
> +        {
> +          int tokenIndex = _formatText[i+1] - '0';
> +          if (tokenIndex >= 0 && tokenIndex < tokenCount)
> +          {
> +            buffer.append(_formatText, lastStart, i - lastStart);
> +            Object sourceString = source[tokenIndex];
> +            if (sourceString != null)
> +              buffer.append(sourceString.toString());
> +
> +            i += 2;
> +            lastStart = i + 1;
> +          }
> +        }
> +      }
> +      // ELSE: Do nothing. The character will be added in later.
> +    }
>
> +    buffer.append(_formatText, lastStart, formatLength - lastStart);
>
> +    return new String(buffer);
> +  }
>
> +  private final char[] _formatText;
> +  private static final TrinidadLogger _LOG =
> +    TrinidadLogger.createTrinidadLogger(FastMessageFormat.class);
> +}
> Index:
> trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/MessageFactory.java
> ===================================================================
> ---
> trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/MessageFactory.java
>     (revision 708412)
> +++
> trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/MessageFactory.java
>     (working copy)
> @@ -430,7 +430,7 @@
>   {
>     if (parameters == null)
>       return pattern;
> -
> +
>     FastMessageFormat formatter = new FastMessageFormat(pattern);
>     String fmtedMsgStr = formatter.format(parameters);
>     return fmtedMsgStr;
> @@ -610,7 +610,7 @@
>       _customDetailErrorMessage = customDetailErrorMessage;
>       _hasBoundParameters = hasBoundParameters;
>     }
> -
> +
>     // Currently only detail message can be customized. So we override the
>     // detail message. If summary is to be overridden we have to do the
>     // same to it also.
> @@ -619,6 +619,7 @@
>     {
>       FacesContext context    = FacesContext.getCurrentInstance();
>       String detailMsgPattern =
> (String)_customDetailErrorMessage.getValue(context.getELContext());
> +
>       if(detailMsgPattern == null)
>       {
>         // Set a default message that might get used by FacesException
> @@ -631,7 +632,10 @@
>
>         // Since that string will get parsed by FastMessageFormat, the { }
>         // of the EL must be escaped
> -        detailMsgPattern = '\'' + detailMsgPattern + '\'';
> +        //detailMsgPattern = '\'' + detailMsgPattern + '\'';
> +
> +        // No need to format this string, just return it here.
> +        return detailMsgPattern;
>       }
>
>       Object[] params = super.getParameters();
> Index:
> trinidad-api/src/test/java/org/apache/myfaces/trinidad/util/FastMessageFormatTest.java
> ===================================================================
> ---
> trinidad-api/src/test/java/org/apache/myfaces/trinidad/util/FastMessageFormatTest.java
>      (revision 0)
> +++
> trinidad-api/src/test/java/org/apache/myfaces/trinidad/util/FastMessageFormatTest.java
>      (revision 0)
> @@ -0,0 +1,59 @@
> +/*
> + *  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.trinidad.util;
> +
> +import junit.framework.Test;
> +import junit.framework.TestCase;
> +import junit.framework.TestSuite;
> +
> +import org.apache.myfaces.trinidad.util.FastMessageFormat;
> +
> +public class FastMessageFormatTest
> +  extends TestCase
> +{
> +  public static final Test suite()
> +  {
> +    return new TestSuite(FastMessageFormatTest.class);
> +  }
> +
> +  public static void main(String[] args)
> +    throws Throwable
> +  {
> +    junit.textui.TestRunner.run(suite());
> +  }
> +
> +  public FastMessageFormatTest(String testName)
> +  {
> +    super(testName);
> +  }
> +
> +  public void testGet()
> +  {
> +    // {0} and {1} should be replaced.
> +    // Param for {2} is null, so remove {2}.
> +    // The rest is interpreted literally.
> +    // Expected result: "beef {{3} isn't {} {a} {12a}kosher {"
> +    FastMessageFormat fmf =
> +      new FastMessageFormat("{0} {{3} isn't {} {a} {12a}{2}{1} {");
> +    String[] params = { "beef", "kosher", null };
> +    String result = fmf.format(params);
> +    assertEquals(result, "beef {{3} isn't {} {a} {12a}kosher {");
> +  }
> +}
> +
> Index: trinidad-impl/src/main/javascript/META-INF/adf/jsLibs/Locale.js
> ===================================================================
> --- trinidad-impl/src/main/javascript/META-INF/adf/jsLibs/Locale.js
> (revision 708412)
> +++ trinidad-impl/src/main/javascript/META-INF/adf/jsLibs/Locale.js
> (working copy)
> @@ -830,7 +830,7 @@
>   * @param {any...:undefined} Varargs objects to substitute for positional
> parameters.
>   * Each parameter will be converted to a String and substituted into the
> format.
>   */
> -TrFastMessageFormatUtils.format = function(
> +/*TrFastMessageFormatUtils.formatOld = function(
>   formatString, // error format string with embedded indexes to be replaced
>   parameters    // {any...:undefined} Varargs objects to substitute for
> positional parameters.
>   )
> @@ -846,6 +846,67 @@
>   // TODO - move the code of the function below into here after
>   //        simplifying it
>   return _formatErrorString(formatString, tempArray);
> +}*/
> +
> + /**
> +  * This formatter will only replace patterns of the type "{[0-9]}"
> +  * for which there is an associated token.
> +  * Any other use of '{}' will be interpreted as literal text.
> +  * This aims to have the same behavior as FastMessageFormat.format
> +  * on the server.
> +  * @param {String} String to format
> +  * @param {any...:undefined} Varargs objects to substitute for positional
> parameters.
> +  * Each parameter will be converted to a String and substituted into the
> format.
> +  */
> +TrFastMessageFormatUtils.format = function(
> +  formatString, // error format string with embedded indexes to be replaced
> +  parameters    // {any...:undefined} Varargs objects to substitute for
> positional parameters.
> +  )
> +{
> +  // There are arguments.length - 1 tokens:
> +  // arguments[1], ..., arguments[arguments.length-1]
> +  var formatLength = formatString.length;
> +  var tokenCount = arguments.length - 1;
> +
> +  // Use the javascript StringBuffer technique.
> +  var buffer = [];
> +
> +  var lastStart = 0;
> +  for (var i = 0; i < formatLength; i++)
> +  {
> +    var ch = formatString[i];
> +    if (ch == '{')
> +    {
> +      // Only check for single digit patterns that have an associated
> token.
> +      if (i + 2 < formatLength && formatString[i+2] == '}')
> +      {
> +        var tokenIndex = formatString[i+1] - '0';
> +        if (tokenIndex >= 0 && tokenIndex < tokenCount)
> +        {
> +          // Use the javascript StringBuffer technique for append(string)
> +          var substr = formatString.substring(lastStart, i);
> +          buffer.push(substr);
> +
> +          var token = arguments[tokenIndex+1];
> +          if (token != null)
> +            buffer.push(token);
> +
> +          i += 2;
> +          lastStart = i + 1;
> +        }
> +      }
> +    }
> +    // ELSE: Do nothing. The character will be added in later.
> +  }
> +
> +  if (lastStart < formatLength)
> +  {
> +    var substr = formatString.substring(lastStart);
> +    buffer.push(substr);
> +  }
> +
> +  // Use the javascript StringBuffer technique for toString()
> +  return buffer.join("");
>  }
>
>  var TrMessageFactory = new Object();
>
>



-- 
Matthias Wessendorf

blog: http://matthiaswessendorf.wordpress.com/
sessions: http://www.slideshare.net/mwessendorf
twitter: http://twitter.com/mwessendorf