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

Summary

Maintainability
F
1 wk
Test Coverage
/** JSCumulativeContentRenderer.java.

    Purpose:
        
    Description:
        
    History:
        10:35:15 AM Apr 22, 2015, Created by jumperchen

Copyright (C) 2015 Potix Corporation. All Rights Reserved.
*/
package org.zkoss.zk.ui.sys;

import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.zkoss.json.JSONAware;
import org.zkoss.json.JSONs;
import org.zkoss.lang.Generics;
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', 'value1', ..]) cumulatively.
 * It can support to add the same name with different value that the different value
 * will be packed into a list.
 * @author jumperchen
 * @since 8.0
 */
public class JSCumulativeContentRenderer implements ContentRenderer {

    private Map<String, List<Object>> _stack = new LinkedHashMap<String, List<Object>>();

    private List<Object> fetch(String name) {
        List<Object> list = _stack.get(name);
        if (list == null) {
            list = new LinkedList<Object>();
            _stack.put(name, list);
        }
        return list;
    }

    public void render(String name, String value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, Date value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, Object value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, int value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, short value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, long value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, byte value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, boolean value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, double value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, float value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    public void render(String name, char value) throws IOException {
        fetch(name).add(renderValue(value));
    }

    private String renderValue(String value) {
        if (value == null)
            return null;
        else {
            return Strings.escapeJavaScript(value);
        }
    }

    private String renderValue(Date value) {
        if (value == null)
            return null;
        else
            return new StringBuilder().append("jq.j2d('").append(JSONs.d2j(value)).append("')").toString();
    }

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

    private String renderValue(Object value) {
        if (value == null || value instanceof String) {
            return renderValue((String) value);
        }
        if (value instanceof Date) {
            return renderValue((Date) value);
        }
        if (value instanceof Component) {
            return renderValue((Component) value);
        }
        if (value instanceof Character) {
            return renderValue(((Character) value).charValue());
        }
        StringBuilder buf = new StringBuilder();
        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 buf.toString();
        }
        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 buf.toString();
        }
        //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 buf.toString();
        }
        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 buf.toString();
        }
        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 buf.toString();
        }
        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 buf.toString();
        }
        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 buf.toString();
        }
        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 buf.toString();
        }
        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 buf.toString();
        }
        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 buf.toString();
        }
        if (value instanceof JSONAware)
            return ((JSONAware) value).toJSONString();
        else
            return renderValue(value.toString());
    }

    private String renderValue(char value) {
        StringBuilder buf = new StringBuilder();
        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('\'');
        return buf.toString();
    }

    public void renderDirectly(String name, Object value) {
        fetch(name).add(renderValue(value));
    }

    public void renderWidgetListeners(Map<String, String> listeners) {
        fetch("listeners0").add(listeners);
    }

    public void renderWidgetOverrides(Map<String, String> overrides) {
        fetch("overrides").add(overrides);
    }

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

    public void renderClientAttributes(Map<String, String> attrs) {
        fetch("domExtraAttrs").add(attrs);
    }

    public String toString() {
        StringBuilder sb = new StringBuilder(64);
        Map<String, List<Object>> result = new LinkedHashMap<String, List<Object>>(_stack);
        List<Map<String, String>> listeners = Generics.cast(result.remove("listeners0"));
        List<Map<String, String>> overrides = Generics.cast(result.remove("overrides"));
        List<Map<String, String>> attrs = Generics.cast(result.remove("domExtraAttrs"));

        for (Map.Entry<String, List<Object>> me : result.entrySet()) {
            renderName(sb, me.getKey());
            sb.append('[');
            final List<Object> value = me.getValue();
            int j = 0;
            for (Iterator<Object> it = value.iterator(); it.hasNext(); j++) {
                if (j > 0)
                    sb.append(',');
                sb.append(it.next());
            }
            sb.append(']');
        }
        if (listeners != null) {
            renderName(sb, "listeners0");
            sb.append('[');

            int j = 0;
            for (Iterator<Map<String, String>> it = listeners.iterator(); it.hasNext(); j++) {
                if (j > 0)
                    sb.append(',');

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

        if (overrides != null) {
            renderName(sb, "overrides");
            sb.append('[');

            int j = 0;
            for (Iterator<Map<String, String>> it = overrides.iterator(); it.hasNext(); j++) {
                if (j > 0)
                    sb.append(',');

                sb.append('{');
                for (Iterator itt = it.next().entrySet().iterator(); itt.hasNext();) {
                    final Map.Entry me = (Map.Entry) itt.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.length() != 0 && ((cc = v.charAt(v.length() - 1)) == ';' || cc == ','
                                || (v.indexOf("function") < 0 && 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)"));
                    }
                    sb.append(name).append(":\n").append(Strings.isEmpty(value) ? "''" : value).append("\n,");
                }
                sb.setCharAt(sb.length() - 1, '}');
            }
            sb.append(']');
        }

        if (attrs != null) {
            renderName(sb, "domExtraAttrs");
            sb.append('[');

            int j = 0;
            for (Iterator<Map<String, String>> it = listeners.iterator(); it.hasNext(); j++) {
                if (j > 0)
                    sb.append(',');

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

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