//
// $Id: MessageUtil.java,v 1.3 2004/03/03 11:44:09 mdb Exp $

package com.samskivert.text;

import java.text.MessageFormat;

import com.samskivert.util.StringUtil;

/**
 * Utility functions for translation string handling.
 */
public class MessageUtil
{
    /** Text prefixed by this character will be considered tainted when
     * doing recursive translations and won't be translated. */
    public static final String TAINT_CHAR = "~";

    /** Used to mark fully qualified message keys. */
    public static final String QUAL_PREFIX = "%";

    /** Used to separate the bundle qualifier from the message key in a
     * fully qualified message key. */
    public static final String QUAL_SEP = ":";

    /**
     * Call this to "taint" any string that has been entered by an entity
     * outside the application so that the translation code knows not to
     * attempt to translate this string when doing recursive translations.
     */
    public static String taint (Object text)
    {
        return TAINT_CHAR + text;
    }

    /**
     * Composes a message key with an array of arguments. The message can
     * subsequently be decomposed and translated without prior knowledge
     * of how many arguments were provided.
     */
    public static String compose (String key, Object... args)
    {
        StringBuilder buf = new StringBuilder();
        buf.append(key);
        buf.append('|');
        for (int i = 0; i < args.length; i++) {
            if (i > 0) {
                buf.append('|');
            }
            // escape the string while adding to the buffer
            String arg = (args[i] == null) ? "" : String.valueOf(args[i]);
            int alength = arg.length();
            for (int p = 0; p < alength; p++) {
                char ch = arg.charAt(p);
                if (ch == '|') {
                    buf.append("\\!");
                } else if (ch == '\\') {
                    buf.append("\\\\");
                } else {
                    buf.append(ch);
                }
            }
        }
        return buf.toString();
    }

    /**
     * Compose a message with String args. This is just a convenience so
     * callers do not have to cast their String[] to an Object[].
     */
    public static String compose (String key, String... args)
    {
        return compose(key, (Object[]) args);
    }

    /**
     * Used to escape single quotes so that they are not interpreted by
     * {@link MessageFormat}. As we assume all single quotes are to be
     * escaped, we cannot use the characters <code>{</code> and
     * <code>}</code> in our translation strings, but this is a small
     * price to pay to have to differentiate between messages that will
     * and won't eventually be parsed by a {@link MessageFormat} instance.
     */
    public static String escape (String message)
    {
        return StringUtil.replace(message, "'", "''");
    }

    /**
     * Unescapes characters that are escaped in a call to compose.
     */
    public static String unescape (String value)
    {
        int bsidx = value.indexOf('\\');
        if (bsidx == -1) {
            return value;
        }

        StringBuilder buf = new StringBuilder();
        int vlength = value.length();
        for (int i = 0; i < vlength; i++) {
            char ch = value.charAt(i);
            if (ch != '\\') {
                buf.append(ch);
            } else if (i < vlength-1) {
                // look at the next character
                ch = value.charAt(++i);
                buf.append((ch == '!') ? '|' : ch);
            } else {
                buf.append(ch);
            }
        }

        return buf.toString();
    }

    /**
     * A convenience method for calling {@link #compose(String,Object[])}
     * with an array of arguments that will be automatically tainted (see
     * {@link #taint}).
     */
    public static String tcompose (String key, Object... args)
    {
        int acount = args.length;
        String[] targs = new String[acount];
        for (int ii = 0; ii < acount; ii++) {
            targs[ii] = taint(args[ii]);
        }
        return compose(key, (Object[]) targs);
    }

    /**
     * A convenience method for calling {@link #compose(String,String[])}
     * with an array of argument that will be automatically tainted.
     */
    public static String tcompose (String key, String... args)
    {
        for (int ii = 0, nn = args.length; ii < nn; ii++) {
            args[ii] = taint(args[ii]);
        }
        return compose(key, args);
    }

    /**
     * Decomposes a compound key into its constituent parts. Arguments
     * that were tainted during composition will remain tainted.
     */
    public static String[] decompose (String compoundKey)
    {
        String[] args = StringUtil.split(compoundKey, "|");
        for (int ii = 0; ii < args.length; ii++) {
            args[ii] = unescape(args[ii]);
        }
        return args;
    }

    /**
     * Returns a fully qualified message key which, when translated by
     * some other bundle, will know to resolve and utilize the supplied
     * bundle to translate this particular key.
     */
    public static String qualify (String bundle, String key)
    {
        // sanity check
        if (bundle.indexOf(QUAL_PREFIX) != -1 ||
            bundle.indexOf(QUAL_SEP) != -1) {
            String errmsg = "Message bundle may not contain '" + QUAL_PREFIX +
                "' or '" + QUAL_SEP + "' [bundle=" + bundle +
                ", key=" + key + "]";
            throw new IllegalArgumentException(errmsg);
        }
        return QUAL_PREFIX + bundle + QUAL_SEP + key;
    }

    /**
     * Returns the bundle name from a fully qualified message key.
     *
     * @see #qualify
     */
    public static String getBundle (String qualifiedKey)
    {
        if (!qualifiedKey.startsWith(QUAL_PREFIX)) {
            throw new IllegalArgumentException(
                qualifiedKey + " is not a fully qualified message key.");
        }

        int qsidx = qualifiedKey.indexOf(QUAL_SEP);
        if (qsidx == -1) {
            throw new IllegalArgumentException(
                qualifiedKey + " is not a valid fully qualified key.");
        }

        return qualifiedKey.substring(QUAL_PREFIX.length(), qsidx);
    }

    /**
     * Returns the unqualified portion of the key from a fully qualified
     * message key.
     *
     * @see #qualify
     */
    public static String getUnqualifiedKey (String qualifiedKey)
    {
        if (!qualifiedKey.startsWith(QUAL_PREFIX)) {
            throw new IllegalArgumentException(
                qualifiedKey + " is not a fully qualified message key.");
        }

        int qsidx = qualifiedKey.indexOf(QUAL_SEP);
        if (qsidx == -1) {
            throw new IllegalArgumentException(
                qualifiedKey + " is not a valid fully qualified key.");
        }

        return qualifiedKey.substring(qsidx+1);
    }
}

@@ -3263,23 +3263,25 @@
 r (int i
+i
  = 0; i
+i
  %3C vleng
@@ -3276,32 +3276,33 @@
 ; ii %3C vlength; 
+i
 i++) %7B%0A         
@@ -3328,16 +3328,17 @@
 charAt(i
+i
 );%0A     
@@ -3354,24 +3354,43 @@
  (ch != '%5C%5C'
+ %7C%7C ii == vlength-1
 ) %7B%0A        
@@ -3435,27 +3435,8 @@
 else
- if (i %3C vlength-1)
  %7B%0A 
@@ -3517,16 +3517,17 @@
 arAt(++i
+i
 );%0A     
@@ -3577,61 +3577,8 @@
 h);%0A
-            %7D else %7B%0A                buf.append(ch);%0A
     

i -> ii, combined two identical cases.