package net.jangaroo.exml.json; import net.jangaroo.utils.CompilerUtils; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; public class JsonObject implements Json { static final String LINE_SEPARATOR = System.getProperty("line.separator"); public static final String NET_JANGAROO_EXT_CREATE = "net.jangaroo.ext.create"; private Map<String, Object> properties = new LinkedHashMap<String, Object>(); private String wrapperClass; private String configClass; public JsonObject(Object ... namesAndValues) { if (namesAndValues.length % 2 != 0) { throw new IllegalArgumentException("argument vector must be of even length, but is " + Arrays.asList(namesAndValues)); } for (int i = 0; i < namesAndValues.length; i += 2) { Object name = namesAndValues[i]; if (!(name instanceof String)) { throw new IllegalArgumentException("property names must be strings, found '" + name + "'"); } Object value = namesAndValues[i + 1]; properties.put((String)name, value); } } public static Code code(final String code) { return new CodeImpl(code); } public String getWrapperClass() { return wrapperClass; } public JsonObject settingWrapperClass(String wrapperClass) { this.wrapperClass = wrapperClass; return this; } public boolean isEmpty() { return properties.isEmpty(); } @Override public String toString() { return toString(0); } /** * Make a pretty-printed JSON text of this JSONObject. * <p/> * Warning: This method assumes that the data structure is acyclic. * * @param indentFactor The number of spaces to add to each level of * indentation. * @return a printable, displayable, portable, transmittable * representation of the object, beginning * with <code>{</code> <small>(left brace)</small> and ending * with <code>}</code> <small>(right brace)</small>. */ public String toString(int indentFactor) { return toString(indentFactor, 0); } /** * Make a prettyprinted JSON text of an object value. * <p/> * Warning: This method assumes that the data structure is acyclic. * * * @param value The value to be serialized. * @param indentFactor The number of spaces to add to each level of * indentation. * @param indent The indentation of the top level. * @return a printable, displayable, transmittable * representation of the object, beginning * with <code>{</code> <small>(left brace)</small> and ending * with <code>}</code> <small>(right brace)</small>. */ public static String valueToString(Object value, int indentFactor, int indent) { if (value == null) { return "null"; } if (value instanceof Number || value instanceof Boolean) { return value.toString(); } else if (value instanceof JsonObject) { return ((JsonObject)value).toString(indentFactor, indent); } else if (value instanceof JsonArray) { return ((JsonArray) value).toString(indentFactor, indent); } else if (value instanceof Code) { return ((Code)value).getCode().replaceAll("\n", LINE_SEPARATOR); } return CompilerUtils.quote(value.toString()).replaceAll("\n", LINE_SEPARATOR); } /** * Make a prettyprinted JSON text of this JSONObject. * <p/> * Warning: This method assumes that the data structure is acyclical. * * @param indentFactor The number of spaces to add to each level of * indentation. * @param indent The indentation of the top level. * @return a printable, displayable, transmittable * representation of the object, beginning * with <code>{</code> <small>(left brace)</small> and ending * with <code>}</code> <small>(right brace)</small>. */ public String toString(int indentFactor, int indent) { Set<String> keySet = this.properties.keySet(); StringBuilder sb = new StringBuilder(); if (wrapperClass != null) { sb.append("new ").append(wrapperClass).append('('); } else if(configClass != null) { sb.append(NET_JANGAROO_EXT_CREATE).append('(').append(configClass).append(','); } sb.append("{"); int newindent = indent + indentFactor; int n = keySet.size(); Iterator<String> keys = keySet.iterator(); if (n == 1) { writeKeyValue(keys.next(), indentFactor, indent, sb); } else if (n > 1) { boolean isFirstAttribute = true; while (keys.hasNext()) { if (isFirstAttribute) { isFirstAttribute = false; } else { sb.append(","); } newlineAndIndent(sb, newindent); writeKeyValue(keys.next(), indentFactor, newindent, sb); } if (sb.length() > 1) { newlineAndIndent(sb, indent); } } sb.append('}'); if (wrapperClass != null || configClass != null) { sb.append(')'); } return sb.toString(); } private void newlineAndIndent(StringBuilder sb, int indent) { if (indent < 0) { return; } sb.append(LINE_SEPARATOR); for (int i = 0; i < indent; i++) { sb.append(' '); } } private void writeKeyValue(String key, int indentFactor, int indent, StringBuilder sb) { if (key.isEmpty()) { // TODO: if (!key.matches(<JS_IDENTIFIER_PATTERN>)) sb.append("\"\""); } else { sb.append(key); } sb.append(": "); sb.append(valueToString(this.properties.get(key), indentFactor, indent)); } public Object get(String property) { return properties.get(property); } public void set(String property, Object value) { this.properties.put(property, value); } public void add(JsonObject jsonObject) { for (Map.Entry<String, Object> property : jsonObject.properties.entrySet()) { set(property.getKey(), property.getValue()); } } public Object remove(String property) { return this.properties.remove(property); } public void settingConfigClass(String fullName) { this.configClass = fullName; } private static class CodeImpl implements Code { private final String code; CodeImpl(String code) { this.code = code; } @Override public String getCode() { return code; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } CodeImpl that = (CodeImpl) o; return code.equals(that.code); } @Override public int hashCode() { return code.hashCode(); } } }