// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.Blazor.Components; using Microsoft.Blazor.Rendering; using System; using System.Collections.Generic; namespace Microsoft.Blazor.RenderTree { /// /// Provides methods for building a collection of entries. /// public class RenderTreeBuilder { private const int MinBufferLength = 10; private readonly Renderer _renderer; private RenderTreeNode[] _entries = new RenderTreeNode[100]; private int _entriesInUse = 0; private readonly Stack _openElementIndices = new Stack(); private RenderTreeNodeType? _lastNonAttributeNodeType; /// /// Constructs an instance of . /// /// The associated . public RenderTreeBuilder(Renderer renderer) { _renderer = renderer ?? throw new ArgumentNullException(nameof(renderer)); } /// /// Appends a node representing an element, i.e., a container for other nodes. /// In order for the state to be valid, you must /// also call immediately after appending the /// new element's child nodes. /// /// A value representing the type of the element. public void OpenElement(string elementName) { _openElementIndices.Push(_entriesInUse); Append(RenderTreeNode.Element(elementName)); } /// /// Marks a previously appended element node as closed. Calls to this method /// must be balanced with calls to . /// public void CloseElement() { var indexOfEntryBeingClosed = _openElementIndices.Pop(); _entries[indexOfEntryBeingClosed].CloseElement(_entriesInUse - 1); } /// /// Appends a node representing text content. /// /// Content for the new text node. public void AddText(string textContent) => Append(RenderTreeNode.Text(textContent)); /// /// Appends a node representing a string-valued attribute. /// The attribute is associated with the most recently added element. /// /// The name of the attribute. /// The value of the attribute. public void AddAttribute(string name, string value) { AssertCanAddAttribute(); Append(RenderTreeNode.Attribute(name, value)); } /// /// Appends a node representing an -valued attribute. /// The attribute is associated with the most recently added element. /// /// The name of the attribute. /// The value of the attribute. public void AddAttribute(string name, UIEventHandler value) { AssertCanAddAttribute(); Append(RenderTreeNode.Attribute(name, value)); } /// /// Appends a node representing a child component. /// /// The type of the child component. public void AddComponent() where TComponent: IComponent { // Later, instead of instantiating the child component here, we'll instead // store a descriptor of the component (type, parameters) on the attributes // of the appended nodes. Then after the tree is diffed against the // previous tree, we'll either instantiate a new component or reuse the // existing instance (and notify it about changes to parameters). var instance = Activator.CreateInstance(); var instanceId = _renderer.AssignComponentId(instance); Append(RenderTreeNode.ChildComponent(instanceId, instance)); } private void AssertCanAddAttribute() { if (_lastNonAttributeNodeType != RenderTreeNodeType.Element && _lastNonAttributeNodeType != RenderTreeNodeType.Component) { throw new InvalidOperationException($"Attributes may only be added immediately after nodes of type {RenderTreeNodeType.Element} or {RenderTreeNodeType.Component}"); } } /// /// Clears the builder. /// public void Clear() { // If the previous usage of the buffer showed that we have allocated // much more space than needed, free up the excess memory var shrinkToLength = Math.Max(MinBufferLength, _entries.Length / 2); if (_entriesInUse < shrinkToLength) { Array.Resize(ref _entries, shrinkToLength); } _entriesInUse = 0; _openElementIndices.Clear(); _lastNonAttributeNodeType = null; } /// /// Returns the values that have been appended. /// The return value's is always zero. /// /// An array segment of values. public ArraySegment GetNodes() => new ArraySegment(_entries, 0, _entriesInUse); private void Append(RenderTreeNode node) { if (_entriesInUse == _entries.Length) { Array.Resize(ref _entries, _entries.Length * 2); } _entries[_entriesInUse++] = node; var nodeType = node.NodeType; if (nodeType != RenderTreeNodeType.Attribute) { _lastNonAttributeNodeType = node.NodeType; } } } }