592 lines
15 KiB
Haxe
592 lines
15 KiB
Haxe
/*
|
|
* Copyright (C)2005-2019 Haxe Foundation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
package haxe;
|
|
|
|
import haxe.ds.List;
|
|
|
|
/**
|
|
The Serializer class can be used to encode values and objects into a `String`,
|
|
from which the `Unserializer` class can recreate the original representation.
|
|
|
|
This class can be used in two ways:
|
|
|
|
- create a `new Serializer()` instance, call its `serialize()` method with
|
|
any argument and finally retrieve the String representation from
|
|
`toString()`
|
|
- call `Serializer.run()` to obtain the serialized representation of a
|
|
single argument
|
|
|
|
Serialization is guaranteed to work for all haxe-defined classes, but may
|
|
or may not work for instances of external/native classes.
|
|
|
|
The specification of the serialization format can be found here:
|
|
<https://haxe.org/manual/std-serialization-format.html>
|
|
**/
|
|
class Serializer {
|
|
/**
|
|
If the values you are serializing can contain circular references or
|
|
objects repetitions, you should set `USE_CACHE` to true to prevent
|
|
infinite loops.
|
|
|
|
This may also reduce the size of serialization Strings at the expense of
|
|
performance.
|
|
|
|
This value can be changed for individual instances of `Serializer` by
|
|
setting their `useCache` field.
|
|
**/
|
|
public static var USE_CACHE = false;
|
|
|
|
/**
|
|
Use constructor indexes for enums instead of names.
|
|
|
|
This may reduce the size of serialization Strings, but makes them less
|
|
suited for long-term storage: If constructors are removed or added from
|
|
the enum, the indices may no longer match.
|
|
|
|
This value can be changed for individual instances of `Serializer` by
|
|
setting their `useEnumIndex` field.
|
|
**/
|
|
public static var USE_ENUM_INDEX = false;
|
|
|
|
static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:";
|
|
static var BASE64_CODES = null;
|
|
|
|
var buf:StringBuf;
|
|
var cache:Array<Dynamic>;
|
|
var shash:haxe.ds.StringMap<Int>;
|
|
var scount:Int;
|
|
|
|
/**
|
|
The individual cache setting for `this` Serializer instance.
|
|
|
|
See `USE_CACHE` for a complete description.
|
|
**/
|
|
public var useCache:Bool;
|
|
|
|
/**
|
|
The individual enum index setting for `this` Serializer instance.
|
|
|
|
See `USE_ENUM_INDEX` for a complete description.
|
|
**/
|
|
public var useEnumIndex:Bool;
|
|
|
|
/**
|
|
Creates a new Serializer instance.
|
|
|
|
Subsequent calls to `this.serialize` will append values to the
|
|
internal buffer of this String. Once complete, the contents can be
|
|
retrieved through a call to `this.toString`.
|
|
|
|
Each `Serializer` instance maintains its own cache if `this.useCache` is
|
|
`true`.
|
|
**/
|
|
public function new() {
|
|
buf = new StringBuf();
|
|
cache = new Array();
|
|
useCache = USE_CACHE;
|
|
useEnumIndex = USE_ENUM_INDEX;
|
|
shash = new haxe.ds.StringMap();
|
|
scount = 0;
|
|
}
|
|
|
|
/**
|
|
Return the String representation of `this` Serializer.
|
|
|
|
The exact format specification can be found here:
|
|
https://haxe.org/manual/serialization/format
|
|
**/
|
|
public function toString() {
|
|
return buf.toString();
|
|
}
|
|
|
|
/* prefixes :
|
|
a : array
|
|
b : hash
|
|
c : class
|
|
d : Float
|
|
e : reserved (float exp)
|
|
f : false
|
|
g : object end
|
|
h : array/list/hash end
|
|
i : Int
|
|
j : enum (by index)
|
|
k : NaN
|
|
l : list
|
|
m : -Inf
|
|
n : null
|
|
o : object
|
|
p : +Inf
|
|
q : haxe.ds.IntMap
|
|
r : reference
|
|
s : bytes (base64)
|
|
t : true
|
|
u : array nulls
|
|
v : date
|
|
w : enum
|
|
x : exception
|
|
y : urlencoded string
|
|
z : zero
|
|
A : Class<Dynamic>
|
|
B : Enum<Dynamic>
|
|
M : haxe.ds.ObjectMap
|
|
C : custom
|
|
*/
|
|
function serializeString(s:String) {
|
|
var x = shash.get(s);
|
|
if (x != null) {
|
|
buf.add("R");
|
|
buf.add(x);
|
|
return;
|
|
}
|
|
shash.set(s, scount++);
|
|
#if old_serialize
|
|
// no more support for -D old_serialize due to 'j' reuse
|
|
#if error
|
|
#end
|
|
#end
|
|
buf.add("y");
|
|
s = StringTools.urlEncode(s);
|
|
buf.add(s.length);
|
|
buf.add(":");
|
|
buf.add(s);
|
|
}
|
|
|
|
function serializeRef(v:Dynamic) {
|
|
#if js
|
|
var vt = js.Syntax.typeof(v);
|
|
#end
|
|
for (i in 0...cache.length) {
|
|
#if js
|
|
var ci = cache[i];
|
|
if (js.Syntax.typeof(ci) == vt && ci == v) {
|
|
#else
|
|
if (cache[i] == v) {
|
|
#end
|
|
buf.add("r");
|
|
buf.add(i);
|
|
return true;
|
|
}
|
|
}
|
|
cache.push(v);
|
|
return false;
|
|
}
|
|
|
|
#if flash
|
|
// only the instance variables
|
|
|
|
function serializeClassFields(v:Dynamic, c:Dynamic) {
|
|
var xml:flash.xml.XML = untyped __global__["flash.utils.describeType"](c);
|
|
var vars = xml.factory[0].child("variable");
|
|
for (i in 0...vars.length()) {
|
|
var f = vars[i].attribute("name").toString();
|
|
if (!v.hasOwnProperty(f))
|
|
continue;
|
|
serializeString(f);
|
|
serialize(Reflect.field(v, f));
|
|
}
|
|
buf.add("g");
|
|
}
|
|
#end
|
|
|
|
function serializeFields(v:{}) {
|
|
for (f in Reflect.fields(v)) {
|
|
serializeString(f);
|
|
serialize(Reflect.field(v, f));
|
|
}
|
|
buf.add("g");
|
|
}
|
|
|
|
/**
|
|
Serializes `v`.
|
|
|
|
All haxe-defined values and objects with the exception of functions can
|
|
be serialized. Serialization of external/native objects is not
|
|
guaranteed to work.
|
|
|
|
The values of `this.useCache` and `this.useEnumIndex` may affect
|
|
serialization output.
|
|
**/
|
|
public function serialize(v:Dynamic) {
|
|
switch (Type.typeof(v)) {
|
|
case TNull:
|
|
buf.add("n");
|
|
case TInt:
|
|
var v:Int = v;
|
|
if (v == 0) {
|
|
buf.add("z");
|
|
return;
|
|
}
|
|
buf.add("i");
|
|
buf.add(v);
|
|
case TFloat:
|
|
var v:Float = v;
|
|
if (Math.isNaN(v))
|
|
buf.add("k");
|
|
else if (!Math.isFinite(v))
|
|
buf.add(if (v < 0) "m" else "p");
|
|
else {
|
|
buf.add("d");
|
|
buf.add(v);
|
|
}
|
|
case TBool:
|
|
buf.add(if (v) "t" else "f");
|
|
case TClass(c):
|
|
if (#if neko untyped c.__is_String #else c == String #end) {
|
|
serializeString(v);
|
|
return;
|
|
}
|
|
if (useCache && serializeRef(v))
|
|
return;
|
|
switch (#if (neko || cs || python) Type.getClassName(c) #else c #end) {
|
|
case #if (neko || cs || python) "Array" #else cast Array #end:
|
|
var ucount = 0;
|
|
buf.add("a");
|
|
#if (flash || python || hl)
|
|
var v:Array<Dynamic> = v;
|
|
#end
|
|
var l = #if (neko || flash || php || cs || java || python || hl || lua || eval) v.length #elseif cpp v.__length() #else __getField(v,
|
|
"length") #end;
|
|
for (i in 0...l) {
|
|
if (v[i] == null)
|
|
ucount++;
|
|
else {
|
|
if (ucount > 0) {
|
|
if (ucount == 1)
|
|
buf.add("n");
|
|
else {
|
|
buf.add("u");
|
|
buf.add(ucount);
|
|
}
|
|
ucount = 0;
|
|
}
|
|
serialize(v[i]);
|
|
}
|
|
}
|
|
if (ucount > 0) {
|
|
if (ucount == 1)
|
|
buf.add("n");
|
|
else {
|
|
buf.add("u");
|
|
buf.add(ucount);
|
|
}
|
|
}
|
|
buf.add("h");
|
|
case #if (neko || cs || python) "haxe.ds.List" #else cast List #end:
|
|
buf.add("l");
|
|
var v:List<Dynamic> = v;
|
|
for (i in v)
|
|
serialize(i);
|
|
buf.add("h");
|
|
case #if (neko || cs || python) "Date" #else cast Date #end:
|
|
var d:Date = v;
|
|
buf.add("v");
|
|
buf.add(d.getTime());
|
|
case #if (neko || cs || python) "haxe.ds.StringMap" #else cast haxe.ds.StringMap #end:
|
|
buf.add("b");
|
|
var v:haxe.ds.StringMap<Dynamic> = v;
|
|
for (k in v.keys()) {
|
|
serializeString(k);
|
|
serialize(v.get(k));
|
|
}
|
|
buf.add("h");
|
|
case #if (neko || cs || python) "haxe.ds.IntMap" #else cast haxe.ds.IntMap #end:
|
|
buf.add("q");
|
|
var v:haxe.ds.IntMap<Dynamic> = v;
|
|
for (k in v.keys()) {
|
|
buf.add(":");
|
|
buf.add(k);
|
|
serialize(v.get(k));
|
|
}
|
|
buf.add("h");
|
|
case #if (neko || cs || python) "haxe.ds.ObjectMap" #else cast haxe.ds.ObjectMap #end:
|
|
buf.add("M");
|
|
var v:haxe.ds.ObjectMap<Dynamic, Dynamic> = v;
|
|
for (k in v.keys()) {
|
|
#if (js || neko)
|
|
var id = Reflect.field(k, "__id__");
|
|
Reflect.deleteField(k, "__id__");
|
|
serialize(k);
|
|
Reflect.setField(k, "__id__", id);
|
|
#else
|
|
serialize(k);
|
|
#end
|
|
serialize(v.get(k));
|
|
}
|
|
buf.add("h");
|
|
case #if (neko || cs || python) "haxe.io.Bytes" #else cast haxe.io.Bytes #end:
|
|
var v:haxe.io.Bytes = v;
|
|
#if neko
|
|
var chars = new String(base_encode(v.getData(), untyped BASE64.__s));
|
|
buf.add("s");
|
|
buf.add(chars.length);
|
|
buf.add(":");
|
|
buf.add(chars);
|
|
#elseif php
|
|
var chars = new String(php.Global.base64_encode(v.getData()));
|
|
chars = php.Global.strtr(chars, '+/', '%:');
|
|
buf.add("s");
|
|
buf.add(chars.length);
|
|
buf.add(":");
|
|
buf.add(chars);
|
|
#else
|
|
buf.add("s");
|
|
buf.add(Math.ceil((v.length * 8) / 6));
|
|
buf.add(":");
|
|
|
|
var i = 0;
|
|
var max = v.length - 2;
|
|
var b64 = BASE64_CODES;
|
|
if (b64 == null) {
|
|
b64 = new haxe.ds.Vector(BASE64.length);
|
|
for (i in 0...BASE64.length)
|
|
b64[i] = BASE64.charCodeAt(i);
|
|
BASE64_CODES = b64;
|
|
}
|
|
while (i < max) {
|
|
var b1 = v.get(i++);
|
|
var b2 = v.get(i++);
|
|
var b3 = v.get(i++);
|
|
|
|
buf.addChar(b64[b1 >> 2]);
|
|
buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]);
|
|
buf.addChar(b64[((b2 << 2) | (b3 >> 6)) & 63]);
|
|
buf.addChar(b64[b3 & 63]);
|
|
}
|
|
if (i == max) {
|
|
var b1 = v.get(i++);
|
|
var b2 = v.get(i++);
|
|
buf.addChar(b64[b1 >> 2]);
|
|
buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]);
|
|
buf.addChar(b64[(b2 << 2) & 63]);
|
|
} else if (i == max + 1) {
|
|
var b1 = v.get(i++);
|
|
buf.addChar(b64[b1 >> 2]);
|
|
buf.addChar(b64[(b1 << 4) & 63]);
|
|
}
|
|
#end
|
|
default:
|
|
if (useCache) cache.pop();
|
|
if (#if flash try
|
|
v.hxSerialize != null
|
|
catch (e:Dynamic)
|
|
false #elseif (cs || java || python) Reflect.hasField(v,
|
|
"hxSerialize") #elseif php php.Global.method_exists(v, 'hxSerialize') #else v.hxSerialize != null #end) {
|
|
buf.add("C");
|
|
serializeString(Type.getClassName(c));
|
|
if (useCache)
|
|
cache.push(v);
|
|
v.hxSerialize(this);
|
|
buf.add("g");
|
|
} else {
|
|
buf.add("c");
|
|
serializeString(Type.getClassName(c));
|
|
if (useCache)
|
|
cache.push(v);
|
|
#if flash
|
|
serializeClassFields(v, c);
|
|
#else
|
|
serializeFields(v);
|
|
#end
|
|
}
|
|
}
|
|
case TObject:
|
|
if (Std.isOfType(v, Class)) {
|
|
var className = Type.getClassName(v);
|
|
#if (flash || cpp)
|
|
// Currently, Enum and Class are the same for flash and cpp.
|
|
// use resolveEnum to test if it is actually an enum
|
|
if (Type.resolveEnum(className) != null)
|
|
buf.add("B")
|
|
else
|
|
#end
|
|
buf.add("A");
|
|
serializeString(className);
|
|
} else if (Std.isOfType(v, Enum)) {
|
|
buf.add("B");
|
|
serializeString(Type.getEnumName(v));
|
|
} else {
|
|
if (useCache && serializeRef(v))
|
|
return;
|
|
buf.add("o");
|
|
serializeFields(v);
|
|
}
|
|
case TEnum(e):
|
|
if (useCache) {
|
|
if (serializeRef(v))
|
|
return;
|
|
cache.pop();
|
|
}
|
|
buf.add(useEnumIndex ? "j" : "w");
|
|
serializeString(Type.getEnumName(e));
|
|
#if neko
|
|
if (useEnumIndex) {
|
|
buf.add(":");
|
|
buf.add(v.index);
|
|
} else
|
|
serializeString(new String(v.tag));
|
|
buf.add(":");
|
|
if (v.args == null)
|
|
buf.add(0);
|
|
else {
|
|
var l:Int = untyped __dollar__asize(v.args);
|
|
buf.add(l);
|
|
for (i in 0...l)
|
|
serialize(v.args[i]);
|
|
}
|
|
#elseif flash
|
|
if (useEnumIndex) {
|
|
buf.add(":");
|
|
var i:Int = v.index;
|
|
buf.add(i);
|
|
} else
|
|
serializeString(v.tag);
|
|
buf.add(":");
|
|
var pl:Array<Dynamic> = v.params;
|
|
if (pl == null)
|
|
buf.add(0);
|
|
else {
|
|
buf.add(pl.length);
|
|
for (p in pl)
|
|
serialize(p);
|
|
}
|
|
#elseif cpp
|
|
var enumBase:cpp.EnumBase = v;
|
|
if (useEnumIndex) {
|
|
buf.add(":");
|
|
buf.add(enumBase.getIndex());
|
|
} else
|
|
serializeString(enumBase.getTag());
|
|
buf.add(":");
|
|
var len = enumBase.getParamCount();
|
|
buf.add(len);
|
|
for (p in 0...len)
|
|
serialize(enumBase.getParamI(p));
|
|
#elseif php
|
|
if (useEnumIndex) {
|
|
buf.add(":");
|
|
buf.add(v.index);
|
|
} else
|
|
serializeString(v.tag);
|
|
buf.add(":");
|
|
var l:Int = php.Syntax.code("count({0})", v.params);
|
|
if (l == 0 || v.params == null)
|
|
buf.add(0);
|
|
else {
|
|
buf.add(l);
|
|
for (i in 0...l) {
|
|
#if php
|
|
serialize(v.params[i]);
|
|
#end
|
|
}
|
|
}
|
|
#elseif (java || cs || python || hl || eval)
|
|
if (useEnumIndex) {
|
|
buf.add(":");
|
|
buf.add(Type.enumIndex(v));
|
|
} else
|
|
serializeString(Type.enumConstructor(v));
|
|
buf.add(":");
|
|
var arr:Array<Dynamic> = Type.enumParameters(v);
|
|
if (arr != null) {
|
|
buf.add(arr.length);
|
|
for (v in arr)
|
|
serialize(v);
|
|
} else {
|
|
buf.add("0");
|
|
}
|
|
#elseif (js && !js_enums_as_arrays)
|
|
if (useEnumIndex) {
|
|
buf.add(":");
|
|
buf.add(v._hx_index);
|
|
} else
|
|
serializeString(Type.enumConstructor(v));
|
|
buf.add(":");
|
|
var params = Type.enumParameters(v);
|
|
buf.add(params.length);
|
|
for (p in params)
|
|
serialize(p);
|
|
#else
|
|
if (useEnumIndex) {
|
|
buf.add(":");
|
|
buf.add(v[1]);
|
|
} else
|
|
serializeString(v[0]);
|
|
buf.add(":");
|
|
var l = __getField(v, "length");
|
|
buf.add(l - 2);
|
|
for (i in 2...l)
|
|
serialize(v[i]);
|
|
#end
|
|
if (useCache)
|
|
cache.push(v);
|
|
case TFunction:
|
|
throw "Cannot serialize function";
|
|
default:
|
|
#if neko
|
|
if (untyped (__i32__kind != null && __dollar__iskind(v, __i32__kind))) {
|
|
buf.add("i");
|
|
buf.add(v);
|
|
return;
|
|
}
|
|
#end
|
|
throw "Cannot serialize " + Std.string(v);
|
|
}
|
|
}
|
|
|
|
extern inline function __getField(o:Dynamic, f:String):Dynamic
|
|
return o[cast f];
|
|
|
|
public function serializeException(e:Dynamic) {
|
|
buf.add("x");
|
|
#if flash
|
|
if (untyped __is__(e, __global__["Error"])) {
|
|
var e:flash.errors.Error = e;
|
|
var s = e.getStackTrace();
|
|
if (s == null)
|
|
serialize(e.message);
|
|
else
|
|
serialize(s);
|
|
return;
|
|
}
|
|
#end
|
|
serialize(e);
|
|
}
|
|
|
|
/**
|
|
Serializes `v` and returns the String representation.
|
|
|
|
This is a convenience function for creating a new instance of
|
|
Serializer, serialize `v` into it and obtain the result through a call
|
|
to `toString()`.
|
|
**/
|
|
public static function run(v:Dynamic) {
|
|
var s = new Serializer();
|
|
s.serialize(v);
|
|
return s.toString();
|
|
}
|
|
|
|
#if neko
|
|
static var base_encode = neko.Lib.load("std", "base_encode", 2);
|
|
#end
|
|
}
|