import { Type, isArray, isPresent, serializeEnum, deserializeEnum } from "angular2/src/core/facade/lang"; import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions'; import { ListWrapper, Map, StringMap, StringMapWrapper, MapWrapper } from "angular2/src/core/facade/collection"; import { ProtoViewDto, RenderDirectiveMetadata, RenderElementBinder, DirectiveBinder, ElementPropertyBinding, EventBinding, ViewDefinition, RenderProtoViewRef, RenderProtoViewMergeMapping, RenderViewRef, RenderFragmentRef, RenderElementRef, ViewType, ViewEncapsulation, PropertyBindingType } from "angular2/src/core/render/api"; import {WebWorkerElementRef} from 'angular2/src/web_workers/shared/api'; import {AST, ASTWithSource} from 'angular2/src/core/change_detection/change_detection'; import {Parser} from "angular2/src/core/change_detection/parser/parser"; import {Injectable} from "angular2/src/core/di"; import {RenderProtoViewRefStore} from 'angular2/src/web_workers/shared/render_proto_view_ref_store'; import { RenderViewWithFragmentsStore } from 'angular2/src/web_workers/shared/render_view_with_fragments_store'; // PRIMITIVE is any type that does not need to be serialized (string, number, boolean) // We set it to String so that it is considered a Type. export const PRIMITIVE: Type = String; @Injectable() export class Serializer { private _enumRegistry: Map>; constructor(private _parser: Parser, private _protoViewStore: RenderProtoViewRefStore, private _renderViewStore: RenderViewWithFragmentsStore) { this._enumRegistry = new Map>(); var viewTypeMap = new Map(); viewTypeMap[0] = ViewType.HOST; viewTypeMap[1] = ViewType.COMPONENT; viewTypeMap[2] = ViewType.EMBEDDED; this._enumRegistry.set(ViewType, viewTypeMap); var viewEncapsulationMap = new Map(); viewEncapsulationMap[0] = ViewEncapsulation.Emulated; viewEncapsulationMap[1] = ViewEncapsulation.Native; viewEncapsulationMap[2] = ViewEncapsulation.None; this._enumRegistry.set(ViewEncapsulation, viewEncapsulationMap); var propertyBindingTypeMap = new Map(); propertyBindingTypeMap[0] = PropertyBindingType.PROPERTY; propertyBindingTypeMap[1] = PropertyBindingType.ATTRIBUTE; propertyBindingTypeMap[2] = PropertyBindingType.CLASS; propertyBindingTypeMap[3] = PropertyBindingType.STYLE; this._enumRegistry.set(PropertyBindingType, propertyBindingTypeMap); } serialize(obj: any, type: Type): Object { if (!isPresent(obj)) { return null; } if (isArray(obj)) { var serializedObj = []; ListWrapper.forEach(obj, (val) => { serializedObj.push(this.serialize(val, type)); }); return serializedObj; } if (type == PRIMITIVE) { return obj; } if (type == ViewDefinition) { return this._serializeViewDefinition(obj); } else if (type == DirectiveBinder) { return this._serializeDirectiveBinder(obj); } else if (type == ProtoViewDto) { return this._serializeProtoViewDto(obj); } else if (type == RenderElementBinder) { return this._serializeElementBinder(obj); } else if (type == RenderDirectiveMetadata) { return this._serializeDirectiveMetadata(obj); } else if (type == ASTWithSource) { return this._serializeASTWithSource(obj); } else if (type == RenderProtoViewRef) { return this._protoViewStore.serialize(obj); } else if (type == RenderProtoViewMergeMapping) { return this._serializeRenderProtoViewMergeMapping(obj); } else if (type == RenderViewRef) { return this._renderViewStore.serializeRenderViewRef(obj); } else if (type == RenderFragmentRef) { return this._renderViewStore.serializeRenderFragmentRef(obj); } else if (type == WebWorkerElementRef) { return this._serializeWorkerElementRef(obj); } else if (type == ElementPropertyBinding) { return this._serializeElementPropertyBinding(obj); } else if (type == EventBinding) { return this._serializeEventBinding(obj); } else { throw new BaseException("No serializer for " + type.toString()); } } deserialize(map: any, type: Type, data?: any): any { if (!isPresent(map)) { return null; } if (isArray(map)) { var obj: any[] = []; ListWrapper.forEach(map, (val) => { obj.push(this.deserialize(val, type, data)); }); return obj; } if (type == PRIMITIVE) { return map; } if (type == ViewDefinition) { return this._deserializeViewDefinition(map); } else if (type == DirectiveBinder) { return this._deserializeDirectiveBinder(map); } else if (type == ProtoViewDto) { return this._deserializeProtoViewDto(map); } else if (type == RenderDirectiveMetadata) { return this._deserializeDirectiveMetadata(map); } else if (type == RenderElementBinder) { return this._deserializeElementBinder(map); } else if (type == ASTWithSource) { return this._deserializeASTWithSource(map, data); } else if (type == RenderProtoViewRef) { return this._protoViewStore.deserialize(map); } else if (type == RenderProtoViewMergeMapping) { return this._deserializeRenderProtoViewMergeMapping(map); } else if (type == RenderViewRef) { return this._renderViewStore.deserializeRenderViewRef(map); } else if (type == RenderFragmentRef) { return this._renderViewStore.deserializeRenderFragmentRef(map); } else if (type == WebWorkerElementRef) { return this._deserializeWorkerElementRef(map); } else if (type == EventBinding) { return this._deserializeEventBinding(map); } else if (type == ElementPropertyBinding) { return this._deserializeElementPropertyBinding(map); } else { throw new BaseException("No deserializer for " + type.toString()); } } mapToObject(map: Map, type?: Type): Object { var object = {}; var serialize = isPresent(type); MapWrapper.forEach(map, (value, key) => { if (serialize) { object[key] = this.serialize(value, type); } else { object[key] = value; } }); return object; } /* * Transforms a Javascript object (StringMap) into a Map * If the values need to be deserialized pass in their type * and they will be deserialized before being placed in the map */ objectToMap(obj: StringMap, type?: Type, data?: any): Map { if (isPresent(type)) { var map: Map = new Map(); StringMapWrapper.forEach(obj, (val, key) => { map.set(key, this.deserialize(val, type, data)); }); return map; } else { return MapWrapper.createFromStringMap(obj); } } allocateRenderViews(fragmentCount: number) { this._renderViewStore.allocate(fragmentCount); } private _serializeElementPropertyBinding(binding: ElementPropertyBinding): StringMap { return { 'type': serializeEnum(binding.type), 'astWithSource': this.serialize(binding.astWithSource, ASTWithSource), 'property': binding.property, 'unit': binding.unit }; } private _deserializeElementPropertyBinding(map: StringMap): ElementPropertyBinding { var type = deserializeEnum(map['type'], this._enumRegistry.get(PropertyBindingType)); var ast = this.deserialize(map['astWithSource'], ASTWithSource, "binding"); return new ElementPropertyBinding(type, ast, map['property'], map['unit']); } private _serializeEventBinding(binding: EventBinding): StringMap { return {'fullName': binding.fullName, 'source': this.serialize(binding.source, ASTWithSource)}; } private _deserializeEventBinding(map: StringMap): EventBinding { return new EventBinding(map['fullName'], this.deserialize(map['source'], ASTWithSource, "action")); } private _serializeWorkerElementRef(elementRef: RenderElementRef): StringMap { return { 'renderView': this.serialize(elementRef.renderView, RenderViewRef), 'renderBoundElementIndex': elementRef.renderBoundElementIndex }; } private _deserializeWorkerElementRef(map: StringMap): RenderElementRef { return new WebWorkerElementRef(this.deserialize(map['renderView'], RenderViewRef), map['renderBoundElementIndex']); } private _serializeRenderProtoViewMergeMapping(mapping: RenderProtoViewMergeMapping): Object { return { 'mergedProtoViewRef': this._protoViewStore.serialize(mapping.mergedProtoViewRef), 'fragmentCount': mapping.fragmentCount, 'mappedElementIndices': mapping.mappedElementIndices, 'mappedElementCount': mapping.mappedElementCount, 'mappedTextIndices': mapping.mappedTextIndices, 'hostElementIndicesByViewIndex': mapping.hostElementIndicesByViewIndex, 'nestedViewCountByViewIndex': mapping.nestedViewCountByViewIndex }; } private _deserializeRenderProtoViewMergeMapping(obj: StringMap): RenderProtoViewMergeMapping { return new RenderProtoViewMergeMapping( this._protoViewStore.deserialize(obj['mergedProtoViewRef']), obj['fragmentCount'], obj['mappedElementIndices'], obj['mappedElementCount'], obj['mappedTextIndices'], obj['hostElementIndicesByViewIndex'], obj['nestedViewCountByViewIndex']); } private _serializeASTWithSource(tree: ASTWithSource): Object { return {'input': tree.source, 'location': tree.location}; } private _deserializeASTWithSource(obj: StringMap, data: string): AST { // TODO: make ASTs serializable var ast: AST; switch (data) { case "action": ast = this._parser.parseAction(obj['input'], obj['location']); break; case "binding": ast = this._parser.parseBinding(obj['input'], obj['location']); break; case "interpolation": ast = this._parser.parseInterpolation(obj['input'], obj['location']); break; default: throw "No AST deserializer for " + data; } return ast; } private _serializeViewDefinition(view: ViewDefinition): Object { return { 'componentId': view.componentId, 'templateAbsUrl': view.templateAbsUrl, 'template': view.template, 'directives': this.serialize(view.directives, RenderDirectiveMetadata), 'styleAbsUrls': view.styleAbsUrls, 'styles': view.styles, 'encapsulation': serializeEnum(view.encapsulation) }; } private _deserializeViewDefinition(obj: StringMap): ViewDefinition { return new ViewDefinition({ componentId: obj['componentId'], templateAbsUrl: obj['templateAbsUrl'], template: obj['template'], directives: this.deserialize(obj['directives'], RenderDirectiveMetadata), styleAbsUrls: obj['styleAbsUrls'], styles: obj['styles'], encapsulation: deserializeEnum(obj['encapsulation'], this._enumRegistry.get(ViewEncapsulation)) }); } private _serializeDirectiveBinder(binder: DirectiveBinder): Object { return { 'directiveIndex': binder.directiveIndex, 'propertyBindings': this.mapToObject(binder.propertyBindings, ASTWithSource), 'eventBindings': this.serialize(binder.eventBindings, EventBinding), 'hostPropertyBindings': this.serialize(binder.hostPropertyBindings, ElementPropertyBinding) }; } private _deserializeDirectiveBinder(obj: StringMap): DirectiveBinder { return new DirectiveBinder({ directiveIndex: obj['directiveIndex'], propertyBindings: this.objectToMap(obj['propertyBindings'], ASTWithSource, "binding"), eventBindings: this.deserialize(obj['eventBindings'], EventBinding), hostPropertyBindings: this.deserialize(obj['hostPropertyBindings'], ElementPropertyBinding) }); } private _serializeElementBinder(binder: RenderElementBinder): Object { return { 'index': binder.index, 'parentIndex': binder.parentIndex, 'distanceToParent': binder.distanceToParent, 'directives': this.serialize(binder.directives, DirectiveBinder), 'nestedProtoView': this.serialize(binder.nestedProtoView, ProtoViewDto), 'propertyBindings': this.serialize(binder.propertyBindings, ElementPropertyBinding), 'variableBindings': this.mapToObject(binder.variableBindings), 'eventBindings': this.serialize(binder.eventBindings, EventBinding), 'readAttributes': this.mapToObject(binder.readAttributes) }; } private _deserializeElementBinder(obj: StringMap): RenderElementBinder { return new RenderElementBinder({ index: obj['index'], parentIndex: obj['parentIndex'], distanceToParent: obj['distanceToParent'], directives: this.deserialize(obj['directives'], DirectiveBinder), nestedProtoView: this.deserialize(obj['nestedProtoView'], ProtoViewDto), propertyBindings: this.deserialize(obj['propertyBindings'], ElementPropertyBinding), variableBindings: this.objectToMap(obj['variableBindings']), eventBindings: this.deserialize(obj['eventBindings'], EventBinding), readAttributes: this.objectToMap(obj['readAttributes']) }); } private _serializeProtoViewDto(view: ProtoViewDto): Object { return { 'render': this._protoViewStore.serialize(view.render), 'elementBinders': this.serialize(view.elementBinders, RenderElementBinder), 'variableBindings': this.mapToObject(view.variableBindings), 'type': serializeEnum(view.type), 'textBindings': this.serialize(view.textBindings, ASTWithSource), 'transitiveNgContentCount': view.transitiveNgContentCount }; } private _deserializeProtoViewDto(obj: StringMap): ProtoViewDto { return new ProtoViewDto({ render: this._protoViewStore.deserialize(obj["render"]), elementBinders: this.deserialize(obj['elementBinders'], RenderElementBinder), variableBindings: this.objectToMap(obj['variableBindings']), textBindings: this.deserialize(obj['textBindings'], ASTWithSource, "interpolation"), type: deserializeEnum(obj['type'], this._enumRegistry.get(ViewType)), transitiveNgContentCount: obj['transitiveNgContentCount'] }); } private _serializeDirectiveMetadata(meta: RenderDirectiveMetadata): Object { var obj = { 'id': meta.id, 'selector': meta.selector, 'compileChildren': meta.compileChildren, 'events': meta.events, 'properties': meta.properties, 'readAttributes': meta.readAttributes, 'type': meta.type, 'callOnDestroy': meta.callOnDestroy, 'callOnChanges': meta.callOnChanges, 'callDoCheck': meta.callDoCheck, 'callOnInit': meta.callOnInit, 'callAfterContentChecked': meta.callAfterContentChecked, 'changeDetection': meta.changeDetection, 'exportAs': meta.exportAs, 'hostProperties': this.mapToObject(meta.hostProperties), 'hostListeners': this.mapToObject(meta.hostListeners), 'hostAttributes': this.mapToObject(meta.hostAttributes) }; return obj; } private _deserializeDirectiveMetadata(obj: StringMap): RenderDirectiveMetadata { return new RenderDirectiveMetadata({ id: obj['id'], selector: obj['selector'], compileChildren: obj['compileChildren'], hostProperties: this.objectToMap(obj['hostProperties']), hostListeners: this.objectToMap(obj['hostListeners']), hostAttributes: this.objectToMap(obj['hostAttributes']), properties: obj['properties'], readAttributes: obj['readAttributes'], type: obj['type'], exportAs: obj['exportAs'], callOnDestroy: obj['callOnDestroy'], callOnChanges: obj['callOnChanges'], callDoCheck: obj['callDoCheck'], callOnInit: obj['callOnInit'], callAfterContentChecked: obj['callAfterContentChecked'], changeDetection: obj['changeDetection'], events: obj['events'] }); } }