123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- /*
- * Copyright (C)2005-2016 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;
- @:noDoc
- typedef TypeResolver = {
- function resolveClass( name : String ) : Class<Dynamic>;
- function resolveEnum( name : String ) : Enum<Dynamic>;
- }
- /**
- The `Unserializer` class is the complement to the `Serializer` class. It parses
- a serialization `String` and creates objects from the contained data.
- This class can be used in two ways:
- - create a `new Unserializer()` instance with a given serialization
- String, then call its `unserialize()` method until all values are
- extracted
- - call `Unserializer.run()` to unserialize a single value from a given
- String
- The specification of the serialization format can be found here:
- <https://haxe.org/manual/serialization/format>
- **/
- class Unserializer {
- /**
- This value can be set to use custom type resolvers.
- A type resolver finds a `Class` or `Enum` instance from a given `String`.
- By default, the Haxe `Type` Api is used.
- A type resolver must provide two methods:
- 1. `resolveClass(name:String):Class<Dynamic>` is called to determine a
- `Class` from a class name
- 2. `resolveEnum(name:String):Enum<Dynamic>` is called to determine an
- `Enum` from an enum name
- This value is applied when a new `Unserializer` instance is created.
- Changing it afterwards has no effect on previously created instances.
- **/
- public static var DEFAULT_RESOLVER : TypeResolver = new DefaultResolver();
- static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:";
- #if !neko
- static var CODES = null;
- static function initCodes() {
- var codes =
- #if flash
- new flash.utils.ByteArray();
- #else
- new Array();
- #end
- for( i in 0...BASE64.length )
- codes[StringTools.fastCodeAt(BASE64,i)] = i;
- return codes;
- }
- #end
- var buf : String;
- var pos : Int;
- var length : Int;
- var cache : Array<Dynamic>;
- var scache : Array<String>;
- var resolver : TypeResolver;
- #if neko
- var upos : Int;
- #end
- /**
- Creates a new Unserializer instance, with its internal buffer
- initialized to `buf`.
- This does not parse `buf` immediately. It is parsed only when calls to
- `this.unserialize` are made.
- Each Unserializer instance maintains its own cache.
- **/
- public function new( buf : String ) {
- this.buf = buf;
- length = buf.length;
- pos = 0;
- #if neko
- upos = 0;
- #end
- scache = new Array();
- cache = new Array();
- var r = DEFAULT_RESOLVER;
- if( r == null ) {
- r = new DefaultResolver();
- DEFAULT_RESOLVER = r;
- }
- resolver = r;
- }
- /**
- Sets the type resolver of `this` Unserializer instance to `r`.
- If `r` is null, a special resolver is used which returns null for all
- input values.
- See `DEFAULT_RESOLVER` for more information on type resolvers.
- **/
- public function setResolver( r ) {
- if( r == null )
- resolver = NullResolver.instance;
- else
- resolver = r;
- }
- /**
- Gets the type resolver of `this` Unserializer instance.
- See `DEFAULT_RESOLVER` for more information on type resolvers.
- **/
- public function getResolver() {
- return resolver;
- }
- inline function get(p) : Int {
- return StringTools.fastCodeAt(buf, p);
- }
- function readDigits() {
- var k = 0;
- var s = false;
- var fpos = pos;
- while( true ) {
- var c = get(pos);
- if( StringTools.isEof(c) )
- break;
- if( c == "-".code ) {
- if( pos != fpos )
- break;
- s = true;
- pos++;
- continue;
- }
- if( c < "0".code || c > "9".code )
- break;
- k = k * 10 + (c - "0".code);
- pos++;
- }
- if( s )
- k *= -1;
- return k;
- }
- function readFloat() {
- var p1 = pos;
- while( true ) {
- var c = get(pos);
- if( StringTools.isEof(c)) break;
- // + - . , 0-9
- if( (c >= 43 && c < 58) || c == "e".code || c == "E".code )
- pos++;
- else
- break;
- }
- return Std.parseFloat(buf.substr(p1,pos-p1));
- }
- function unserializeObject(o) {
- while( true ) {
- if( pos >= length )
- throw "Invalid object";
- if( get(pos) == "g".code )
- break;
- var k = unserialize();
- if( !Std.is(k,String) )
- throw "Invalid object key";
- var v = unserialize();
- Reflect.setField(o,k,v);
- }
- pos++;
- }
- function unserializeEnum( edecl, tag ) {
- if( get(pos++) != ":".code )
- throw "Invalid enum format";
- var nargs = readDigits();
- if( nargs == 0 )
- return Type.createEnum(edecl,tag);
- var args = new Array();
- while( nargs-- > 0 )
- args.push(unserialize());
- return Type.createEnum(edecl,tag,args);
- }
- /**
- Unserializes the next part of `this` Unserializer instance and returns
- the according value.
- This function may call `this.resolver.resolveClass` to determine a
- Class from a String, and `this.resolver.resolveEnum` to determine an
- Enum from a String.
- If `this` Unserializer instance contains no more or invalid data, an
- exception is thrown.
- This operation may fail on structurally valid data if a type cannot be
- resolved or if a field cannot be set. This can happen when unserializing
- Strings that were serialized on a different Haxe target, in which the
- serialization side has to make sure not to include platform-specific
- data.
- Classes are created from `Type.createEmptyInstance`, which means their
- constructors are not called.
- **/
- public function unserialize() : Dynamic {
- switch( get(pos++) ) {
- case "n".code:
- return null;
- case "t".code:
- return true;
- case "f".code:
- return false;
- case "z".code:
- return 0;
- case "i".code:
- return readDigits();
- case "d".code:
- return readFloat();
- case "y".code:
- var len = readDigits();
- if( get(pos++) != ":".code || length - pos < len )
- throw "Invalid string length";
- var s = buf.substr(pos,len);
- pos += len;
- s = StringTools.urlDecode(s);
- scache.push(s);
- return s;
- case "k".code:
- return Math.NaN;
- case "m".code:
- return Math.NEGATIVE_INFINITY;
- case "p".code:
- return Math.POSITIVE_INFINITY;
- case "a".code:
- var buf = buf;
- var a = new Array<Dynamic>();
- cache.push(a);
- while( true ) {
- var c = get(pos);
- if( c == "h".code ) {
- pos++;
- break;
- }
- if( c == "u".code ) {
- pos++;
- var n = readDigits();
- a[a.length+n-1] = null;
- } else
- a.push(unserialize());
- }
- return a;
- case "o".code:
- var o = {};
- cache.push(o);
- unserializeObject(o);
- return o;
- case "r".code:
- var n = readDigits();
- if( n < 0 || n >= cache.length )
- throw "Invalid reference";
- return cache[n];
- case "R".code:
- var n = readDigits();
- if( n < 0 || n >= scache.length )
- throw "Invalid string reference";
- return scache[n];
- case "x".code:
- throw unserialize();
- case "c".code:
- var name = unserialize();
- var cl = resolver.resolveClass(name);
- if( cl == null )
- throw "Class not found " + name;
- var o = Type.createEmptyInstance(cl);
- cache.push(o);
- unserializeObject(o);
- return o;
- case "w".code:
- var name = unserialize();
- var edecl = resolver.resolveEnum(name);
- if( edecl == null )
- throw "Enum not found " + name;
- var e = unserializeEnum(edecl, unserialize());
- cache.push(e);
- return e;
- case "j".code:
- var name = unserialize();
- var edecl = resolver.resolveEnum(name);
- if( edecl == null )
- throw "Enum not found " + name;
- pos++; /* skip ':' */
- var index = readDigits();
- var tag = Type.getEnumConstructs(edecl)[index];
- if( tag == null )
- throw "Unknown enum index "+name+"@"+index;
- var e = unserializeEnum(edecl, tag);
- cache.push(e);
- return e;
- case "l".code:
- var l = new List();
- cache.push(l);
- var buf = buf;
- while( get(pos) != "h".code )
- l.add(unserialize());
- pos++;
- return l;
- case "b".code:
- var h = new haxe.ds.StringMap();
- cache.push(h);
- var buf = buf;
- while( get(pos) != "h".code ) {
- var s = unserialize();
- h.set(s,unserialize());
- }
- pos++;
- return h;
- case "q".code:
- var h = new haxe.ds.IntMap();
- cache.push(h);
- var buf = buf;
- var c = get(pos++);
- while( c == ":".code ) {
- var i = readDigits();
- h.set(i,unserialize());
- c = get(pos++);
- }
- if( c != "h".code )
- throw "Invalid IntMap format";
- return h;
- case "M".code:
- var h = new haxe.ds.ObjectMap();
- cache.push(h);
- var buf = buf;
- while( get(pos) != "h".code ) {
- var s = unserialize();
- h.set(s,unserialize());
- }
- pos++;
- return h;
- case "v".code:
- var d;
- if( get(pos) >= '0'.code && get(pos) <= '9'.code &&
- get(pos + 1) >= '0'.code && get(pos + 1) <= '9'.code &&
- get(pos + 2) >= '0'.code && get(pos + 2) <= '9'.code &&
- get(pos + 3) >= '0'.code && get(pos + 3) <= '9'.code &&
- get(pos + 4) == '-'.code
- ) {
- // Included for backwards compatibility
- d = Date.fromString(buf.substr(pos,19));
- pos += 19;
- } else
- d = Date.fromTime(readFloat());
- cache.push(d);
- return d;
- case "s".code:
- var len = readDigits();
- var buf = buf;
- if( get(pos++) != ":".code || length - pos < len )
- throw "Invalid bytes length";
- #if neko
- var bytes = haxe.io.Bytes.ofData( base_decode(untyped buf.substr(pos,len).__s,untyped BASE64.__s) );
- #else
- var codes = CODES;
- if( codes == null ) {
- codes = initCodes();
- CODES = codes;
- }
- var i = pos;
- var rest = len & 3;
- var size = (len >> 2) * 3 + ((rest >= 2) ? rest - 1 : 0);
- var max = i + (len - rest);
- var bytes = haxe.io.Bytes.alloc(size);
- var bpos = 0;
- while( i < max ) {
- var c1 = codes[StringTools.fastCodeAt(buf,i++)];
- var c2 = codes[StringTools.fastCodeAt(buf,i++)];
- bytes.set(bpos++,(c1 << 2) | (c2 >> 4));
- var c3 = codes[StringTools.fastCodeAt(buf,i++)];
- bytes.set(bpos++,(c2 << 4) | (c3 >> 2));
- var c4 = codes[StringTools.fastCodeAt(buf,i++)];
- bytes.set(bpos++,(c3 << 6) | c4);
- }
- if( rest >= 2 ) {
- var c1 = codes[StringTools.fastCodeAt(buf,i++)];
- var c2 = codes[StringTools.fastCodeAt(buf,i++)];
- bytes.set(bpos++,(c1 << 2) | (c2 >> 4));
- if( rest == 3 ) {
- var c3 = codes[StringTools.fastCodeAt(buf,i++)];
- bytes.set(bpos++,(c2 << 4) | (c3 >> 2));
- }
- }
- #end
- pos += len;
- cache.push(bytes);
- return bytes;
- case "C".code:
- var name = unserialize();
- var cl = resolver.resolveClass(name);
- if( cl == null )
- throw "Class not found " + name;
- var o : Dynamic = Type.createEmptyInstance(cl);
- cache.push(o);
- o.hxUnserialize(this);
- if( get(pos++) != "g".code )
- throw "Invalid custom data";
- return o;
- case "A".code:
- var name = unserialize();
- var cl = resolver.resolveClass(name);
- if( cl == null )
- throw "Class not found " + name;
- return cl;
- case "B".code:
- var name = unserialize();
- var e = resolver.resolveEnum(name);
- if( e == null )
- throw "Enum not found " + name;
- return e;
- default:
- }
- pos--;
- throw ("Invalid char "+buf.charAt(pos)+" at position "+pos);
- }
- /**
- Unserializes `v` and returns the according value.
- This is a convenience function for creating a new instance of
- Unserializer with `v` as buffer and calling its unserialize() method
- once.
- **/
- public static function run( v : String ) : Dynamic {
- return new Unserializer(v).unserialize();
- }
- #if neko
- static var base_decode = neko.Lib.load("std","base_decode",2);
- #end
- }
- private class DefaultResolver {
- public function new() {}
- @:final public inline function resolveClass(name:String):Class<Dynamic> return Type.resolveClass(name);
- @:final public inline function resolveEnum(name:String):Enum<Dynamic> return Type.resolveEnum(name);
- }
- private class NullResolver {
- function new() {}
- @:final public inline function resolveClass(name:String):Class<Dynamic> return null;
- @:final public inline function resolveEnum(name:String):Enum<Dynamic> return null;
- public static var instance(get,null):NullResolver;
- inline static function get_instance():NullResolver {
- if (instance == null) instance = new NullResolver();
- return instance;
- }
- }
|