org.rythmengine.utils.S.java Source code

Java tutorial

Introduction

Here is the source code for org.rythmengine.utils.S.java

Source

/* 
 * Copyright (C) 2013 The Rythm Engine project
 * Gelin Luo <greenlaw110(at)gmail.com>
 *
 * 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.rythmengine.utils;

import org.apache.commons.lang3.StringEscapeUtils;
import org.rythmengine.RythmEngine;
import org.rythmengine.conf.RythmConfiguration;
import org.rythmengine.extension.IFormatter;
import org.rythmengine.extension.II18nMessageResolver;
import org.rythmengine.extension.Transformer;
import org.rythmengine.internal.CacheKey;
import org.rythmengine.logger.ILogger;
import org.rythmengine.logger.Logger;
import org.rythmengine.template.ITemplate;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.URLEncoder;
import java.text.*;
import java.util.*;

/**
 * A utility class providing String manipulation methods. Commonly used in template engine process.
 * <p/>
 * <p>Note all methods defined in this class is null safe.
 * if any input is <code>null</code> the return value is <code>""</code></p>
 * <p/>
 * <p>An instance of this utility is exposed to any Rythm template
 * via {@link org.rythmengine.template.TemplateBase#s()} method and can be used
 * in template source code freely. E.g.</p>
 * <p/>
 * <pre><code>
 * {@literal @}if(s().empty(name)) {
 * <div class="alert alert-error">user name is empty!</div>
 * }
 * </code></pre>
 */
public class S {
    public static final S INSTANCE = new S();
    public static final String EMPTY_STR = "";
    private static final ILogger logger = Logger.get(S.class);

    /**
     * Determine if a given String is null or empty. By empty it
     * means equals to <code>""</code> after do a {@link String#trim()}
     * operation on it
     *
     * @param s
     * @return true if the String specified is empty or null
     */
    public static boolean isEmpty(String s) {
        return null == s || "".equals(s.trim());
    }

    /**
     * Alias of {@link #isEmpty(String)}
     *
     * @param s
     * @return true if the given string is empty
     */
    public static boolean empty(String s) {
        return null == s || "".equals(s.trim());
    }

    /**
     * Determine if a given String is NOT null or empty.
     *
     * @param s
     * @return false if the String specified is empty or null
     * @see #isEmpty(String)
     */
    public static boolean isNotEmpty(String s) {
        return !isEmpty(s);
    }

    /**
     * Alias of {@link #isNotEmpty(String)}
     *
     * @param s
     * @return true if the give string is not empty
     */
    public static boolean notEmpty(String s) {
        return !isEmpty(s);
    }

    /**
     * Determine if a given Object instance is null or empty after it
     * converted to a String.
     *
     * @param o
     * @return true if the object string representation is empty
     * @see #isEmpty(String)
     */
    public static boolean isEmpty(Object o) {
        return null == o || "".equals(o.toString().trim());
    }

    /**
     * Alias of {@link #isEmpty(Object)}
     *
     * @param o
     * @return true if the object string representation is empty
     */
    public static boolean empty(Object o) {
        return null == o || "".equals(str(o).trim());
    }

    /**
     * Determine if a given Object instance is NOT null or empty.
     *
     * @param o
     * @return false if the String specified is empty or null
     * @see #isEmpty(Object)
     */
    public static boolean isNotEmpty(Object o) {
        return !isEmpty(o);
    }

    /**
     * Alias of {@link #isNotEmpty(Object)}
     *
     * @param o
     * @return true if object str representation is not empty
     */
    public static boolean notEmpty(Object o) {
        return !isEmpty(o);
    }

    /**
     * The modifier used to indicate the comparison should
     * ignore case
     *
     * @see #isEqual(String, String, int)
     */
    public static final int IGNORECASE = 0x00001000;

    /**
     * The modifier used to indicate the comparison should
     * ignore space
     *
     * @see #isEqual(String, String, int)
     */
    public static final int IGNORESPACE = 0x00002000;

    /**
     * Check if two String is equal. This comparison is {@link #IGNORECASE case sensitive}
     * and {@link #IGNORESPACE space sensitive}
     *
     * @param s1
     * @param s2
     * @return true if the two specified Strings are equal to each other
     */
    public static boolean isEqual(String s1, String s2) {
        return isEqual(s1, s2, 0);
    }

    /**
     * Check if two String is not equal. This comparison is {@link #IGNORECASE case sensitive}
     * and {@link #IGNORESPACE space sensitive}
     *
     * @param s1
     * @param s2
     * @return true if the two specified Strings are not equal to each other
     */
    public static boolean isNotEqual(String s1, String s2) {
        return !isEqual(s1, s2, 0);
    }

    /**
     * Alias of {@link #isEqual(String, String)}
     *
     * @param s1
     * @param s2
     * @return true if s1 equals s2
     */
    @Transformer
    public static boolean eq(String s1, String s2) {
        return isEqual(s1, s2, 0);
    }

    /**
     * Alias of {@link #isNotEqual(String, String)}
     *
     * @param s1
     * @param s2
     * @return true if s1 not equals s2
     */
    @Transformer
    public static boolean ne(String s1, String s2) {
        return !isEqual(s1, s2, 0);
    }

    /**
     * Check if two Object is equal after converted into String
     *
     * @param o1
     * @param o2
     * @return true if the specified two object instance are equal after converting to String
     */
    public static boolean isEqual(Object o1, Object o2) {
        return isEqual(str(o1), str(o2));
    }

    /**
     * Check if two Object is not equal after converted into String
     *
     * @param o1
     * @param o2
     * @return true if the specified two object instance are not equal after converting to String
     */
    public static boolean isNotEqual(Object o1, Object o2) {
        return !isEqual(str(o1), str(o2));
    }

    /**
     * Alias of {@link #isEqual(Object, Object)}
     *
     * @param o1
     * @param o2
     * @return true if o1's str equals o2's str
     */
    public static boolean eq(Object o1, Object o2) {
        return isEqual(str(o1), str(o2), 0);
    }

    /**
     * Alias of {@link #isNotEqual(Object, Object)}
     *
     * @param o1
     * @param o2
     * @return true if o1's str not equals o2's str
     */
    public static boolean ne(Object o1, Object o2) {
        return isEqual(str(o1), str(o2), 0);
    }

    /**
     * Alias of {@link #isEqual(String, String, int)}
     *
     * @param s1
     * @param s2
     * @param modifier
     * @return true if o1's str equals o2's str
     */
    public static boolean eq(String s1, String s2, int modifier) {
        return isEqual(s1, s2, modifier);
    }

    /**
     * Alias of {@link #isNotEqual(String, String, int)}
     *
     * @param s1
     * @param s2
     * @param modifier
     * @return true if o1's str not equals o2's str
     */
    public static boolean ne(String s1, String s2, int modifier) {
        return !isEqual(s1, s2, modifier);
    }

    /**
     * Determine whether two string instance is equal based on
     * the modifier passed in.
     * <p/>
     * <p>
     * is 2 strings equal case insensitive?
     * <code>S.isEqual(s1, s2, S.IGNORECASE)</code>
     * </p>
     * <p/>
     * <p>
     * is 2 strings equals case and space insensitive?
     * <code>S.isEqual(s1, s2, S.IGNORECASE & S.IGNORESPACE)</code>
     * </p>
     *
     * @param s1
     * @param s2
     * @param modifier
     * @return true if s1 equals s2
     */
    public static boolean isEqual(String s1, String s2, int modifier) {
        if (s1 == s2)
            return true;
        if (null == s1) {
            return s2 == null;
        }
        if (null == s2) {
            return false;
        }
        if ((modifier & IGNORESPACE) != 0) {
            s1 = s1.trim();
            s2 = s2.trim();
        }
        if ((modifier & IGNORECASE) != 0) {
            return s1.equalsIgnoreCase(s2);
        } else {
            return s1.equals(s2);
        }
    }

    /**
     * The counter function of {@link #isEqual(String, String, int)}
     * 
     * @param s1
     * @param s2
     * @param modifier
     * @return true if s1 not equals s2
     */
    public static boolean isNotEqual(String s1, String s2, int modifier) {
        return !isEqual(s1, s2, modifier);
    }

    /**
     * Alias of {@link #toString(Object)}
     *
     * @param o
     * @return the string representation of object
     */
    public static String str(Object o) {
        return null == o ? "" : o.toString();
    }

    /**
     * Safe convert an Object to String. if the Object
     * is <code>null</code> than <code>""</code> is
     * returned
     *
     * @param o
     * @return String representation of the object
     */
    public static String toString(Object o) {
        return null == o ? "" : o.toString();
    }

    /**
     * Remove all line breaks from string representation of specified object O
     *
     * @param o
     * @return String
     */
    public static String removeAllLineBreaks(Object o) {
        String s = str(o);
        return s.replaceAll("[\n\r]+", " ");
    }

    /**
     * Return a {@link org.rythmengine.utils.RawData} type wrapper of
     * an object without any escaping.
     *
     * @param o
     * @return raw data
     */
    @Transformer
    public static RawData raw(Object o) {
        return new RawData(o);
    }

    /**
     * Return a {@link org.rythmengine.utils.RawData} type wrapper of
     * an object without escaping or if the current template exists 
     * return the escape specified by the current escape scheme of the current
     * render template
     * <p/>
     * <p>Object is {@link #toString(Object) converted to String} before escaping</p>
     *
     * @param o
     * @return escaped data
     */
    @Transformer(requireTemplate = true)
    public static RawData escape(Object o) {
        return escape(null, o);
    }

    /**
     * The template implicit argument version of {@link #escape(Object)}
     * 
     * @param template
     * @param o
     * @return escaped data
     */
    public static RawData escape(ITemplate template, Object o) {
        if (empty(o))
            return RawData.NULL;
        Escape escape;
        if (null != template) {
            escape = template.__curEscape();
        } else {
            RythmEngine engine = RythmEngine.get();
            if (null != engine) {
                escape = engine.conf().defaultCodeType().escape();
            } else {
                escape = Escape.RAW;
            }
        }
        return escape.apply(o);
    }

    /**
     * Return a {@link org.rythmengine.utils.RawData} type wrapper of
     * an object with specified escaping scheme.
     * <p/>
     * <p>
     * You can pass any type of object to specify the escaping scheme. However
     * they will in the end converted to {@link #toString(Object) converted to String}
     * and then determine which escaping to use:
     * <p/>
     * <ul>
     * <li>json: {@link #escapeJSON(Object)} </li>
     * <li>xml: {@link #escapeXML(Object)} </li>
     * <li>javascript|js: {@link #escapeJavaScript(Object)} </li>
     * <li>csv: {@link #escapeCSV(Object)} </li>
     * <li>html: {@link #escapeCSV(Object)} </li>
     * </ul>
     * </p>
     * <p/>
     * <p>Note Object instance is converted to String before escaping</p>
     *
     * @param o
     * @param escape
     * @return escaped data
     */
    @Transformer
    public static RawData escape(Object o, Object escape) {
        if (isEmpty(o))
            return RawData.NULL;
        if (o instanceof RawData)
            return (RawData) o;
        if (escape instanceof Escape)
            return ((Escape) escape).apply(o);
        if (isEmpty(escape))
            return escape(o);
        String se = escape.toString();
        if ("json".equalsIgnoreCase(se))
            return escapeJson(o);
        if ("xml".equalsIgnoreCase(se))
            return escapeXml(o);
        if ("javascript".equalsIgnoreCase(se) || "js".equalsIgnoreCase(se))
            return escapeJavaScript(o);
        if ("csv".equalsIgnoreCase(se))
            return escapeCsv(o);
        if ("html".equalsIgnoreCase(se))
            return escapeHtml(o);
        if ("raw".equalsIgnoreCase(se))
            return raw(o);
        throw new IllegalArgumentException("Unknown escape scheme: " + se);
    }

    /**
     * Return a {@link org.rythmengine.utils.RawData} type wrapper of
     * an object with HTML escaping
     * <p/>
     * <p>Object is {@link #toString(Object) converted to String} before escaping</p>
     *
     * @param o
     * @return html escaped data
     */
    @Transformer
    public static RawData escapeHTML(Object o) {
        if (null == o)
            return RawData.NULL;
        if (o instanceof RawData) {
            return (RawData) o;
        }
        return new RawData(StringEscapeUtils.escapeHtml4(o.toString()));
    }

    /**
     * Alias of {@link #escapeHTML(Object)}
     *
     * @param o
     * @return html escaped data
     */
    public static RawData escapeHtml(Object o) {
        return escapeHTML(o);
    }

    /**
     * Return a {@link org.rythmengine.utils.RawData} type wrapper of
     * an object with CSV escaping
     * <p/>
     * <p>Object is {@link #toString(Object) converted to String} before escaping</p>
     *
     * @param o
     * @return csv escaped data
     */
    @Transformer
    public static RawData escapeCSV(Object o) {
        if (null == o)
            return RawData.NULL;
        if (o instanceof RawData) {
            return (RawData) o;
        }
        //return new RawData(StringEscapeUtils.escapeCsv(o.toString()));
        // fix https://github.com/greenlaw110/Rythm/issues/155
        return Escape.CSV.apply_(o.toString());
    }

    /**
     * Alias of {@link #escapeCSV(Object)}
     *
     * @param o
     * @return CSV escaped data
     */
    public static RawData escapeCsv(Object o) {
        return escapeCSV(o);
    }

    /**
     * Return a {@link org.rythmengine.utils.RawData} type wrapper of
     * an object with JSON escaping
     * <p/>
     * <p>Object is {@link #toString(Object) converted to String} before escaping</p>
     * <p/>
     * <p>After the object get escaped, the output string is safe to put into a
     * JSON block</p>
     *
     * @param o
     * @return JSON escaped data
     */
    @Transformer
    public static RawData escapeJSON(Object o) {
        if (null == o)
            return RawData.NULL;
        if (o instanceof RawData)
            return (RawData) o;
        String s0 = o.toString();
        s0 = s0.replace("\\", "\\\\").replaceAll("[\n\r]+", "\\\\\\n").replaceAll("[ \t]+", " ").replaceAll("\"",
                "\\\\\"");
        return new RawData(s0);
    }

    /**
     * Alias of {@link #escapeCSV(Object)}
     *
     * @param o
     * @return JSON escaped data
     */
    public static RawData escapeJson(Object o) {
        return escapeJSON(o);
    }

    /**
     * Return a {@link org.rythmengine.utils.RawData} type wrapper of
     * an object with JavaScript escaping
     * <p/>
     * <p>Object is {@link #toString(Object) converted to String} before escaping</p>
     * <p/>
     * <p>After the object get escaped, the output string is safe to put inside a pair of
     * JavaScript quotation marks</p>
     *
     * @param o
     * @return JavaScript escaped data
     */
    @Transformer
    public static RawData escapeJavaScript(Object o) {
        if (null == o)
            return RawData.NULL;
        if (o instanceof RawData)
            return (RawData) o;
        return new RawData(StringEscapeUtils.escapeEcmaScript(o.toString()));
    }

    /**
     * Alias of {@link #escapeJavaScript(Object)}
     *
     * @param o
     * @return JavaScript escaped data
     */
    public static RawData escapeJavascript(Object o) {
        return escapeJavaScript(o);
    }

    /**
     * Alias of {@link #escapeJavaScript(Object)}
     *
     * @param o
     * @return JavaScript escaped data
     */
    @Transformer
    public static RawData escapeJS(Object o) {
        return escapeJavaScript(o);
    }

    /**
     * Return a {@link org.rythmengine.utils.RawData} type wrapper of
     * an object with Java escaping
     * <p/>
     * <p>Object is {@link #toString(Object) converted to String} before escaping</p>
     * <p/>
     * <p>After the object get escaped, the output string is safe to put inside a pair of
     * Java quotation marks</p>
     *
     * @param o
     * @return Java escaped data
     */
    public static RawData escapeJava(Object o) {
        if (null == o)
            return RawData.NULL;
        if (o instanceof RawData)
            return (RawData) o;
        return new RawData(StringEscapeUtils.escapeJava(o.toString()));
    }

    /**
     * Return a {@link org.rythmengine.utils.RawData} type wrapper of
     * an object with XML escaping
     * <p/>
     * <p>Object is {@link #toString(Object) converted to String} before escaping</p>
     * <p/>
     * <p>After the object get escaped, the output string is safe to put inside a XML
     * attribute
     * @param o
     * @return XML escaped data
     */
    @Transformer
    public static RawData escapeXML(Object o) {
        if (null == o)
            return RawData.NULL;
        if (o instanceof RawData)
            return (RawData) o;
        return new RawData(StringEscapeUtils.escapeXml(o.toString()));
    }

    /**
     * Alias of {@link #escapeXML(Object)}
     *
     * @param o
     * @return XML escaped data
     */
    public static RawData escapeXml(Object o) {
        if (null == o)
            return RawData.NULL;
        if (o instanceof RawData)
            return (RawData) o;
        return new RawData(StringEscapeUtils.escapeXml(o.toString()));
    }

    /**
     * Escape for regular expression
     *
     * @param o
     * @return Regex escaped data
     */
    public static RawData escapeRegex(Object o) {
        if (null == o)
            return RawData.NULL;
        if (o instanceof RawData)
            return (RawData) o;
        String s = o.toString();
        return new RawData(s.replaceAll("([\\/\\*\\{\\}\\<\\>\\-\\\\\\!])", "\\\\$1"));
    }

    /**
     * Strip the prefix and suffix from an object's String representation and
     * return the result
     * <p/>
     * <p>For example: </p>
     * <p/>
     * <pre><code>Object o = "xxBByy";
     * String s = S.strip(o, "xx", "yy")</code></pre>
     * <p/>
     * <p>At the end above code, <code>s</code> should be "BB"</p>
     *
     * @param o
     * @param prefix
     * @param suffix
     * @return the String result
     */
    public static String strip(Object o, String prefix, String suffix) {
        if (null == o)
            return "";
        String s = o.toString();
        s = s.trim();
        if (s.startsWith(prefix))
            s = s.substring(prefix.length());
        if (s.endsWith(suffix))
            s = s.substring(0, s.length() - suffix.length());
        return s;
    }

    /**
     * Strip the brace from an object's string representation and return the result
     *
     * @param o
     * @return the string result
     */
    public static String stripBrace(Object o) {
        return strip(o, "(", ")");
    }

    /**
     * Strip the quotation mark from an object's string representation and return the result
     *
     * @param o
     * @return the String result
     */
    public static String stripQuotation(Object o) {
        return strip(strip(o, "\"", "\""), "'", "'");
    }

    /**
     * Strip off both brace and quotation
     *
     * @param o
     * @return the string result
     */
    public static String stripBraceAndQuotation(Object o) {
        if (null == o)
            return "";
        String s = stripBrace(o);
        s = stripQuotation(s);
        return s;
    }

    /**
     * Shrink spaces in an object's string representation by merge multiple
     * spaces, tabs into one space, and multiple line breaks into one line break
     *
     * @param o
     * @return the string result
     */
    public static String shrinkSpace(Object o) {
        if (null == o)
            return "";
        return o.toString().replaceAll("[\r\n]+", "\n").replaceAll("[ \\t\\x0B\\f]+", " ");
    }

    private static final Range<Integer> digits = F.R(0x30, 0x3a);
    private static final Range<Integer> uppers = F.R(0x41, 0x5b);
    private static final Range<Integer> lowers = F.R(0x61, 0x7b);

    public static boolean isDigitsOrAlphabetic(char c) {
        int i = (int) c;
        return digits.include(i) || uppers.include(i) || lowers.include(i);
    }

    /**
     * Capitalize the first character of every word of the specified object's
     * string representation. Words are separated by space
     *
     * @param o 
     * @return the string result
     */
    @Transformer
    public static String capitalizeWords(Object o) {
        if (null == o)
            return "";
        String source = o.toString();
        char prevc = ' '; // first char of source is capitalized
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < source.length(); i++) {
            char c = source.charAt(i);
            if (c != ' ' && !isDigitsOrAlphabetic(prevc)) {
                sb.append(Character.toUpperCase(c));
            } else {
                sb.append(c);
            }
            prevc = c;
        }
        return sb.toString();
    }

    /**
     * Replace accent character (usually found in European languages) of the String representation of a
     * give object to non-accent char.
     *
     * @param o
     * @return the string result
     */
    @Transformer
    public static String noAccents(Object o) {
        if (null == o)
            return "";
        String string = o.toString();
        return Normalizer.normalize(string, Normalizer.Form.NFKC).replaceAll("[?]", "a")
                .replaceAll("[?]", "c").replaceAll("[?]", "d").replaceAll("[]", "e")
                .replaceAll("[]", "f").replaceAll("[?]", "g").replaceAll("[]", "h")
                .replaceAll("[]", "i").replaceAll("[]", "j").replaceAll("[]", "k")
                .replaceAll("[]", "l").replaceAll("[]", "n")
                .replaceAll("[??]", "o").replaceAll("[]", "p").replaceAll("[]", "r")
                .replaceAll("[?]", "s").replaceAll("[]", "t")
                .replaceAll("[]", "u").replaceAll("[]", "w").replaceAll("[]", "y")
                .replaceAll("[]", "z").replaceAll("[]", "ae").replaceAll("[?]", "A")
                .replaceAll("[]", "C").replaceAll("[??]", "D").replaceAll("[]", "E")
                .replaceAll("[]", "G").replaceAll("[]", "H").replaceAll("[??]", "I")
                .replaceAll("[]", "J").replaceAll("[]", "K").replaceAll("[?]", "L")
                .replaceAll("[]", "N").replaceAll("[?]", "O").replaceAll("[]", "R")
                .replaceAll("[]", "S").replaceAll("[]", "U").replaceAll("[]", "W")
                .replaceAll("[?]", "Y").replaceAll("[]", "Z").replaceAll("[]", "ss");
    }

    /**
     * Make the first character be lowercase of the given object's string representation
     *
     * @param o
     * @return the string result
     */
    @Transformer
    public static String lowerFirst(Object o) {
        if (null == o)
            return "";
        String string = o.toString();
        if (string.length() == 0) {
            return string;
        }
        return ("" + string.charAt(0)).toLowerCase() + string.substring(1);
    }

    /**
     * Make the first character be uppercase of the given object's string representation
     *
     * @param o
     * @return the string result
     */
    @Transformer
    public static String capFirst(Object o) {
        if (null == o)
            return "";
        String string = o.toString();
        if (string.length() == 0) {
            return string;
        }
        return ("" + string.charAt(0)).toUpperCase() + string.substring(1);
    }

    /**
     * Turn an object's String representation into Camel Case
     *
     * @param obj
     * @return the string result
     */
    @Transformer
    public static String camelCase(Object obj) {
        if (null == obj)
            return "";
        String string = obj.toString();
        //string = noAccents(string);
        //string = string.replaceAll("[^\\w ]", "");
        StringBuilder result = new StringBuilder(string.length());
        String[] sa = string.split(" ");
        int l = sa.length;
        for (int i = 0; i < l; ++i) {
            if (i > 0)
                result.append(" ");
            for (String s : sa[i].split("_")) {
                result.append(capFirst(s));
            }
        }
        return result.toString();
    }

    /**
     * Bridge String.split() method to Object
     * @param o
     * @param sep
     * @return string array separated by sep
     */
    @Transformer
    public static String[] divide(Object o, String sep) {
        return str(o).split(sep);
    }

    /**
     * Bridge String.toUpperCase() method to Object
     * @param o
     * @return the lower case of string presentation of o
     */
    @Transformer
    public static String lowerCase(Object o) {
        return str(o).toLowerCase();
    }

    /**
     * Bridge String.toUpperCase() method to Object
     * @param o
     * @return the upper case of string presentation of o
     */
    @Transformer
    public static String upperCase(Object o) {
        return str(o).toUpperCase();
    }

    /**
     * get length of specified object
     * 
     * <ul>
     * <li>If o is a Collection or Map, then return size of it</li>
     * <li>If o is an array, then return length of it</li>
     * <li>Otherwise return length() of String representation of the object</li>
     * </ul>
     * @param o
     * @return length
     */
    @Transformer
    public static int len(Object o) {
        if (o instanceof Collection) {
            return ((Collection) o).size();
        } else if (o instanceof Map) {
            return ((Map) o).size();
        } else if (o.getClass().isArray()) {
            return Array.getLength(o);
        } else {
            return str(o).length();
        }
    }

    /**
     * Change line break in the data string into <tt><br/></tt>
     * @param data
     * @return raw data of transformed result
     */
    @Transformer
    public static RawData nl2br(RawData data) {
        return new RawData(data.toString().replace("\n", "<br/>"));
    }

    /**
     * Change line break in the data string into <tt><br/></tt>
     * @param data
     * @return raw data of transformed result
     */
    public static RawData nl2br(Object data) {
        return new RawData(StringEscapeUtils.escapeHtml4(str(data)).replace("\n", "<br/>"));
    }

    /**
     * encode using utf-8
     * 
     * @param data
     * @return encoded 
     */
    @Transformer
    public static String urlEncode(Object data) {
        if (null == data)
            return "";
        String entity = data.toString();
        try {
            String encoding = "utf-8";
            return URLEncoder.encode(entity, encoding);
        } catch (UnsupportedEncodingException e) {
            Logger.error(e, entity);
        }
        return entity;
    }

    /**
     * Format a number using default pattern, language and locale
     * @param number
     * @return the formatted string
     */
    public static String format(Number number) {
        return format(null, number, null, null);
    }

    /**
     * Format number with specified template
     * @param template
     * @param number
     * @return the formatted string
     */
    public static String format(ITemplate template, Number number) {
        return format(template, number, null, null);
    }

    /**
     * Format the number with specified pattern, language and locale
     * @param number
     * @param pattern
     * @param locale
     * @return the formatted String
     * @see DecimalFormatSymbols
     */
    @Transformer(requireTemplate = true)
    public static String format(Number number, String pattern, Locale locale) {
        return format(null, number, pattern, locale);
    }

    /**
     * Format the number with specified template, pattern, language and locale
     * @param number
     * @param pattern
     * @param locale
     * @return the formatted String
     * @see DecimalFormatSymbols
     */
    public static String format(ITemplate template, Number number, String pattern, Locale locale) {
        if (null == number)
            number = 0;
        if (null == locale) {
            locale = I18N.locale(template);
        }

        NumberFormat nf;
        if (null == pattern)
            nf = NumberFormat.getNumberInstance(locale);
        else {
            DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
            nf = new DecimalFormat(pattern, symbols);
        }

        return nf.format(number);
    }

    /**
     * Format a number with specified pattern
     * 
     * @param number
     * @param pattern
     * @return formatted String
     */
    @Transformer(requireTemplate = true)
    public static String format(Number number, String pattern) {
        return format(null, number, pattern, null);
    }

    /**
     * Format a number with specified engine, pattern
     * 
     * @param number
     * @param pattern
     * @return formatted String
     */
    public static String format(ITemplate template, Number number, String pattern) {
        return format(template, number, pattern, null);
    }

    /**
     * Format general object: for the sake of dynamic expr evaluation
     * @param o
     * @return
     */
    public static String format(Object o) {
        if (null == o)
            return "";
        return format(null, o, null, null, null);
    }

    /**
     * Format a date with engine's default format corresponding
     * to the engine's locale configured
     * 
     * @param date
     * @return the formatted String
     */
    @Transformer(requireTemplate = true)
    public static String format(Date date) {
        return format(date, null, null, null);
    }

    /**
     * Generalize format parameter for the sake of dynamic expr evaluation
     * @param template
     * @param o
     * @return
     */
    public static String format(ITemplate template, Object o) {
        if (null == o)
            return "";
        return format(template, o, null, null, null);
    }

    /**
     * Format a date with specified engine's default format corresponding
     * to the engine's locale configured
     * 
     * @param date
     * @return the formatted String
     */
    public static String format(ITemplate template, Date date) {
        return format(template, date, null, null, null);
    }

    /**
     * Format a date with specified pattern
     * 
     * @param date
     * @param pattern
     * @return formated string
     */
    @Transformer(requireTemplate = true)
    public static String format(Date date, String pattern) {
        return format(date, pattern, null, null);
    }

    /**
     * Generalize format parameter for the sake of dynamic evaluation
     * @param o
     * @param pattern
     * @return
     */
    public static String format(Object o, String pattern) {
        if (null == o)
            return "";
        return format(null, o, pattern, null, null);
    }

    /**
     * Format a date with specified pattern
     * 
     * @param template
     * @param date
     * @param pattern
     * @return formated string
     */
    public static String format(ITemplate template, Date date, String pattern) {
        return format(template, date, pattern, null, null);
    }

    /**
     * Generalize format parameter for the sake of dynamic evaluation
     * @param o
     * @param pattern
     * @return
     */
    public static String format(ITemplate template, Object o, String pattern) {
        if (null == o)
            return "";
        return format(template, o, pattern, null, null);
    }

    /**
     * Transformer. Format a date with specified pattern, language and locale
     * @param date
     * @param pattern
     * @param locale
     * @return the formatted String
     */
    @Transformer(requireTemplate = true)
    public static String format(Date date, String pattern, Locale locale) {
        return format(date, pattern, locale, null);
    }

    /**
     * Generalize format parameter for the sake of dynamic evaluation
     * @param o
     * @param pattern
     * @return
     */
    public static String format(Object o, String pattern, Locale locale) {
        if (null == o)
            return "";
        return format(null, o, pattern, locale, null);
    }

    /**
     * See {@link #format(org.rythmengine.template.ITemplate, java.util.Date, String, java.util.Locale, String)}
     * @param template
     * @param date
     * @param pattern
     * @param locale
     * @return formatted date string
     */
    public static String format(ITemplate template, Date date, String pattern, Locale locale) {
        return format(template, date, pattern, locale, null);
    }

    /**
     * Generalize format parameter for the sake of dynamic evaluation
     * @param o
     * @param pattern
     * @return
     */
    public static String format(ITemplate template, Object o, String pattern, Locale locale) {
        if (null == o)
            return "";
        return format(template, o, pattern, locale, null);
    }

    /**
     * Transformer. Format a date with specified pattern, lang, locale and timezone.
     * 
     * @param date
     * @param pattern
     * @param locale
     * @param timezone
     * @return the formatted String
     * @see SimpleDateFormat
     */
    @Transformer(requireTemplate = true)
    public static String format(Date date, String pattern, Locale locale, String timezone) {
        return format(null, date, pattern, locale, timezone);
    }

    /**
     * Generalize format parameter for the sake of dynamic evaluation
     * @param o
     * @param pattern
     * @return
     */
    public static String format(Object o, String pattern, Locale locale, String timezone) {
        if (null == o)
            return "";
        return format(null, o, pattern, locale, timezone);
    }

    /**
     * Format a date with specified pattern, lang, locale and timezone. The locale
     * comes from the engine instance specified
     * 
     * @param template
     * @param date
     * @param pattern
     * @param locale
     * @param timezone
     * @return format result
     */
    public static String format(ITemplate template, Date date, String pattern, Locale locale, String timezone) {
        if (null == date)
            date = new Date(0);
        if (null == locale) {
            locale = I18N.locale(template);
        }

        DateFormat df;
        if (null != pattern)
            df = new SimpleDateFormat(pattern, locale);
        else
            df = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);

        if (null != timezone)
            df.setTimeZone(TimeZone.getTimeZone(timezone));

        return df.format(date);
    }

    /**
     * Generalize format parameter for the sake of dynamic evaluation
     * @param o
     * @param pattern
     * @return
     */
    public static String format(ITemplate template, Object o, String pattern, Locale locale, String timezone) {
        if (null == o)
            return "";
        if (o instanceof Date)
            return format(template, (Date) o, pattern, locale, timezone);
        if (o instanceof Number)
            return format(template, (Number) o, pattern, locale);
        if (null == locale) {
            locale = I18N.locale(template);
        }
        RythmEngine engine = null == template ? RythmEngine.get() : template.__engine();
        if (null == engine)
            return o.toString();
        for (IFormatter fmt : engine.extensionManager().formatters()) {
            String s = fmt.format(o, pattern, locale, timezone);
            if (null != s) {
                return s;
            }
        }
        return o.toString();
    }

    /**
     * Format size (e.g. disk space in bytes) into human readable style
     * <ul>
     * <li>When size is smaller than <code>1024L</code>, return size + <code>B</code></li>
     * <li>When size is smaller than <code>1024L ^ 2</code>, return size/1024L + <code>KB</code></li>
     * <li>When size is smaller than <code>1024L ^ 3</code>, return size/1048576L + <code>MB</code></li>
     * <li>When size is smaller than <code>1024L ^ 4</code>, return size/1073741824L + <code>GB</code></li>
     * </ul>
     * 
     * <p>The method accept any data type. When <code>null</code> is found then 
     * <code>NullPointerException</code> will be thrown out; if an <code>Number</code>
     * is passed in, it will be type cast to <code>Long</code>; otherwise 
     * a <code>Long.valueOf(data.toString())</code> is used to find out
     * the number</p>
     * 
     * @param data
     * @return formatted string result
     */
    @Transformer
    public static String formatSize(Object data) {
        if (null == data)
            throw new NullPointerException();
        Long bytes;
        if (data instanceof Number) {
            bytes = (Long) data;
        } else {
            bytes = Long.valueOf(data.toString());
        }
        if (bytes < 1024L) {
            return bytes + " B";
        }
        if (bytes < 1048576L) {
            return bytes / 1024L + "KB";
        }
        if (bytes < 1073741824L) {
            return bytes / 1048576L + "MB";
        }
        return bytes / 1073741824L + "GB";
    }

    /**
     * Transformer method. Format give data into currency
     * 
     * @param data
     * @return the currency
     * @see {@link #formatCurrency(org.rythmengine.template.ITemplate, Object, String, java.util.Locale)}
     */
    @Transformer(requireTemplate = true)
    public static String formatCurrency(Object data) {
        return formatCurrency(null, data, null, null);
    }

    /**
     * See {@link #formatCurrency(org.rythmengine.template.ITemplate, Object)}
     * 
     * @param template
     * @param data
     * @return the currency string
     */
    public static String formatCurrency(ITemplate template, Object data) {
        return formatCurrency(template, data, null, null);
    }

    /**
     * Transformer method. Format currency using specified currency code
     * 
     * @param data
     * @param currencyCode
     * @return the currency
     * @see {@link #formatCurrency(org.rythmengine.template.ITemplate, Object, String, java.util.Locale)}
     */
    @Transformer(requireTemplate = true)
    public static String formatCurrency(Object data, String currencyCode) {
        return formatCurrency(null, data, currencyCode, null);
    }

    /**
     * See {@link #formatCurrency(org.rythmengine.template.ITemplate, Object, String, java.util.Locale)}
     * 
     * @param template
     * @param data
     * @param currencyCode
     * @return the currency string
     */
    public static String formatCurrency(ITemplate template, Object data, String currencyCode) {
        return formatCurrency(template, data, currencyCode, null);
    }

    /**
     * See {@link #formatCurrency(org.rythmengine.template.ITemplate, Object, String, java.util.Locale)}
     * 
     * @param data
     * @param currencyCode
     * @param locale
     * @return the currency string
     */
    public static String formatCurrency(Object data, String currencyCode, Locale locale) {
        return formatCurrency(null, data, currencyCode, locale);
    }

    /**
     * Format give data into currency using locale info from the engine specified
     * 
     * <p>The method accept any data type. When <code>null</code> is found then 
     * <code>NullPointerException</code> will be thrown out; if an <code>Number</code>
     * is passed in, it will be type cast to <code>Number</code>; otherwise 
     * a <code>Double.valueOf(data.toString())</code> is used to find out
     * the number</p>
     *
     * @param template
     * @param data
     * @param currencyCode
     * @param locale
     * @return the currency
     */
    public static String formatCurrency(ITemplate template, Object data, String currencyCode, Locale locale) {
        if (null == data)
            throw new NullPointerException();
        Number number;
        if (data instanceof Number) {
            number = (Number) data;
        } else {
            number = Double.parseDouble(data.toString());
        }
        if (null == locale)
            locale = I18N.locale(template);
        Currency currency = null == currencyCode ? Currency.getInstance(locale)
                : Currency.getInstance(currencyCode);
        NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
        numberFormat.setCurrency(currency);
        numberFormat.setMaximumFractionDigits(currency.getDefaultFractionDigits());
        String s = numberFormat.format(number);
        s = s.replace(currency.getCurrencyCode(), currency.getSymbol(locale));
        return s;
    }

    private static String getMessage(ITemplate template, ResourceBundle bundle, String key, Locale locale,
            Object... args) {
        if (null == locale)
            locale = I18N.locale(template);
        String s = key;
        try {
            s = bundle.getString(key);
        } catch (RuntimeException e) {
            //ignore it
        }
        int argLen = args.length;
        if (argLen > 0) {
            MessageFormat fmt = new MessageFormat(s, locale);
            Object[] argsResolved = new Object[argLen];
            for (int i = 0; i < argLen; ++i) {
                Object arg = args[i];
                if (arg instanceof String) {
                    arg = S.i18n(template, (String) arg);
                }
                argsResolved[i] = arg;
            }
            return fmt.format(argsResolved);
        } else {
            return s;
        }
    }

    /**
     * <p>Return i18n message of a given key and args, use the locale info from the template specified. 
     * if <tt>null</tt> template instance passed in then it will try to guess from the current engine via
     * {@link org.rythmengine.RythmEngine#get()}</p>
     * 
     * @param template
     * @param key
     * @param args the format arguments. If the first argument is of type Locale then it will be used to specify
     * the locale of the processing, and the rest elements are used as format arguments
     * @return the i18n message
     */
    public static String i18n(ITemplate template, String key, Object... args) {
        if (null != template) {
            II18nMessageResolver resolver = template.__engine().conf().i18nMessageResolver();
            if (null != resolver && II18nMessageResolver.DefaultImpl.INSTANCE != resolver) {
                return resolver.getMessage(template, key, args);
            }
        }
        boolean useFormat = args.length > 0;
        Locale locale = null;
        if (useFormat) {
            // check if the first arg is locale
            Object arg0 = args[0];
            if (arg0 instanceof Locale) {
                locale = (Locale) arg0;
                Object[] args0 = new Object[args.length - 1];
                System.arraycopy(args, 1, args0, 0, args.length - 1);
                args = args0;
                useFormat = args.length > 0;
            }
        }
        if (null == locale)
            locale = I18N.locale(template);
        RythmEngine engine = null == template ? RythmEngine.get() : template.__engine();
        String cacheKey = null;
        if (null != template && null != locale) {
            cacheKey = CacheKey.i18nMsg(template, key, useFormat, locale);
            Object cached = engine.cached(cacheKey);
            if (S.notEmpty(cached))
                return S.str(cached);
        }
        ResourceBundle bundle;
        for (String msgSrc : RythmConfiguration.get().messageSources()) {
            bundle = I18N.bundle(template, msgSrc, locale);
            if (null != bundle) {
                String data = getMessage(template, bundle, key, locale, args);
                if (null != data) {
                    if (null != engine) {
                        engine.cache(cacheKey, data, -1);
                    }
                    return data;
                }
            }
        }
        return key;
    }

    /**
     * Transformer method. Return i18n message of a given key and args.
     * 
     * @param key
     * @param args
     * @return the i18n message
     */
    @Transformer(requireTemplate = true)
    public static String i18n(String key, Object... args) {
        return i18n(null, key, args);
    }

    @Transformer(requireTemplate = true)
    public static String i18n(String key) {
        return i18n(null, key, new Object[0]);
    }

    /**
     * Generate random string.
     *
     * The generated string is safe to be used as filename
     * @param len
     * @return a random string with specified length
     */
    public static String random(int len) {
        final char[] chars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '$', '#', '^', '&', '_', 'a', 'b',
                'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
                'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '~', '!', '@' };

        final int max = chars.length;
        Random r = new Random();
        StringBuffer sb = new StringBuffer(len);
        while (len-- > 0) {
            int i = r.nextInt(max);
            sb.append(chars[i]);
        }
        return sb.toString();
    }

    /**
     * Return a random string with 8 chars
     * @return the string generated
     */
    public static String random() {
        return random(8);
    }

    /**
     * Join items in an {@link java.lang.Iterable iterable} with ","
     * 
     * @param itr
     * @return joined String
     */
    @Transformer
    public static String join(Iterable itr) {
        return join(",", itr);
    }

    /**
     * Join an {@link java.lang.Iterable iterable} with separator
     * 
     * @param itr
     * @param sep
     * @return the String joined
     */
    @Transformer(lastParam = true)
    public static String join(String sep, Iterable itr) {
        StringBuilder sb = new StringBuilder();
        Iterator i = itr.iterator();
        if (!i.hasNext())
            return "";
        sb.append(i.next());
        while (i.hasNext()) {
            sb.append(sep);
            sb.append(i.next());
        }
        return sb.toString();
    }

    /**
     * Join an {@link java.lang.Iterable iterable} with a char separator
     * @param itr
     * @param sep
     * @return joined string
     */
    public static String join(char sep, Iterable itr) {
        return join(String.valueOf(sep), itr);
    }

    public static String join(Character[] a) {
        return join(",", a);
    }

    public static String join(String sep, Character[] a) {
        int len = a.length;
        if (len == 0)
            return "";
        StringBuilder sb = new StringBuilder(String.valueOf(a[0]));
        for (int i = 1; i < len; ++i) {
            sb.append(sep).append(a[i]);
        }
        return sb.toString();
    }

    public static String join(char sep, Character[] a) {
        return join(String.valueOf(sep), a);
    }

    public static String join(Integer[] a) {
        return join(",", a);
    }

    public static String join(String sep, Integer[] a) {
        int len = a.length;
        if (len == 0)
            return "";
        StringBuilder sb = new StringBuilder(String.valueOf(a[0]));
        for (int i = 1; i < len; ++i) {
            sb.append(sep).append(a[i]);
        }
        return sb.toString();
    }

    public static String join(char sep, Integer[] a) {
        return join(String.valueOf(sep), a);
    }

    public static String join(Long[] a) {
        return join(",", a);
    }

    public static String join(String sep, Long[] a) {
        int len = a.length;
        if (len == 0)
            return "";
        StringBuilder sb = new StringBuilder(String.valueOf(a[0]));
        for (int i = 1; i < len; ++i) {
            sb.append(sep).append(a[i]);
        }
        return sb.toString();
    }

    public static String join(char sep, Long[] a) {
        return join(String.valueOf(sep), a);
    }

    public static String join(Float[] a) {
        return join(",", a);
    }

    public static String join(String sep, Float[] a) {
        int len = a.length;
        if (len == 0)
            return "";
        StringBuilder sb = new StringBuilder(String.valueOf(a[0]));
        for (int i = 1; i < len; ++i) {
            sb.append(sep).append(a[i]);
        }
        return sb.toString();
    }

    public static String join(char sep, Float[] a) {
        return join(String.valueOf(sep), a);
    }

    public static String join(Double[] a) {
        return join(",", a);
    }

    public static String join(String sep, Double[] a) {
        int len = a.length;
        if (len == 0)
            return "";
        StringBuilder sb = new StringBuilder(String.valueOf(a[0]));
        for (int i = 1; i < len; ++i) {
            sb.append(sep).append(a[i]);
        }
        return sb.toString();
    }

    public static String join(char sep, Double[] a) {
        return join(String.valueOf(sep), a);
    }

}