基于函数的表达式消息渲染器

时间:2011-05-30 02:54:18

标签: java message renderer

我正在做一个简单的MessageRenderer。

它的规格:

  • 根据上下文呈现消息(它是包含所有键/值对参数的地图)
  • 支持简单渲染,例如:您的用户名是<<用户名>>。假设上下文中的用户名是barcelona,结果将是您的用户名是Barcelona。
  • 支持类似函数的对象。示例:当前时间<< now()>>,now():是一个返回当前日期时间字符串的对象。结果将是:当前时间是2011-05-30
  • 函数的每个参数也可以模板化:当前时间<<< now(<<< date_format>>)>> 。此模板返回当前日期时间的字符串,格式为从Context中检索的键“date_format”的值。假设上下文中的date_format是dd / MM / yyyy,结果将是:当前时间是30/05/2011
  • 函数的每个参数也可以用不同的方法调用进行模板化:时间是<< now_locale(<<< getLocale()>>)。假设getLocale()是一个将返回一个语言环境的函数对象是en_US,结果将是:时间是2011/05/30 11:20:34 PM
  • 模板可以嵌套。示例:您的用户名是<< <<用户名>> >取代。这意味着,Key用户名的值为param1,Key param1的值为barcelona,因此最终结果为:您的用户名是Barcelona。

我的课程和界面:

RenderContext.java

public interface RenderContext {
    public String getParameter(String key);
}

MessageRenderer.java

public interface MessageRenderer {
      public String render(String s, RenderContext... context);    
}

MethodExpressionEvaluator.java

// Using this class to implements the method evaluation, such as now(), now_locale()
public interface MethodExpressionEvaluator {
      public String evaluate(String[] methodParams, RenderContext... context);
}

AbstractMessageRenderer.java

public abstract class AbstractMessageRenderer implements MessageRenderer {

public static final String DEFAULT_NULL = "###";
public static final String PLACEHOLDER_START_TOKEN = "<<";
public static final String PLACEHOLDER_END_TOKEN = ">>";

protected int lenPlaceholderStartToken = 0;
protected int lenPlaceholderEndToken = 0;
protected String nullToken;
protected String placeholderStartToken;
protected String placeholderEndToken;
protected boolean escape = true;

public AbstractMessageRenderer() {
    placeholderStartToken = PLACEHOLDER_START_TOKEN;
    placeholderEndToken = PLACEHOLDER_END_TOKEN;
    lenPlaceholderStartToken = placeholderStartToken.length();
    lenPlaceholderEndToken = placeholderEndToken.length();
    nullToken = DEFAULT_NULL;
}

public String getNullToken() {
    return nullToken;
}

public void setNullToken(String defaultNull) {
    this.nullToken = defaultNull;
}

public String getPlaceholderStartToken() {
    return placeholderStartToken;
}

public void setPlaceholderStartToken(String placeholderStartToken) {
    this.placeholderStartToken = placeholderStartToken;
    lenPlaceholderStartToken = placeholderStartToken.length();
}

public String getPlaceholderEndToken() {
    return placeholderEndToken;
}

public void setPlaceholderEndToken(String placeholderEndToken) {
    this.placeholderEndToken = placeholderEndToken;
    lenPlaceholderEndToken = placeholderEndToken.length();
}

public boolean isEscape() {
    return escape;
}

public boolean getEscape() {
    return escape;
}

public void setEscape(boolean escape) {
    this.escape = escape;
}

public String getParam(String key, RenderContext... context) {

    if(context != null)
    {
        for(RenderContext param:context)
        {
            if(param != null)
            {
                String value = param.getParameter(key);

                if(!StringUtil.isEmpty(value))
                {
                    return value;
                }
            }
        }
    }

    return nullToken;
}

public String render(String s, RenderContext... context) {

    // handle trivial cases of empty template or no placeholders
    if (s == null)
    {
        Log4j.app.debug("Message is null in template. Cannot render null message.");
        return nullToken;
    }

    if (context == null)
    {
        Log4j.app.debug("RenderContext is null. Cannot render message with null RenderContext.");
        return nullToken;
    }

    if (s.indexOf(placeholderStartToken) < 0)
    {
        return s;
    }

    String msg = nullToken;

    try
    {
        // private int renderTemplate(Renderable r, String src, StringBuffer dst, String nil, int i, String[] marks, StringBuffer end,boolean escapes)
        msg = doRender(s, context);
    }
    catch (Exception e)
    {
        Log4j.app.error("Exception in rendering template: " + e.getMessage(), e);
        return nullToken;
    }

       return msg;
    }

protected abstract String doRender(String s, RenderContext... context);

}

MethodExpressionRenderer.java

public class MethodExpressionRenderer extends AbstractMessageRenderer {

    private boolean inSingleQuote = false;
    private boolean inDoubleQuote=false;
    private int placeholders;
    private Stack<String> methodStack;    
    private String[] endTokens;
    private String marker;
    private List<String> methodParams;
    private String prefix = "&";

    public MethodExpressionRenderer() {
        super();
        methodStack = new Stack<String>();
        marker = ",";
        endTokens = new String[] { placeholderEndToken, marker, "(", ")" };
        methodParams = new ArrayList<String>();
    }

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getMarker() {
        return marker;
    }

    public void setMarker(String marker) {
        this.marker = marker;
        endTokens = new String[] { placeholderEndToken, marker };
    }

    @Override
    public void setPlaceholderEndToken(String placeholderEndToken) {
        super.setPlaceholderEndToken(placeholderEndToken);
        endTokens = new String[] { placeholderEndToken, marker };
    }

    protected String doRender(String s, RenderContext... context) {

        StringBuffer sb = new StringBuffer();

        try
        {            
            renderTemplate(s, sb, nullToken, 0, endTokens, null, context);
        } 
        catch (Exception e)
        {
            Log4j.app.error("Exception in rendering method expression message emplate: " + e.getMessage(), e);
            return nullToken;
        }

        return sb.toString();
    }

    private int renderTemplate(String src, StringBuffer dst, String nil, int i, String[] marks, StringBuffer end, RenderContext... context) {

        int len = src.length();

        while (i < len)
        {
            char c = src.charAt(i);

            if (escape)
            {
                if (c=='\\')
                {
                    i++;
                    char ch = src.charAt(i);

                    if(inSingleQuote)
                    {
                        if(ch=='\'')
                        {
                            inSingleQuote=false;
                        }
                    }
                    else if(inDoubleQuote)
                    {
                        if(ch=='"')
                        {
                            inDoubleQuote=false;
                        }
                    }
                    else
                    {
                        if(ch=='\'')
                        {
                            inSingleQuote=true;
                        }
                        else if(ch=='"')
                        {
                            inDoubleQuote=true;
                        }
                    }

                    dst.append(ch);
                    i++;
                    continue;
                }
            }

            if(inSingleQuote)
            {
                if(c=='\'')
                {
                    inSingleQuote=false;
                }
            }
            else if(inDoubleQuote)
            {
                if(c=='"')
                {
                    inDoubleQuote=false;
                }
            }
            else
            {
                if(c=='\'')
                {
                    inSingleQuote=true;
                }
                else if(c=='"')
                {
                    inDoubleQuote=true;
                }
            }

            // check for end marker
            if (marks != null && !inSingleQuote && !inDoubleQuote)
            {
                for (int m = 0; m < marks.length; m++)
                {
                    // If one of markers found
                    if (src.regionMatches(i, marks[m], 0, marks[m].length()))
                    {
                        // return marker if required
                        if (end != null)
                        {
                            end.append(marks[m]);
                        }

                        return i+marks[m].length();
                    }
                }
            }

            // check for start of placeholder
            if (src.regionMatches(i, placeholderStartToken, i, lenPlaceholderStartToken))
            {
                synchronized(this)
                {
                    ++placeholders;
                }

                i = renderPlaceholder(src, dst, nil, i, new ArrayList<String>(), context);
                continue;
            }

            // just add plain character

            if(c != '\'' && c!= '"')
            {
                dst.append(c);
            }

            i++;
        }

        return i;
    }

    private int renderPlaceholder(String src, StringBuffer dst, String nil, int i, List<String> params, RenderContext... context){

        StringBuffer token = new StringBuffer(); // placeholder token
        StringBuffer end = new StringBuffer();  // placeholder end marker
        String value;

        i = renderTemplate(src, token, nil, i+lenPlaceholderStartToken, endTokens, end);

        String sToken = token.toString().trim();        
        String sEnd = end.toString().trim();
        boolean isFunction = sEnd.equals("(");

        // This is method name
        if(isFunction && placeholders > methodStack.size())
        {   // Method
            synchronized(this)
            {
                methodStack.push(sToken); // put method into stack
            }
        }
        else if(!isFunction && (methodStack.size()==0) && sEnd.equals(placeholderEndToken)) // Single template param such as <<param>>
        {
            value = getParam(sToken, context);

            if(value != null)
            {
                if(value.trim().startsWith(placeholderStartToken))
                {
                    value = render(src, context);                    
                }

                dst.append(value);
                return i;
            }
        }

       // TODO: Process method parameters to invoke
       //.... ?????????

        // Found end method token ')'
        // Pop method out of stack to invoke
        if ( (methodStack.size() >0) && (sEnd.length() == 0 || sEnd.equals(")")))
        {
            String method = null;

            synchronized(this)
            {
                // Pop method out of stack to invoke
                method = methodStack.pop();
                --placeholders;
                dst.append(invokeMethodEvaluator(method, methodParams.toArray(new String[0]), context));
                methodParams.clear();
            }
        }

        return i;
    }

    // Currently this method just implement to test so it just printout the method name 
    // and its parameter
    // We can register MethodExpressionEvaluator to process
    protected String invokeMethodEvaluator(String method, String[] params, RenderContext... context){
        StringBuffer result = new StringBuffer();

        result.append("[ ")
              .append(method)
              .append(" ( ");

        if(params != null)
        {
            for(int i=0; i<params.length; i++)
            {
                result.append(params[i]);

                if(i != params.length-1)
                {
                    result.append(" , ");
                }
            }
        }

        result.append(" ) ")
              .append(" ] ");

        return result.toString();
    }

}

我们可以轻松地向渲染器注册更多方法以进行调用。每个方法都是一个对象,可以重用。但我有问题如何解决嵌套的方法参数。任何人都可以给我一个建议,我们如何处理方法参数的嵌套模板来调用???这条线有TODO。我的代码会以正确的方式进行吗?

2 个答案:

答案 0 :(得分:0)

评估<< count( << getTransId() >> ) >>之类的内容时,您可以:

  • 在解析时执行直接评估,并将每个函数推送到堆栈,这样一旦您评估getTransId(),就会弹出堆栈并使用返回值(来自堆栈)作为{的参数{1}}或
  • 您可以构建一个解析树来表示生成的所有函数调用,然后在构建之后评估您的解析树。 (构建树可能不会给你任何东西;因为你正在编写一个模板引擎,你可能无法执行高级树操作'优化'。)

我非常喜欢的一本优秀小书是帕尔Language Implementation Patterns。他介绍了构建简单到复杂的语言,并在某种程度上涵盖了这样的决策。 (是的,他总是使用ANTLR解析器生成器,但是你的代码看起来像你对手工生成的解析器很熟悉,不同的工具不会让你分心。)

答案 1 :(得分:0)

我发现了这个错误并修复了它。

这是我的新来源:

// AbstractMethodExpressionRenderer.java

public class AbstractMethodExpressionRenderer extends AbstractMessageRenderer {

    private boolean inSingleQuote = false;
    private boolean inDoubleQuote=false;
    private Stack<MethodExpressionDescriptor> functionStack;
    private String[] endTokens;
    private String marker;
    private String prefix = "~";

    public AbstractMethodExpressionRenderer() {
        super();        
        functionStack = new Stack<MethodExpressionDescriptor>();
        marker = ",";
        endTokens = new String[] { placeholderEndToken, "(", ")", };
    }

    private class MethodExpressionDescriptor {
        public List<String> params;
        public String function;

        public MethodExpressionDescriptor() {
            params = new ArrayList<String>();
        }

        public MethodExpressionDescriptor(String name) {
            this();
            this.function = name;
        }
    }

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getMarker() {
        return marker;
    }

    public void setMarker(String marker) {
        this.marker = marker;
        endTokens = new String[] { placeholderEndToken, marker };
    }

    @Override
    public void setPlaceholderEndToken(String placeholderEndToken) {
        super.setPlaceholderEndToken(placeholderEndToken);
        endTokens = new String[] { placeholderEndToken, marker };
    }

    protected String doRender(String s, RenderContext... context) {

        StringBuffer sb = new StringBuffer();

        try
        {            
            renderTemplate(s, sb, nullToken, 0, endTokens, null, context);
        } 
        catch (Exception e)
        {
            Log4j.app.error("Exception in rendering method expression message emplate: " + e.getMessage(), e);
            return nullToken;
        }

        return sb.toString();
    }

    private int renderTemplate(String src, StringBuffer dst, String nil, int i, String[] marks, StringBuffer end, RenderContext... context) {

        int len = src.length();

        while (i < len)
        {
            char c = src.charAt(i);

            if (escape)
            {
                if (c=='\\')
                {
                    i++;
                    char ch = src.charAt(i);

                    if(inSingleQuote)
                    {
                        if(ch=='\'')
                        {
                            inSingleQuote=false;
                        }
                    }
                    else if(inDoubleQuote)
                    {
                        if(ch=='"')
                        {
                            inDoubleQuote=false;
                        }
                    }
                    else
                    {
                        if(ch=='\'')
                        {
                            inSingleQuote=true;
                        }
                        else if(ch=='"')
                        {
                            inDoubleQuote=true;
                        }
                    }

                    dst.append(ch);
                    i++;
                    continue;
                }
            }

            if(inSingleQuote)
            {
                if(c=='\'')
                {
                    inSingleQuote=false;
                }
            }
            else if(inDoubleQuote)
            {
                if(c=='"')
                {
                    inDoubleQuote=false;
                }
            }
            else
            {
                if(c=='\'')
                {
                    inSingleQuote=true;
                }
                else if(c=='"')
                {
                    inDoubleQuote=true;
                }
            }

            // check for end marker
            if (marks != null && !inSingleQuote && !inDoubleQuote)
            {
                for (int m = 0; m < marks.length; m++)
                {
                    // If one of markers found
                    if (src.regionMatches(i, marks[m], 0, marks[m].length()))
                    {
                        // return marker if required
                        if (end != null)
                        {
                            end.append(marks[m]);
                        }

                        return i+marks[m].length();
                    }
                }
            }

            // check for start of placeholder
            if (src.regionMatches(i, placeholderStartToken, 0, lenPlaceholderStartToken))
            {
                i = renderPlaceholder(src, dst, nil, i, new ArrayList<String>(), context);
                continue;
            }

            // just add plain character
            if(c != '\'' && c!= '"')
            {
                dst.append(c);
            }

            i++;
        }

        return i;
    }

    /**
     * Render a placeholder as follows:
     *
     * <<key>>: Simple render, key value map
     * <<function(<<param1>>, <<param2>>)>> : Function object render
     * 
     * @param src
     * @param dst
     * @param nil
     * @param i
     * @param params
     * @param context
     * @return
     */
    private int renderPlaceholder(String src, StringBuffer dst, String nil, int i, List<String> params, RenderContext... context){

        StringBuffer token = new StringBuffer(); // placeholder token
        StringBuffer end = new StringBuffer();  // placeholder end marker
        String value = null;

        // Simple key
        i = renderTemplate(src, token, nil, i+lenPlaceholderStartToken, endTokens, end, context);

        String sToken = token.toString().trim();        
        String sEnd = end.toString().trim();

        // This is method name
        if(sEnd.equals("("))
        {   // Method
            functionStack.add(new MethodExpressionDescriptor(sToken));
        }
        else    // Try to resolve value
        {
            if(sToken.startsWith(placeholderStartToken))
            {
                value = render(sToken, context);
            }
            else if(sToken.startsWith(prefix))
            {
                if(functionStack.size() > 0)
                {
                    functionStack.peek().params.add(sToken.substring(1));
                }

                return i;
            }
            else
            {
                value = getParam(sToken, context);
            }            
        }

        if (sEnd.length() == 0 || sEnd.equals(placeholderEndToken))
        {
            // No method found but found the end of placeholder token
            if(functionStack.size() == 0)
            {
                if(value != null)
                {
                    dst.append(value);
                }
                else
                {
                    dst.append(nil);
                }
            }
            else
            {
                functionStack.peek().params.add(value);
            }
        }
        else
        {
            if(value != null)
            {
                value = value.trim();
            }

            if(end.substring(0, 1).equals("(") ||
               end.substring(0, 1).equals(marker))
            {
                // right hand side is remainder of placeholder
                StringBuffer tmp = new StringBuffer();

                end = new StringBuffer();
                i = renderTemplate(src, tmp, nil, i, endTokens, end, context);                
            }

            if(end.substring(0, 1).equals(")"))
            {
                if ( functionStack.size() > 0 )
                {
                    // Pop method out of stack to invoke
                    MethodExpressionDescriptor descriptor = functionStack.pop();

                    if(functionStack.size() > 0 )
                    {
                        functionStack.peek().params.add(invokeMethodEvaluator(descriptor.function, descriptor.params.toArray(new String[0]), context));
                    }
                    else
                    {
                        dst.append(invokeMethodEvaluator(descriptor.function, descriptor.params.toArray(new String[0]), context));
                    }

                    end = new StringBuffer();
                    StringBuffer tmp = new StringBuffer();
                    i = renderTemplate(src, tmp, nil, i, endTokens, end, context);
                }
            }
        }

        return i;
    }

    protected String invokeMethodEvaluator(String method, String[] params, RenderContext... context){

        StringBuffer result = new StringBuffer();

        result.append("[ ")
              .append(method)
              .append(" ( ");

        if(params != null)
        {
            for(int i=0; i<params.length; i++)
            {
                result.append(params[i]);

                if(i != params.length-1)
                {
                    result.append(" , ");
                }
            }
        }

        result.append(" ) ")
              .append(" ] ");

        return result.toString();
    }

}