All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ceylon.language.serialization.DeserializationContextImpl.ceylon Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
import ceylon.language.meta.declaration {
    ValueDeclaration
}
import ceylon.language.meta {
    type, 
    typeLiteral
}
import ceylon.language.meta.model {
    ClassModel
}
import ceylon.language.serialization {
    DeserializationException
}
import ceylon.language.impl {
    MemberImpl, ElementImpl
}

"Implementation of [[DeserializationContext]] using a few native helper classes."
class DeserializationContextImpl() satisfies DeserializationContext 
        given Id satisfies Object {
    """The `Item` in the instances map is either a `Partial` or the actual instance
       that's not ambiguous because `Partial` never leaks, so it's impossible
       for a client to use the API to instantiate a `Partial`
       they can only end up in the map due to our implementation.
    """
    NativeMap instances = NativeMap();
    
    """a cache of "attribute" (represented as a TypeDescriptor and an attribute name)
       to its type"""
    shared NativeMapString, Object> memberTypeCache = NativeMapString, Object>();
    
    """Get the [[Partial]] or instance with the given id"""
    shared Anything leakInstance(Id id) => instances.get(id);
    
    shared actual void attribute(Id instanceId, ValueDeclaration attribute, Id attributeValueId) {
        attributeOrElement(instanceId, MemberImpl(attribute), attributeValueId);
    }
    
    "A [[DeserializationException]] to day that the instance with the given id has already been instantiated."
    shared DeserializationException alreadyComplete(Id instanceId) 
        => DeserializationException("instance referred to by id ``instanceId`` already complete.");
    
    "Get or create the [[Partial]] for `instanceId`; 
     add the given `attributeValueId` to its partial state."
    throws(`class DeserializationException`, 
        "If the `instanceId` corresponds to a reconstructed 
         instance or to a partial that's already been instantiated")
    void attributeOrElement(Id instanceId, ReachableReference attributeOrIndex, Id attributeValueId) {
        Partial referring;
        switch(r=instances.get(instanceId))
        case (is Null){
            value p = PartialImpl(instanceId);
            instances.put(instanceId, p);
            referring = p;
        }
        case (is Partial) {
            referring = r;
            if (referring.instantiated) {
                throw alreadyComplete(instanceId);
            }
        }
        else {//referring is an instance
            throw alreadyComplete(instanceId);
        }
        referring.addState(attributeOrIndex, attributeValueId);
    }
    
    shared actual void element(Id instanceId, Integer index, Id elementValueId) {
        attributeOrElement(instanceId, ElementImpl(index), elementValueId);
    }
    
    shared actual void instance(Id instanceId, ClassModel clazz) {
        if (!clazz.declaration.serializable) {
            throw DeserializationException("not serializable: ``clazz``");
        }
        getOrCreatePartial(instanceId).clazz = clazz;
    }
    
    "Get or create a [[Partial]] for the given `instanceId`."
    Partial getOrCreatePartial(Id instanceId) {
        Partial partial;
        switch(r=instances.get(instanceId))
        case (is Null) {
            value p = PartialImpl(instanceId);
            instances.put(instanceId, p);
            partial = p;
        }
        case (is Partial) {
            partial = r;
        }
        else {// an instance
            throw alreadyComplete(instanceId);
        }
        return partial;
    }
    
    shared actual void memberInstance(Id containerId, Id instanceId) {
        Anything container;
        switch(r=instances.get(containerId))
        case (is Null) {
            value p = PartialImpl(containerId);
            instances.put(containerId, p);
            container = p;
        }
        else {
            container = r;
        }
        getOrCreatePartial(instanceId).container = container;
    }
    
    shared actual void instanceValue(Id instanceId, Anything instanceValue) {
        instances.put(instanceId, instanceValue);
    }
    
    shared actual Instance reconstruct(Id instanceId) {
        NativeDeque deque = NativeDeque();
        value root = instances.get(instanceId);
        if (!root exists) {
            if (instances.contains(instanceId)) {
                assert(is Instance r=null);
                return r;
            } else {
                throw DeserializationException("unknown id: ``instanceId``.");
            }
        }
        deque.pushFront(instances.get(instanceId));
        while (!deque.empty){
            value r=deque.popFront();
            switch(r)
            case (is Null) {
                assert(false);
            }
            case (is Partial) {
                if (r.member, is Partial container = r.container,
                    !container.instantiated) {
                    // On the JVM we need to ensure that all outer instances are 
                    // instantiated before all member instances
                    // so do the container first and then come back for the member
                    deque.pushFront(r);
                    deque.pushFront(container);
                    continue;
                }
                if (!r.instantiated) {
                    r.instantiate();
                }
                // push the referred things on to the stack
                // but only if they haven't yet been instantiated
                for (referredId in r.refersTo) {
                    assert(is Id referredId);
                    value referred = instances.get(referredId);
                    if (is Partial referred,
                        !referred.instantiated) {
                        deque.pushFront(referred);
                    }
                }
            } else {
                // it's an instance already, nothing to do
            }
        }
        // we now have real instances for everything reachable from instanceId
        // so now we can inject the state...
        deque.pushFront(instances.get(instanceId));
        while (!deque.empty){
            switch(r=deque.popFront())
            case (is Partial) {
                if (r.member) {
                    if (is Partial container = r.container,
                        !container.initialized) {
                        deque.pushFront(r);
                        deque.pushFront(container);
                        continue;
                    }
                }
                if (!r.initialized) {
                    // push the referred things on to the stack
                    // but only if they haven't yet been initialized
                    for (referredId in r.refersTo) {
                        assert(is Id referredId);
                        value referred = instances.get(referredId);
                        if (is Partial referred,
                            !referred.initialized) {
                            deque.pushFront(referred);
                        }
                    }
                    if (r.member,
                        is Partial container = r.container,
                            !container.initialized) {
                        deque.pushFront(container);
                    }
                    r.initialize(this);
                }
            } else {
                // it's an instance already, nothing to do
            }
        }
        
        switch(r=instances.get(instanceId))
        case (is Partial) {
            value result=r.instance();
            if (is Instance result) {
                instances.put(instanceId, result);
                return result;
            } else {
                throw DeserializationException("instance with id ``instanceId`` has class ``type(result)`` not assignable to given type ``typeLiteral()``");
            }
        }
        else {
            if (is Instance r) {
                return r;
            } else {
                throw DeserializationException("instance with id ``instanceId`` has class ``type(r)`` not assignable to given type ``typeLiteral()``");
            }
        }
        
    }
    
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy