/// <summary> /// Reorders the buffered properties to conform to the required payload order. /// </summary> /// <remarks> /// The required order is: odata.context comes first, odata.removed comes next (for deleted resources), /// then comes odata.type, then all odata.* property annotations /// and finally, we preserve the relative order of custom annotations and data properties.</remarks> internal void Reorder() { BufferedNode currentNode = this.ObjectStart; // Sort the property names preserving the relative order of the properties except for the OData instance annotations. IEnumerable <string> sortedPropertyNames = this.SortPropertyNames(); foreach (string propertyName in sortedPropertyNames) { Debug.Assert(this.propertyCache.ContainsKey(propertyName), "Property must be in the cache for its name to be in the list of property names."); object storedValue = this.propertyCache[propertyName]; BufferedProperty storedProperty = storedValue as BufferedProperty; if (storedProperty != null) { storedProperty.InsertAfter(currentNode); currentNode = storedProperty.EndOfPropertyValueNode; } else { IEnumerable <BufferedProperty> sortedProperties = SortBufferedProperties((IList <BufferedProperty>)storedValue); foreach (BufferedProperty sortedProperty in sortedProperties) { sortedProperty.InsertAfter(currentNode); currentNode = sortedProperty.EndOfPropertyValueNode; } } } }
/// <summary> /// Adds a new buffered property to the list of buffered properties for this object. /// </summary> /// <param name="propertyName">The name of the data property (null for instance annotations).</param> /// <param name="annotationName">The name of the annotation (null for data properties).</param> /// <param name="bufferedProperty">The buffered property to add.</param> internal void AddBufferedProperty(string propertyName, string annotationName, BufferedProperty bufferedProperty) { this.CurrentProperty = bufferedProperty; string lookupName = propertyName ?? annotationName; // We have to record the relative positions of all properties so we can later on properly sort them. // Note that we have to also capture the positions of the property annotations as long as we have not // seen a data property for that annotation since the data property might be missing. if (propertyName == null) { this.propertyNamesWithAnnotations.Add(new KeyValuePair <string, string>(annotationName, null)); } else if (!this.dataProperties.Contains(propertyName)) { if (annotationName == null) { this.dataProperties.Add(propertyName); } this.propertyNamesWithAnnotations.Add(new KeyValuePair <string, string>(propertyName, annotationName)); } object storedValue; if (this.propertyCache.TryGetValue(lookupName, out storedValue)) { Debug.Assert(storedValue != null, "storedValue != null"); List <BufferedProperty> storedProperties; BufferedProperty storedProperty = storedValue as BufferedProperty; if (storedProperty != null) { storedProperties = new List <BufferedProperty>(4); storedProperties.Add(storedProperty); this.propertyCache[lookupName] = storedProperties; } else { storedProperties = (List <BufferedProperty>)storedValue; } storedProperties.Add(bufferedProperty); } else { this.propertyCache.Add(lookupName, bufferedProperty); } }
/// <summary> /// Sort the data properties and property annotations stored for a particular property name. /// </summary> /// <param name="bufferedProperties">The list of buffered properties to sort.</param> /// <returns>The sorted enumerable of buffered properties.</returns> /// <remarks>The sort order is for all odata.* property annotations to come before the data property /// but otherwise preserve the relative order of custom property annotations with regard to the position of the data property.</remarks> private static IEnumerable <BufferedProperty> SortBufferedProperties(IList <BufferedProperty> bufferedProperties) { Debug.Assert(bufferedProperties != null && bufferedProperties.Count > 1, "bufferedProperties != null && bufferedProperties.Count > 1"); // NOTE: we re-order the property annotations so that ... // 1) all odata.* property annotations come before the data property. // 2) we preserve the relative order of custom property annotations with regard to the data property. List <BufferedProperty> delayedProperties = null; for (int i = 0; i < bufferedProperties.Count; ++i) { BufferedProperty bufferedProperty = bufferedProperties[i]; string annotationName = bufferedProperty.PropertyAnnotationName; if (annotationName == null || !IsODataInstanceAnnotation(annotationName)) { // This is either the data property or a custom annotation; we have to delay reporting it until we reported all OData.* annotations if (delayedProperties == null) { delayedProperties = new List <BufferedProperty>(); } delayedProperties.Add(bufferedProperty); } else { yield return(bufferedProperty); } } // If we delayed reporting of any property annotations, report them now preserving their relative order. if (delayedProperties != null) { for (int i = 0; i < delayedProperties.Count; ++i) { yield return(delayedProperties[i]); } } }
/// <summary> /// Asynchronously called whenever we find a new object value in the payload. /// Buffers and re-orders an object value for later consumption by the JsonLight reader. /// </summary> /// <remarks> /// This method is called when the reader is in the buffering mode and can read ahead (buffering) as much as it needs to /// once it returns the reader will be returned to the position before the method was called. /// The reader is always positioned on a start object when this method is called. /// </remarks> protected override async Task ProcessObjectValueAsync() { Debug.Assert(this.currentBufferedNode.NodeType == JsonNodeType.StartObject, "this.currentBufferedNode.NodeType == JsonNodeType.StartObject"); this.AssertBuffering(); Stack <BufferedObject> bufferedObjectStack = new Stack <BufferedObject>(); while (true) { switch (this.currentBufferedNode.NodeType) { case JsonNodeType.StartObject: { // New object record - add the node to our stack BufferedObject bufferedObject = new BufferedObject { ObjectStart = this.currentBufferedNode }; bufferedObjectStack.Push(bufferedObject); // See if it's an in-stream error await base.ProcessObjectValueAsync() .ConfigureAwait(false); this.currentBufferedNode = bufferedObject.ObjectStart; await this.ReadInternalAsync() .ConfigureAwait(false); } break; case JsonNodeType.EndObject: { // End of object record // Pop the node from our stack BufferedObject bufferedObject = bufferedObjectStack.Pop(); // If there is a previous property record, mark its last value node. if (bufferedObject.CurrentProperty != null) { bufferedObject.CurrentProperty.EndOfPropertyValueNode = this.currentBufferedNode.Previous; } // Now perform the re-ordering on the buffered nodes bufferedObject.Reorder(); if (bufferedObjectStack.Count == 0) { // No more objects to process - we're done. return; } await this.ReadInternalAsync() .ConfigureAwait(false); } break; case JsonNodeType.Property: { BufferedObject bufferedObject = bufferedObjectStack.Peek(); // If there is a current property, mark its last value node. if (bufferedObject.CurrentProperty != null) { bufferedObject.CurrentProperty.EndOfPropertyValueNode = this.currentBufferedNode.Previous; } BufferedProperty bufferedProperty = new BufferedProperty(); bufferedProperty.PropertyNameNode = this.currentBufferedNode; string propertyName, annotationName; Tuple <string, string> readPropertyNameResult = await this.ReadPropertyNameAsync() .ConfigureAwait(false); propertyName = readPropertyNameResult.Item1; annotationName = readPropertyNameResult.Item2; bufferedProperty.PropertyAnnotationName = annotationName; bufferedObject.AddBufferedProperty(propertyName, annotationName, bufferedProperty); if (annotationName != null) { // Instance-level property annotation - no reordering in its value // or instance-level annotation - no reordering in its value either // So skip its value while buffering. await this.BufferValueAsync() .ConfigureAwait(false); } } break; default: // Read over (buffer) everything else await this.ReadInternalAsync() .ConfigureAwait(false); break; } } }
/// <summary> /// Called whenever we find a new object value in the payload. /// Buffers and re-orders an object value for later consumption by the JsonLight reader. /// </summary> /// <remarks> /// This method is called when the reader is in the buffering mode and can read ahead (buffering) as much as it needs to /// once it returns the reader will be returned to the position before the method was called. /// The reader is always positioned on a start object when this method is called. /// </remarks> protected override void ProcessObjectValue() { Debug.Assert(this.currentBufferedNode.NodeType == JsonNodeType.StartObject, "this.currentBufferedNode.NodeType == JsonNodeType.StartObject"); this.AssertBuffering(); Stack<BufferedObject> bufferedObjectStack = new Stack<BufferedObject>(); while (true) { switch (this.currentBufferedNode.NodeType) { case JsonNodeType.StartObject: { // New object record - add the node to our stack BufferedObject bufferedObject = new BufferedObject { ObjectStart = this.currentBufferedNode }; bufferedObjectStack.Push(bufferedObject); // See if it's an in-stream error base.ProcessObjectValue(); this.currentBufferedNode = bufferedObject.ObjectStart; this.ReadInternal(); } break; case JsonNodeType.EndObject: { // End of object record // Pop the node from our stack BufferedObject bufferedObject = bufferedObjectStack.Pop(); // If there is a previous property record, mark its last value node. if (bufferedObject.CurrentProperty != null) { bufferedObject.CurrentProperty.EndOfPropertyValueNode = this.currentBufferedNode.Previous; } // Now perform the re-ordering on the buffered nodes bufferedObject.Reorder(); if (bufferedObjectStack.Count == 0) { // No more objects to process - we're done. return; } this.ReadInternal(); } break; case JsonNodeType.Property: { BufferedObject bufferedObject = bufferedObjectStack.Peek(); // If there is a current property, mark its last value node. if (bufferedObject.CurrentProperty != null) { bufferedObject.CurrentProperty.EndOfPropertyValueNode = this.currentBufferedNode.Previous; } BufferedProperty bufferedProperty = new BufferedProperty(); bufferedProperty.PropertyNameNode = this.currentBufferedNode; string propertyName; string annotationName; this.ReadPropertyName(out propertyName, out annotationName); bufferedProperty.PropertyAnnotationName = annotationName; bufferedObject.AddBufferedProperty(propertyName, annotationName, bufferedProperty); if (annotationName != null) { // Instance-level property annotation - no reordering in its value // or instance-level annotation - no reordering in its value either // So skip its value while buffering. this.BufferValue(); } } break; default: // Read over (buffer) everything else this.ReadInternal(); break; } } }
/// <summary> /// Adds a new buffered property to the list of buffered properties for this object. /// </summary> /// <param name="propertyName">The name of the data property (null for instance annotations).</param> /// <param name="annotationName">The name of the annotation (null for data properties).</param> /// <param name="bufferedProperty">The buffered property to add.</param> internal void AddBufferedProperty(string propertyName, string annotationName, BufferedProperty bufferedProperty) { this.CurrentProperty = bufferedProperty; string lookupName = propertyName ?? annotationName; // We have to record the relative positions of all properties so we can later on propertly sort them. // Note that we have to also capture the positions of the property annotations as long as we have not // seen a data property for that annotation since the data property might be missing. if (propertyName == null) { this.propertyNamesWithAnnotations.Add(new KeyValuePair<string, string>(annotationName, null)); } else if (!this.dataProperties.Contains(propertyName)) { if (annotationName == null) { this.dataProperties.Add(propertyName); } this.propertyNamesWithAnnotations.Add(new KeyValuePair<string, string>(propertyName, annotationName)); } object storedValue; if (this.propertyCache.TryGetValue(lookupName, out storedValue)) { Debug.Assert(storedValue != null, "storedValue != null"); List<BufferedProperty> storedProperties; BufferedProperty storedProperty = storedValue as BufferedProperty; if (storedProperty != null) { storedProperties = new List<BufferedProperty>(4); storedProperties.Add(storedProperty); this.propertyCache[lookupName] = storedProperties; } else { storedProperties = (List<BufferedProperty>)storedValue; } storedProperties.Add(bufferedProperty); } else { this.propertyCache.Add(lookupName, bufferedProperty); } }