// 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;
}
}
}
}