zk/src/main/java/org/zkoss/zk/ui/sys/JsContentRenderer.java

Summary

Maintainability
F
6 days
Test Coverage
/* JsContentRenderer.java

    Purpose:
        
    Description:
        
    History:
        Wed Oct  1 19:08:57     2008, Created by tomyeh

Copyright (C) 2008 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
    This program is distributed under LGPL Version 2.1 in the hope that
    it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui.sys;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.zkoss.json.JSONAware;
import org.zkoss.json.JSONs;
import org.zkoss.lang.Strings;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.UiException;

/**
 * An implementation of {@link ContentRenderer} that renders
 * the content as a JavaScript property (i.e., name: 'value').
 * @author tomyeh
 * @since 5.0.0
 */
public class JsContentRenderer implements ContentRenderer {
    private final StringBuilder _buf = new StringBuilder(128);

    public JsContentRenderer() {
    }

    /** Returns the content being rendered.
     */
    public CharSequence getBuffer() {
        return _buf;
    }

    /** Renders a string property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, String value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders a Date property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, Date value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders an arbitrary object. */
    public void render(String name, Object value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders an integer property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, int value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders a long property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, long value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders a short property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, short value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders a byte property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, byte value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders a boolean property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, boolean value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders a double property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, double value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders a double property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, float value) {
        renderName(name);
        renderValue(value);
    }

    /** Renders a char property.
     * @param name the property name. Note: it must be a legal JavaScript
     * variable name.
     */
    public void render(String name, char value) {
        renderName(name);
        renderValue(value);
    }

    private void renderName(String name) {
        if (_buf.length() > 0)
            _buf.append(',');
        _buf.append(name).append(':');
    }

    private void renderValue(String value) {
        if (value == null)
            _buf.append((String) null);
        else {
            _buf.append('\'');
            _buf.append(Strings.escapeJavaScript(value));
            _buf.append('\'');
        }
    }

    private void renderValue(Date value) {
        if (value == null)
            _buf.append((String) null);
        else
            _buf.append("jq.j2d('").append(JSONs.d2j(value)).append("')");
    }

    private void renderValue(Component value) {
        if (value == null || value.getPage() == null)
            _buf.append((String) null);
        else
            _buf.append("{$u:'").append(value.getUuid()).append("'}");
    }

    private void renderValue(Object value) {
        if (value == null || value instanceof String) {
            renderValue((String) value);
            return;
        }
        if (value instanceof Date) {
            renderValue((Date) value);
            return;
        }
        if (value instanceof Component) {
            renderValue((Component) value);
            return;
        }
        if (value instanceof Integer) {
            renderValue(((Integer) value).intValue());
            return;
        }
        if (value instanceof Long) {
            renderValue(((Long) value).longValue());
            return;
        }
        if (value instanceof Double) {
            renderValue(((Double) value).doubleValue());
            return;
        }
        if (value instanceof Short) {
            renderValue(((Short) value).shortValue());
            return;
        }
        if (value instanceof Byte) {
            renderValue(((Byte) value).byteValue());
            return;
        }
        if (value instanceof Boolean) {
            renderValue(((Boolean) value).booleanValue());
            return;
        }

        if (value instanceof Float) {
            renderValue(((Float) value).floatValue());
            return;
        }
        if (value instanceof Character) {
            renderValue(((Character) value).charValue());
            return;
        }
        if (value instanceof BigDecimal) {
            renderValue(((BigDecimal) value).toString());
            return;
        }
        if (value instanceof Map) {
            _buf.append('{');
            boolean first = true;
            for (Iterator it = ((Map) value).entrySet().iterator(); it.hasNext();) {
                final Map.Entry me = (Map.Entry) it.next();
                if (first)
                    first = false;
                else
                    _buf.append(',');
                _buf.append('\'').append(me.getKey()).append("':");
                renderValue(me.getValue());
            }
            _buf.append('}');
            return;
        }
        if (value instanceof List) {
            _buf.append('[');
            int j = 0;
            for (Iterator it = ((List) value).iterator(); it.hasNext(); j++) {
                if (j > 0)
                    _buf.append(',');
                renderValue(it.next());
            }
            _buf.append(']');
            return;
        }
        //handle array
        if (value instanceof Object[]) {
            _buf.append('[');
            final Object[] ary = (Object[]) value;
            for (int j = 0; j < ary.length; ++j) {
                if (j > 0)
                    _buf.append(',');
                renderValue(ary[j]);
            }
            _buf.append(']');
            return;
        }
        if (value instanceof int[]) {
            _buf.append('[');
            final int[] ary = (int[]) value;
            for (int j = 0; j < ary.length; ++j) {
                if (j > 0)
                    _buf.append(',');
                renderValue(ary[j]);
            }
            _buf.append(']');
            return;
        }
        if (value instanceof long[]) {
            _buf.append('[');
            final long[] ary = (long[]) value;
            for (int j = 0; j < ary.length; ++j) {
                if (j > 0)
                    _buf.append(',');
                renderValue(ary[j]);
            }
            _buf.append(']');
            return;
        }
        if (value instanceof short[]) {
            _buf.append('[');
            final short[] ary = (short[]) value;
            for (int j = 0; j < ary.length; ++j) {
                if (j > 0)
                    _buf.append(',');
                renderValue(ary[j]);
            }
            _buf.append(']');
            return;
        }
        if (value instanceof float[]) {
            _buf.append('[');
            final float[] ary = (float[]) value;
            for (int j = 0; j < ary.length; ++j) {
                if (j > 0)
                    _buf.append(',');
                renderValue(ary[j]);
            }
            _buf.append(']');
            return;
        }
        if (value instanceof double[]) {
            _buf.append('[');
            final double[] ary = (double[]) value;
            for (int j = 0; j < ary.length; ++j) {
                if (j > 0)
                    _buf.append(',');
                renderValue(ary[j]);
            }
            _buf.append(']');
            return;
        }
        if (value instanceof byte[]) {
            _buf.append('[');
            final byte[] ary = (byte[]) value;
            for (int j = 0; j < ary.length; ++j) {
                if (j > 0)
                    _buf.append(',');
                renderValue(ary[j]);
            }
            _buf.append(']');
            return;
        }
        if (value instanceof char[]) {
            _buf.append('[');
            final char[] ary = (char[]) value;
            for (int j = 0; j < ary.length; ++j) {
                if (j > 0)
                    _buf.append(',');
                renderValue(ary[j]);
            }
            _buf.append(']');
            return;
        }
        if (value instanceof JSONAware)
            _buf.append(((JSONAware) value).toJSONString());
        else
            renderValue(value.toString());
    }

    private void renderValue(int value) {
        _buf.append(value);
    }

    private void renderValue(long value) {
        _buf.append(value);
    }

    private void renderValue(short value) {
        _buf.append(value);
    }

    private void renderValue(byte value) {
        _buf.append(value);
    }

    private void renderValue(boolean value) {
        _buf.append(value);
    }

    private void renderValue(double value) {
        _buf.append(value);
    }

    private void renderValue(float value) {
        _buf.append(value);
    }

    private void renderValue(char value) {
        _buf.append('\'');
        switch (value) {
        case '\'':
        case '\\':
            _buf.append('\\');
            break;
        case '\n':
            _buf.append('\\');
            value = 'n';
            break;
        case '\t':
            _buf.append('\\');
            value = 't';
            break;
        case '\r':
            _buf.append('\\');
            value = 'r';
            break;
        case '\f':
            _buf.append('\\');
            value = 'f';
            break;
        }
        _buf.append(value).append('\'');
    }

    /** Renders the JavaScript code snippet.
     */
    public void renderDirectly(String name, Object value) {
        renderName(name);
        if (value != null && !(value instanceof String))
            throw new UnsupportedOperationException("Only String or null allowed, not " + value.toString());
        _buf.append((String) value);
    }

    /** Renders the JavaScript code snippet for event listeners
     * registered for the peer widget.
     */
    public void renderWidgetListeners(Map<String, String> listeners) {
        if (listeners == null || listeners.isEmpty())
            return;

        renderName("listeners");
        _buf.append('{');
        for (Iterator it = listeners.entrySet().iterator(); it.hasNext();) {
            final Map.Entry me = (Map.Entry) it.next();
            _buf.append(me.getKey()).append(":function(event){\n").append(me.getValue()).append("\n},");
        }
        _buf.setCharAt(_buf.length() - 1, '}');
    }

    /** Renders the JavaScript codes snippet to override the methods
     * and properties of the peer widget.
     * This method uses the widget's setOverrides method (at client),
     * so, if the value is a method, it will preserve the previous method
     * as '$' + method_name
     *
     * @param overrides a map of methods and properties. Notice that the value
     * must be a valid JavaScript snippet that can be evaluated to
     * a value. In fact, the map will be generated as follows.<br/>
     * <code>{name1: value1, name2: value2}</code>.
     * Examples of values: <code>function () {}</code>, <code>123</code>,
     * <code>new Date()</code>, and <code>"a literal string"</code>
     */
    public void renderWidgetOverrides(Map<String, String> overrides) {
        if (overrides == null || overrides.isEmpty())
            return;

        renderName("overrides");
        _buf.append('{');
        for (Iterator it = overrides.entrySet().iterator(); it.hasNext();) {
            final Map.Entry me = (Map.Entry) it.next();
            final String name = (String) me.getKey();
            final String value = (String) me.getValue();
            if (value != null) {
                //It is too costly to detect if it is a legal expression
                //so we only check the most common illegal case
                final String v = value.trim();
                char cc;
                if (!v.isEmpty() && ((cc = v.charAt(v.length() - 1)) == ';' || cc == ','
                        || (!v.contains("function") && v.indexOf(';') >= 0)))
                    throw new UiException("Illegal client override: " + v
                            + (name.startsWith("on")
                                    ? "\nTo listen an event, remember to captalize the third letter, such as onClick"
                                    : "\nIt must be a legal JavaScript expression (not statement)"));
            }
            _buf.append(name).append(":\n").append(Strings.isEmpty(value) ? "''" : value).append("\n,");
        }
        _buf.setCharAt(_buf.length() - 1, '}');
    }

    public void renderWidgetAttributes(Map<String, String> attrs) {
        renderClientAttributes(attrs);
    }

    public void renderClientAttributes(Map<String, String> attrs) {
        if (attrs == null || attrs.isEmpty())
            return;

        renderName("domExtraAttrs");
        _buf.append('{');
        for (Iterator it = attrs.entrySet().iterator(); it.hasNext();) {
            final Map.Entry me = (Map.Entry) it.next();
            renderValue(me.getKey()); //allow ':' or others
            _buf.append(':');
            renderValue(me.getValue());
            _buf.append("\n,");
        }
        _buf.setCharAt(_buf.length() - 1, '}');
    }
}