/src/sys/java/fanx/serial/ObjEncoder.java
Java | 367 lines | 261 code | 47 blank | 59 comment | 46 complexity | f65ccf76bc7c8867ec91d481dc3c2e3a MD5 | raw file
Possible License(s): CC-BY-SA-3.0
- //
- // Copyright (c) 2007, Brian Frank and Andy Frank
- // Licensed under the Academic Free License version 3.0
- //
- // History:
- // 18 Aug 07 Brian Frank Creation
- //
- package fanx.serial;
- import java.math.BigDecimal;
- import java.util.Iterator;
- import java.util.Map.Entry;
- import fan.sys.*;
- import fanx.util.*;
- /**
- * ObjEncoder serializes an object to an output stream.
- */
- public class ObjEncoder
- {
- //////////////////////////////////////////////////////////////////////////
- // Static
- //////////////////////////////////////////////////////////////////////////
- public static String encode(Object obj)
- {
- StrBufOutStream out = new StrBufOutStream();
- new ObjEncoder(out, null).writeObj(obj);
- return out.string();
- }
- //////////////////////////////////////////////////////////////////////////
- // Constructor
- //////////////////////////////////////////////////////////////////////////
- public ObjEncoder(OutStream out, Map options)
- {
- this.out = out;
- if (options != null) initOptions(options);
- }
- //////////////////////////////////////////////////////////////////////////
- // Write
- //////////////////////////////////////////////////////////////////////////
- public void writeObj(Object obj)
- {
- if (obj == null)
- {
- w("null");
- return;
- }
- if (obj.getClass().getName().charAt(0) == 'j')
- {
- if (obj instanceof Boolean) { w(obj.toString()); return; }
- if (obj instanceof String) { wStrLiteral(obj.toString(), '"'); return; }
- if (obj instanceof Long) { w(obj.toString()); return; }
- if (obj instanceof Double) { FanFloat.encode((Double)obj, this); return; }
- if (obj instanceof BigDecimal) { FanDecimal.encode((BigDecimal)obj, this); return; }
- }
- if (obj instanceof Literal)
- {
- ((Literal)obj).encode(this);
- return;
- }
- Type type = FanObj.typeof(obj);
- Serializable ser = (Serializable)type.facet(Sys.SerializableType, false);
- if (ser != null)
- {
- if (ser.simple)
- writeSimple(type, obj);
- else
- writeComplex(type, obj, ser);
- }
- else
- {
- if (skipErrors)
- w("null /* Not serializable: ").w(type.qname()).w(" */");
- else
- throw IOErr.make("Not serializable: " + type);
- }
- }
- //////////////////////////////////////////////////////////////////////////
- // Simple
- //////////////////////////////////////////////////////////////////////////
- private void writeSimple(Type type, Object obj)
- {
- wType(type).w('(').wStrLiteral(FanObj.toStr(obj), '"').w(')');
- }
- //////////////////////////////////////////////////////////////////////////
- // Complex
- //////////////////////////////////////////////////////////////////////////
- private void writeComplex(Type type, Object obj, Serializable ser)
- {
- wType(type);
- boolean first = true;
- Object defObj = null;
- if (skipDefaults) defObj = FanObj.typeof(obj).make();
- List fields = type.fields();
- for (int i=0; i<fields.sz(); ++i)
- {
- Field f = (Field)fields.get(i);
- // skip static, transient, and synthetic (once) fields
- if (f.isStatic() || f.isSynthetic() || f.hasFacet(Sys.TransientType))
- continue;
- // get the value
- Object val = f.get(obj);
- // if skipping defaults
- if (defObj != null)
- {
- Object defVal = f.get(defObj);
- if (OpUtil.compareEQ(val, defVal)) continue;
- }
- // if first then open braces
- if (first) { w('\n').wIndent().w('{').w('\n'); level++; first = false; }
- // field name =
- wIndent().w(f.name()).w('=');
- // field value
- curFieldType = f.type().toNonNullable();
- writeObj(val);
- curFieldType = null;
- w('\n');
- }
- // if collection
- if (ser.collection)
- first = writeCollectionItems(type, obj, first);
- // if we output fields, then close braces
- if (!first) { level--; wIndent().w('}'); }
- }
- //////////////////////////////////////////////////////////////////////////
- // Collection (@collection)
- //////////////////////////////////////////////////////////////////////////
- private boolean writeCollectionItems(Type type, Object obj, boolean first)
- {
- // lookup each method
- Method m = type.method("each", false);
- if (m == null) throw IOErr.make("Missing " + type.qname() + ".each");
- // call each(it)
- EachIterator it = new EachIterator(first);
- m.invoke(obj, new Object[] { it });
- return it.first;
- }
- static final FuncType eachIteratorType = new FuncType(new Type[] { Sys.ObjType }, Sys.VoidType);
- class EachIterator extends Func.Indirect1
- {
- EachIterator (boolean first) { super(eachIteratorType); this.first = first; }
- public Object call(Object obj)
- {
- if (first) { w('\n').wIndent().w('{').w('\n'); level++; first = false; }
- wIndent();
- writeObj(obj);
- w(',').w('\n');
- return null;
- }
- boolean first;
- }
- //////////////////////////////////////////////////////////////////////////
- // List
- //////////////////////////////////////////////////////////////////////////
- public void writeList(List list)
- {
- // get of type
- Type of = list.of();
- // decide if we're going output as single or multi-line format
- boolean nl = isMultiLine(of);
- // figure out if we can use an inferred type
- boolean inferred = false;
- if (curFieldType != null && curFieldType.fits(Sys.ListType))
- {
- inferred = true;
- }
- // clear field type, so it doesn't get used for inference again
- curFieldType = null;
- // if we don't have an inferred type, then prefix of type
- if (!inferred) wType(of);
- // handle empty list
- int size = list.sz();
- if (size == 0) { w("[,]"); return; }
- // items
- if (nl) w('\n').wIndent();
- w('[');
- level++;
- for (int i=0; i<size; ++i)
- {
- if (i > 0) w(',');
- if (nl) w('\n').wIndent();
- writeObj(list.get(i));
- }
- level--;
- if (nl) w('\n').wIndent();
- w(']');
- }
- //////////////////////////////////////////////////////////////////////////
- // Map
- //////////////////////////////////////////////////////////////////////////
- public void writeMap(Map map)
- {
- // get k,v type
- MapType t = (MapType)map.typeof();
- // decide if we're going output as single or multi-line format
- boolean nl = isMultiLine(t.k) || isMultiLine(t.v);
- // figure out if we can use an inferred type
- boolean inferred = false;
- if (curFieldType != null && curFieldType.fits(Sys.MapType))
- {
- inferred = true;
- }
- // clear field type, so it doesn't get used for inference again
- curFieldType = null;
- // if we don't have an inferred type, then prefix of type
- if (!inferred) wType(t);
- // handle empty map
- if (map.isEmpty()) { w("[:]"); return; }
- // items
- level++;
- w('[');
- boolean first = true;
- Iterator it = map.pairsIterator();
- while (it.hasNext())
- {
- Entry e = (Entry)it.next();
- if (first) first = false; else w(',');
- if (nl) w('\n').wIndent();
- Object key = e.getKey();
- Object val = e.getValue();
- writeObj(key); w(':'); writeObj(val);
- }
- w(']');
- level--;
- }
- private boolean isMultiLine(Type t)
- {
- return t.pod() != Sys.sysPod;
- }
- //////////////////////////////////////////////////////////////////////////
- // Output
- //////////////////////////////////////////////////////////////////////////
- public final ObjEncoder wType(Type t)
- {
- return w(t.signature());
- }
- public final ObjEncoder wStrLiteral(String s, char quote)
- {
- int len = s.length();
- w(quote);
- // NOTE: these escape sequences are duplicated in FanStr.toCode()
- for (int i=0; i<len; ++i)
- {
- char c = s.charAt(i);
- switch (c)
- {
- case '\n': w('\\').w('n'); break;
- case '\r': w('\\').w('r'); break;
- case '\f': w('\\').w('f'); break;
- case '\t': w('\\').w('t'); break;
- case '\\': w('\\').w('\\'); break;
- case '"': if (quote == '"') w('\\').w('"'); else w(c); break;
- case '`': if (quote == '`') w('\\').w('`'); else w(c); break;
- case '$': w('\\').w('$'); break;
- default: w(c);
- }
- }
- return w(quote);
- }
- public final ObjEncoder wIndent()
- {
- int num = level*indent;
- for (int i=0; i<num; ++i) w(' ');
- return this;
- }
- public final ObjEncoder w(String s)
- {
- int len = s.length();
- for (int i=0; i<len; ++i)
- out.writeChar(s.charAt(i));
- return this;
- }
- public final ObjEncoder w(char ch)
- {
- out.writeChar(ch);
- return this;
- }
- //////////////////////////////////////////////////////////////////////////
- // Options
- //////////////////////////////////////////////////////////////////////////
- private void initOptions(Map options)
- {
- indent = option(options, "indent", indent);
- skipDefaults = option(options, "skipDefaults", skipDefaults);
- skipErrors = option(options, "skipErrors", skipErrors);
- }
- private static int option(Map options, String name, int def)
- {
- Long val = (Long)options.get(name);
- if (val == null) return def;
- return val.intValue();
- }
- private static boolean option(Map options, String name, boolean def)
- {
- Boolean val = (Boolean)options.get(name);
- if (val == null) return def;
- return val;
- }
- //////////////////////////////////////////////////////////////////////////
- // Fields
- //////////////////////////////////////////////////////////////////////////
- OutStream out;
- int level = 0;
- int indent = 0;
- boolean skipDefaults = false;
- boolean skipErrors = false;
- Type curFieldType;
- }