PageRenderTime 57ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/sys/js/fanx/ObjEncoder.js

https://bitbucket.org/bedlaczech/fan-1.0
JavaScript | 328 lines | 229 code | 43 blank | 56 comment | 45 complexity | 7f8ee4b79590233345e418ac2bfd347a MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. //
  2. // Copyright (c) 2010, Brian Frank and Andy Frank
  3. // Licensed under the Academic Free License version 3.0
  4. //
  5. // History:
  6. // 11 Jun 10 Andy Frank Creation
  7. //
  8. /**
  9. * ObjEncoder serializes an object to an output stream.
  10. */
  11. function fanx_ObjEncoder(out, options)
  12. {
  13. this.out = out;
  14. this.level = 0;
  15. this.indent = 0;
  16. this.skipDefaults = false;
  17. this.skipErrors = false;
  18. this.curFieldType = null;
  19. if (options != null) this.initOptions(options);
  20. }
  21. //////////////////////////////////////////////////////////////////////////
  22. // Static
  23. //////////////////////////////////////////////////////////////////////////
  24. fanx_ObjEncoder.encode = function(obj)
  25. {
  26. var buf = fan.sys.StrBuf.make();
  27. var out = new fan.sys.StrBufOutStream(buf);
  28. new fanx_ObjEncoder(out, null).writeObj(obj);
  29. return buf.toStr();
  30. }
  31. //////////////////////////////////////////////////////////////////////////
  32. // Write
  33. //////////////////////////////////////////////////////////////////////////
  34. fanx_ObjEncoder.prototype.writeObj = function(obj)
  35. {
  36. if (obj == null)
  37. {
  38. this.w("null");
  39. return;
  40. }
  41. var t = typeof obj;
  42. if (t === "boolean") { this.w(obj.toString()); return; }
  43. if (t === "number") { this.w(obj.toString()); return; }
  44. if (t === "string") { this.wStrLiteral(obj.toString(), '"'); return; }
  45. var f = obj.$fanType;
  46. if (f === fan.sys.Float.$type) { fan.sys.Float.encode(obj, this); return; }
  47. if (f === fan.sys.Decimal.$type) { fan.sys.Decimal.encode(obj, this); return; }
  48. if (obj.$literalEncode)
  49. {
  50. obj.$literalEncode(this);
  51. return;
  52. }
  53. var type = fan.sys.ObjUtil.$typeof(obj);
  54. var ser = type.facet(fan.sys.Serializable.$type, false);
  55. if (ser != null)
  56. {
  57. if (ser.m_simple)
  58. this.writeSimple(type, obj);
  59. else
  60. this.writeComplex(type, obj, ser);
  61. }
  62. else
  63. {
  64. if (this.skipErrors) // NOTE: /* not playing nice in str - escape as unicode char
  65. this.w("null /\u002A Not serializable: ").w(type.qname()).w(" */");
  66. else
  67. throw fan.sys.IOErr.make("Not serializable: " + type);
  68. }
  69. }
  70. //////////////////////////////////////////////////////////////////////////
  71. // Simple
  72. //////////////////////////////////////////////////////////////////////////
  73. fanx_ObjEncoder.prototype.writeSimple = function(type, obj)
  74. {
  75. var str = fan.sys.ObjUtil.toStr(obj);
  76. this.wType(type).w('(').wStrLiteral(str, '"').w(')');
  77. }
  78. //////////////////////////////////////////////////////////////////////////
  79. // Complex
  80. //////////////////////////////////////////////////////////////////////////
  81. fanx_ObjEncoder.prototype.writeComplex = function(type, obj, ser)
  82. {
  83. this.wType(type);
  84. var first = true;
  85. var defObj = null;
  86. if (this.skipDefaults) defObj = fan.sys.ObjUtil.$typeof(obj).make();
  87. var fields = type.fields();
  88. for (var i=0; i<fields.size(); ++i)
  89. {
  90. var f = fields.get(i);
  91. // skip static, transient, and synthetic (once) fields
  92. if (f.isStatic() || f.isSynthetic() || f.hasFacet(fan.sys.Transient.$type))
  93. continue;
  94. // get the value
  95. var val = f.get(obj);
  96. // if skipping defaults
  97. if (defObj != null)
  98. {
  99. var defVal = f.get(defObj);
  100. if (fan.sys.ObjUtil.equals(val, defVal)) continue;
  101. }
  102. // if first then open braces
  103. if (first) { this.w('\n').wIndent().w('{').w('\n'); this.level++; first = false; }
  104. // field name =
  105. this.wIndent().w(f.$name()).w('=');
  106. // field value
  107. this.curFieldType = f.type().toNonNullable();
  108. this.writeObj(val);
  109. this.curFieldType = null;
  110. this.w('\n');
  111. }
  112. // if collection
  113. if (ser.m_collection)
  114. first = this.writeCollectionItems(type, obj, first);
  115. // if we output fields, then close braces
  116. if (!first) { this.level--; this.wIndent().w('}'); }
  117. }
  118. //////////////////////////////////////////////////////////////////////////
  119. // Collection (@collection)
  120. //////////////////////////////////////////////////////////////////////////
  121. fanx_ObjEncoder.prototype.writeCollectionItems = function(type, obj, first)
  122. {
  123. // lookup each method
  124. var m = type.method("each", false);
  125. if (m == null) throw fan.sys.IOErr.make("Missing " + type.qname() + ".each");
  126. // call each(it)
  127. var enc = this;
  128. var it = fan.sys.Func.make(
  129. fan.sys.List.make(fan.sys.Param.$type),
  130. fan.sys.Void.$type,
  131. function(obj)
  132. {
  133. if (first) { enc.w('\n').wIndent().w('{').w('\n'); enc.level++; first = false; }
  134. enc.wIndent();
  135. enc.writeObj(obj);
  136. enc.w(',').w('\n');
  137. return null;
  138. });
  139. m.invoke(obj, fan.sys.List.make(fan.sys.Obj.$type, [it]));
  140. return first;
  141. }
  142. //////////////////////////////////////////////////////////////////////////
  143. // List
  144. //////////////////////////////////////////////////////////////////////////
  145. fanx_ObjEncoder.prototype.writeList = function(list)
  146. {
  147. // get of type
  148. var of = list.of();
  149. // decide if we're going output as single or multi-line format
  150. var nl = this.isMultiLine(of);
  151. // figure out if we can use an inferred type
  152. var inferred = false;
  153. if (this.curFieldType != null && (this.curFieldType instanceof fan.sys.ListType))
  154. {
  155. inferred = true;
  156. }
  157. // clear field type, so it doesn't get used for inference again
  158. this.curFieldType = null;
  159. // if we don't have an inferred type, then prefix of type
  160. if (!inferred) this.wType(of);
  161. // handle empty list
  162. var size = list.size();
  163. if (size == 0) { this.w("[,]"); return; }
  164. // items
  165. if (nl) this.w('\n').wIndent();
  166. this.w('[');
  167. this.level++;
  168. for (var i=0; i<size; ++i)
  169. {
  170. if (i > 0) this.w(',');
  171. if (nl) this.w('\n').wIndent();
  172. this.writeObj(list.get(i));
  173. }
  174. this.level--;
  175. if (nl) this.w('\n').wIndent();
  176. this.w(']');
  177. }
  178. //////////////////////////////////////////////////////////////////////////
  179. // Map
  180. //////////////////////////////////////////////////////////////////////////
  181. fanx_ObjEncoder.prototype.writeMap = function(map)
  182. {
  183. // get k,v type
  184. var t = map.$typeof();
  185. // decide if we're going output as single or multi-line format
  186. var nl = this.isMultiLine(t.k) || this.isMultiLine(t.v);
  187. // figure out if we can use an inferred type
  188. var inferred = false;
  189. if (this.curFieldType != null && (this.curFieldType instanceof fan.sys.MapType))
  190. {
  191. inferred = true;
  192. }
  193. // clear field type, so it doesn't get used for inference again
  194. this.curFieldType = null;
  195. // if we don't have an inferred type, then prefix of type
  196. if (!inferred) this.wType(t);
  197. // handle empty map
  198. if (map.isEmpty()) { this.w("[:]"); return; }
  199. // items
  200. this.level++;
  201. this.w('[');
  202. var first = true;
  203. var keys = map.keys();
  204. for (var i=0; i<keys.size(); i++)
  205. {
  206. if (first) first = false; else this.w(',');
  207. if (nl) this.w('\n').wIndent();
  208. var key = keys.get(i);
  209. var val = map.get(key);
  210. this.writeObj(key); this.w(':'); this.writeObj(val);
  211. }
  212. this.w(']');
  213. this.level--;
  214. }
  215. fanx_ObjEncoder.prototype.isMultiLine = function(t)
  216. {
  217. return t.pod() != fan.sys.Pod.$sysPod;
  218. }
  219. //////////////////////////////////////////////////////////////////////////
  220. // Output
  221. //////////////////////////////////////////////////////////////////////////
  222. fanx_ObjEncoder.prototype.wType = function(t)
  223. {
  224. return this.w(t.signature());
  225. }
  226. fanx_ObjEncoder.prototype.wStrLiteral = function(s, quote)
  227. {
  228. var len = s.length;
  229. this.w(quote);
  230. // NOTE: these escape sequences are duplicated in FanStr.toCode()
  231. for (var i=0; i<len; ++i)
  232. {
  233. var c = s.charAt(i);
  234. switch (c)
  235. {
  236. case '\n': this.w('\\').w('n'); break;
  237. case '\r': this.w('\\').w('r'); break;
  238. case '\f': this.w('\\').w('f'); break;
  239. case '\t': this.w('\\').w('t'); break;
  240. case '\\': this.w('\\').w('\\'); break;
  241. case '"': if (quote == '"') this.w('\\').w('"'); else this.w(c); break;
  242. case '`': if (quote == '`') this.w('\\').w('`'); else this.w(c); break;
  243. case '$': this.w('\\').w('$'); break;
  244. default: this.w(c);
  245. }
  246. }
  247. return this.w(quote);
  248. }
  249. fanx_ObjEncoder.prototype.wIndent = function()
  250. {
  251. var num = this.level * this.indent;
  252. for (var i=0; i<num; ++i) this.w(' ');
  253. return this;
  254. }
  255. fanx_ObjEncoder.prototype.w = function(s)
  256. {
  257. var len = s.length;
  258. for (var i=0; i<len; ++i)
  259. this.out.writeChar(s.charCodeAt(i));
  260. return this;
  261. }
  262. //////////////////////////////////////////////////////////////////////////
  263. // Options
  264. //////////////////////////////////////////////////////////////////////////
  265. fanx_ObjEncoder.prototype.initOptions = function(options)
  266. {
  267. this.indent = fanx_ObjEncoder.option(options, "indent", this.indent);
  268. this.skipDefaults = fanx_ObjEncoder.option(options, "skipDefaults", this.skipDefaults);
  269. this.skipErrors = fanx_ObjEncoder.option(options, "skipErrors", this.skipErrors);
  270. }
  271. fanx_ObjEncoder.option = function(options, name, def)
  272. {
  273. var val = options.get(name);
  274. if (val == null) return def;
  275. return val;
  276. }