ceylon.language.serialization.DeserializationContextImpl.ceylon Maven / Gradle / Ivy
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