--- /dev/null
+package org.simantics.document.server;\r
+\r
+import java.io.IOException;\r
+import java.util.Arrays;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.Set;\r
+import java.util.TreeMap;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.adapter.AdaptException;\r
+import org.simantics.databoard.adapter.Adapter;\r
+import org.simantics.databoard.adapter.AdapterConstructionException;\r
+import org.simantics.databoard.binding.ArrayBinding;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.binding.MapBinding;\r
+import org.simantics.databoard.binding.OptionalBinding;\r
+import org.simantics.databoard.binding.RecordBinding;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.parser.repository.DataValueRepository;\r
+import org.simantics.databoard.type.Component;\r
+import org.simantics.databoard.type.RecordType;\r
+import org.simantics.databoard.util.Bean;\r
+import org.simantics.document.server.io.IJSONObject;\r
+import org.simantics.scl.runtime.tuple.Tuple;\r
+\r
+final public class JSONObject extends Bean implements IJSONObject {\r
+\r
+ final public String id;\r
+ final public TreeMap<String, Object> fields = new TreeMap<String, Object>();\r
+ private int hashCode = 0;\r
+\r
+ public JSONObject(Binding binding, String id) {\r
+ super(binding);\r
+ assert (binding != null);\r
+ assert (id != null);\r
+ this.id = id.intern();\r
+ }\r
+\r
+ public JSONObject(String id) {\r
+ assert (id != null);\r
+ this.id = id.intern();\r
+ }\r
+\r
+ public JSONObject clone() {\r
+ JSONObject result = new JSONObject(binding, id);\r
+ for (Map.Entry<String, Object> e : fields.entrySet())\r
+ result.addJSONField(e.getKey(), e.getValue());\r
+ return result;\r
+ }\r
+\r
+ public void add(Map<String, Object> fields) {\r
+ for (Map.Entry<String, Object> e : fields.entrySet())\r
+ addJSONField(e.getKey(), e.getValue());\r
+ }\r
+\r
+ @Override\r
+ public int hashCode() {\r
+ \r
+ if(hashCode == 0) {\r
+ int result = id.hashCode();\r
+ Iterator<Entry<String,Object>> i = fields.entrySet().iterator();\r
+ while (i.hasNext()) {\r
+ Entry<String,Object> entry = i.next();\r
+ String key = entry.getKey();\r
+ Object value = entry.getValue();\r
+ if(value != null) {\r
+ if(value.getClass().isArray())\r
+ result += objectHashCode(key) ^ arrayHashCode(value);\r
+ else \r
+ result += objectHashCode(key) ^ objectHashCode(value);\r
+ } else {\r
+ result += objectHashCode(key);\r
+ }\r
+ }\r
+ hashCode = result;\r
+ }\r
+ return hashCode;\r
+\r
+ }\r
+\r
+ /**\r
+ * Returns the hash code of a non-{@code null} argument and 0 for\r
+ * a {@code null} argument.\r
+ *\r
+ * @param o an object\r
+ * @return the hash code of a non-{@code null} argument and 0 for\r
+ * a {@code null} argument\r
+ * @see Object#hashCode\r
+ */\r
+ private static int objectHashCode(Object o) {\r
+ return o != null ? o.hashCode() : 0;\r
+ }\r
+\r
+ private final boolean arrayEquals(Object av1, Object av2) {\r
+ if (av2 == null)\r
+ return false;\r
+ Class<?> c1 = av1.getClass().getComponentType();\r
+ Class<?> c2 = av2.getClass().getComponentType();\r
+ if (c2 == null || !c1.equals(c2))\r
+ return false;\r
+ boolean p1 = c1.isPrimitive();\r
+ boolean p2 = c2.isPrimitive();\r
+ if (p1 != p2)\r
+ return false;\r
+ if (!p1)\r
+ return Arrays.equals((Object[]) av1, (Object[]) av2);\r
+ if (boolean.class.equals(c1))\r
+ return Arrays.equals((boolean[]) av1, (boolean[]) av2);\r
+ else if (byte.class.equals(c1))\r
+ return Arrays.equals((byte[]) av1, (byte[]) av2);\r
+ else if (int.class.equals(c1))\r
+ return Arrays.equals((int[]) av1, (int[]) av2);\r
+ else if (long.class.equals(c1))\r
+ return Arrays.equals((long[]) av1, (long[]) av2);\r
+ else if (float.class.equals(c1))\r
+ return Arrays.equals((float[]) av1, (float[]) av2);\r
+ else if (double.class.equals(c1))\r
+ return Arrays.equals((double[]) av1, (double[]) av2);\r
+ throw new RuntimeException("??? Contact application querySupport.");\r
+ }\r
+\r
+ private final int arrayHashCode(Object av) {\r
+ if (av == null)\r
+ return 0;\r
+ Class<?> c1 = av.getClass().getComponentType();\r
+ boolean p1 = c1.isPrimitive();\r
+ if (!p1)\r
+ return Arrays.hashCode((Object[]) av);\r
+ if (boolean.class.equals(c1))\r
+ return Arrays.hashCode((boolean[]) av);\r
+ else if (byte.class.equals(c1))\r
+ return Arrays.hashCode((byte[]) av);\r
+ else if (int.class.equals(c1))\r
+ return Arrays.hashCode((int[]) av);\r
+ else if (long.class.equals(c1))\r
+ return Arrays.hashCode((long[]) av);\r
+ else if (float.class.equals(c1))\r
+ return Arrays.hashCode((float[]) av);\r
+ else if (double.class.equals(c1))\r
+ return Arrays.hashCode((double[]) av);\r
+ throw new RuntimeException("??? Contact application querySupport.");\r
+ }\r
+ \r
+ @Override\r
+ public boolean equals(Object object) {\r
+\r
+ if (this == object)\r
+ return true;\r
+ else if (object == null)\r
+ return false;\r
+ else if (!(object instanceof JSONObject))\r
+ return false;\r
+ JSONObject o = (JSONObject) object;\r
+ \r
+ if (!id.equals(o.id))\r
+ return false;\r
+\r
+ Set<String> keys = fields.keySet();\r
+ Set<String> otherKeys = o.fields.keySet();\r
+ \r
+ if (!keys.equals(otherKeys))\r
+ return false;\r
+\r
+ for (String key : keys) {\r
+\r
+ Object value = fields.get(key);\r
+ Object otherValue = o.fields.get(key);\r
+\r
+ if (otherValue != null) {\r
+ if (otherValue.getClass().isArray()) {\r
+ if (!arrayEquals(otherValue, value)) {\r
+ return false;\r
+ }\r
+ } else {\r
+ if (!otherValue.equals(value)) {\r
+ return false;\r
+ }\r
+ }\r
+ } else if (value != null)\r
+ return false;\r
+\r
+ }\r
+\r
+ return true;\r
+\r
+ }\r
+\r
+ public void addJSONField(String key, Object content) {\r
+ fields.put(key, content);\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ public <T> T getJSONField(String key) {\r
+ return (T) fields.get(key);\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ public <T> T getJSONFieldDefault(String key, T defaultValue) {\r
+ T value = (T) fields.get(key);\r
+ if (value != null)\r
+ return value;\r
+ else\r
+ return defaultValue;\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ public <T> T getBeanJSONFieldDefault(String key, Binding target,\r
+ T defaultValue) {\r
+ T value = (T) fields.get(key);\r
+ try {\r
+ if (value != null) {\r
+// if (value instanceof Bean) {\r
+ Binding source = Bindings.getBinding(target.type());\r
+ Adapter adapter = Bindings.getAdapter(source, target);\r
+ return (T) adapter.adapt(value);\r
+// }\r
+// return value;\r
+ }\r
+ } catch (AdapterConstructionException e) {\r
+ } catch (AdaptException e) {\r
+ }\r
+ return defaultValue;\r
+ }\r
+\r
+ public String getParent() {\r
+ return (String) fields.get("parent");\r
+ }\r
+\r
+ public String getParentOrd() {\r
+ return (String) fields.get("parentOrd");\r
+ }\r
+\r
+ public String getType() {\r
+ return (String) fields.get("type");\r
+ }\r
+\r
+ public String toString() {\r
+ StringBuilder b = new StringBuilder();\r
+ b.append("{");\r
+ boolean first = true;\r
+ for (Map.Entry<String, Object> entry : fields.entrySet()) {\r
+ if (first)\r
+ first = false;\r
+ else\r
+ b.append(",");\r
+ String key = entry.getKey();\r
+ String value = fieldJSON(entry.getValue());\r
+ if (value == null) {\r
+ first = true; // prevents ", ," when no key and value are given\r
+ continue;\r
+ }\r
+ b.append('"');\r
+ b.append(key);\r
+ b.append('"');\r
+ b.append(':');\r
+ b.append(value);\r
+ b.append("\n");\r
+ }\r
+ b.append("}");\r
+ return b.toString();\r
+ }\r
+\r
+ private void printValue(Object value, Binding binding_, StringBuilder sb)\r
+ throws IOException {\r
+ try {\r
+ if (binding_ instanceof RecordBinding) {\r
+ RecordBinding binding = (RecordBinding) binding_;\r
+ sb.append("{");\r
+ RecordType type = binding.type();\r
+ for (int i = 0, j = 0; i < type.getComponentCount(); i++) {\r
+\r
+ Component c = type.getComponent(i);\r
+\r
+ Object field = binding.getComponent(value, i);\r
+\r
+ Binding b = binding.getComponentBinding(i);\r
+ if (b instanceof OptionalBinding) {\r
+ OptionalBinding ob = (OptionalBinding) b;\r
+ if (!ob.hasValueUnchecked(field))\r
+ continue;\r
+ b = ob.getComponentBinding();\r
+ }\r
+\r
+ if (j > 0)\r
+ sb.append(",");\r
+ sb.append("\n");\r
+ j++;\r
+\r
+ sb.append("\"");\r
+ sb.append(c.name);\r
+ sb.append("\" : ");\r
+ printValue(field, b, sb);\r
+ }\r
+ sb.append("}");\r
+ } else if (binding_ instanceof ArrayBinding) {\r
+ ArrayBinding binding = (ArrayBinding) binding_;\r
+ Binding b = binding.getComponentBinding();\r
+ sb.append("[");\r
+ for (int i = 0; i < binding.size(value); i++) {\r
+ if (i > 0)\r
+ sb.append(",");\r
+ printValue(binding.get(value, i), b, sb);\r
+ }\r
+ sb.append("]");\r
+ } else if (binding_ instanceof MapBinding) {\r
+ sb.append("{");\r
+ MapBinding binding = (MapBinding) binding_;\r
+ int j = 0;\r
+ for (Object key : binding.getKeys(value)) {\r
+ Object val = binding.get(value, key);\r
+ if (key instanceof String && val instanceof String) {\r
+\r
+ if (j > 0)\r
+ sb.append(",");\r
+ sb.append("\n");\r
+ j++;\r
+\r
+ sb.append("\"");\r
+ sb.append((String) key);\r
+ sb.append("\" : \"");\r
+ sb.append((String) val);\r
+ sb.append("\"");\r
+\r
+ }\r
+ }\r
+ sb.append("}");\r
+ } else {\r
+ DataValueRepository rep = new DataValueRepository();\r
+ binding_.printValue(value, sb, rep, false);\r
+ }\r
+ } catch (BindingException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ private String printList(List<?> list) {\r
+ StringBuilder b = new StringBuilder();\r
+ b.append("[");\r
+ boolean first = true;\r
+ for (Object o : list) {\r
+ if (first) {\r
+ first = false;\r
+ } else {\r
+ b.append(",");\r
+ }\r
+ b.append(fieldJSON(o));\r
+ }\r
+ b.append("]");\r
+ return b.toString();\r
+ }\r
+\r
+ private String fieldJSON(Object field) {\r
+\r
+ if (field == null)\r
+ return null;\r
+\r
+ String valueString = null;\r
+ if (field instanceof Bean) {\r
+ // Try bean to JSON\r
+ try {\r
+ Bean bean = (Bean) field;\r
+ StringBuilder sb = new StringBuilder();\r
+ printValue(bean, bean.getBinding(), sb);\r
+ valueString = sb.toString();\r
+ } catch (IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ } else if (field instanceof List) {\r
+ return printList((List<?>) field);\r
+ } else if (field instanceof Tuple) {\r
+ Tuple t = (Tuple) field;\r
+ if (t.length() == 2) {\r
+ Object o1 = t.get(0);\r
+ Object o2 = t.get(1);\r
+ if (o1 instanceof String) {\r
+ return fieldJSON(o1) + " : " + fieldJSON(o2);\r
+ } else {\r
+ return "{" + fieldJSON(o1) + " , " + fieldJSON(o2) + "}";\r
+ }\r
+ } else {\r
+ StringBuilder b = new StringBuilder();\r
+ b.append("{");\r
+ for (int i = 0; i < t.length(); i++) {\r
+ if (i > 0)\r
+ b.append(",");\r
+ b.append(fieldJSON(t.get(i)));\r
+ }\r
+ b.append("}");\r
+ return b.toString();\r
+ }\r
+ } else {\r
+ if (field.getClass().isArray()) {\r
+\r
+ Object[] array;\r
+ if (field instanceof float[]) {\r
+ array = new Float[((float[]) field).length];\r
+ for (int i = 0; i < array.length; i++) {\r
+ array[i] = ((float[]) field)[i];\r
+ }\r
+ } else if (field instanceof int[]) {\r
+ array = new Integer[((int[]) field).length];\r
+ for (int i = 0; i < array.length; i++) {\r
+ array[i] = ((int[]) field)[i];\r
+ }\r
+ } else\r
+ array = (Object[]) field;\r
+\r
+ // Build a string of the value array. Format is: [ value, value,\r
+ // value, ... ]\r
+ StringBuilder arrayBuilder = new StringBuilder();\r
+ arrayBuilder.append("[");\r
+ for (int i = 0; i < array.length; i++) {\r
+ Object o = array[i];\r
+ if (i != 0)\r
+ arrayBuilder.append(",");\r
+\r
+ if (o instanceof String)\r
+ arrayBuilder.append("\"");\r
+\r
+ arrayBuilder.append(o.toString());\r
+\r
+ if (o instanceof String)\r
+ arrayBuilder.append("\"");\r
+ }\r
+ arrayBuilder.append("]");\r
+ valueString = arrayBuilder.toString();\r
+ } else {\r
+ if (field instanceof String) {\r
+ // Use a string representation of the value\r
+ valueString = quote((String) field);\r
+ } else {\r
+ // Use a string representation of the value\r
+ valueString = "\"" + field.toString() + "\"";\r
+ }\r
+\r
+ }\r
+ }\r
+\r
+ return valueString;\r
+\r
+ }\r
+ \r
+ /*\r
+ * Copied from org.json\r
+ * \r
+ Copyright (c) 2002 JSON.org\r
+\r
+ Permission is hereby granted, free of charge, to any person obtaining a copy\r
+ of this software and associated documentation files (the "Software"), to deal\r
+ in the Software without restriction, including without limitation the rights\r
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+ copies of the Software, and to permit persons to whom the Software is\r
+ furnished to do so, subject to the following conditions:\r
+\r
+ The above copyright notice and this permission notice shall be included in all\r
+ copies or substantial portions of the Software.\r
+\r
+ The Software shall be used for Good, not Evil.\r
+\r
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
+ SOFTWARE.\r
+ */\r
+ /**\r
+ * Produce a string in double quotes with backslash sequences in all the\r
+ * right places. A backslash will be inserted within </, allowing JSON\r
+ * text to be delivered in HTML. In JSON text, a string cannot contain a\r
+ * control character or an unescaped quote or backslash.\r
+ * @param string A String\r
+ * @return A String correctly formatted for insertion in a JSON text.\r
+ */\r
+ public static String quote(String string) {\r
+ if (string == null || string.length() == 0) {\r
+ return "\"\"";\r
+ }\r
+\r
+ char b;\r
+ char c = 0;\r
+ int i;\r
+ int len = string.length();\r
+ StringBuffer sb = new StringBuffer(len + 4);\r
+ String t;\r
+\r
+ sb.append('"');\r
+ for (i = 0; i < len; i += 1) {\r
+ b = c;\r
+ c = string.charAt(i);\r
+ switch (c) {\r
+ case '\\':\r
+ case '"':\r
+ sb.append('\\');\r
+ sb.append(c);\r
+ break;\r
+ case '/':\r
+ if (b == '<') {\r
+ sb.append('\\');\r
+ }\r
+ sb.append(c);\r
+ break;\r
+ case '\b':\r
+ sb.append("\\b");\r
+ break;\r
+ case '\t':\r
+ sb.append("\\t");\r
+ break;\r
+ case '\n':\r
+ sb.append("\\n");\r
+ break;\r
+ case '\f':\r
+ sb.append("\\f");\r
+ break;\r
+ case '\r':\r
+ sb.append("\\r");\r
+ break;\r
+ default:\r
+ if (c < ' ' || (c >= '\u0080' && c < '\u00a0') ||\r
+ (c >= '\u2000' && c < '\u2100')) {\r
+ t = "000" + Integer.toHexString(c);\r
+ sb.append("\\u" + t.substring(t.length() - 4));\r
+ } else {\r
+ sb.append(c);\r
+ }\r
+ }\r
+ }\r
+ sb.append('"');\r
+ return sb.toString();\r
+ } \r
+ \r
+ public String getId() {\r
+ return id;\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public <T> T getValue(String key) {\r
+ return (T)fields.get(key);\r
+ }\r
+\r
+ @Override\r
+ public Iterator<String> keys() {\r
+ return fields.keySet().iterator();\r
+ }\r
+\r
+ @Override\r
+ public IJSONObject clone(Map<String, Object> newObjects) {\r
+ JSONObject result = new JSONObject(binding, id);\r
+ for (Map.Entry<String, Object> e : fields.entrySet())\r
+ result.addJSONField(e.getKey(), e.getValue());\r
+ \r
+ for (Map.Entry<String, Object> e : newObjects.entrySet())\r
+ result.addJSONField(e.getKey(), e.getValue());\r
+ return result;\r
+ }\r
+}
\ No newline at end of file