// 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 System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Razor.Language.Legacy { internal class Block : SyntaxTreeNode { public Block(BlockBuilder source) : this(source.Type, source.Children, source.ChunkGenerator) { source.Reset(); } protected Block(BlockKind? type, IReadOnlyList children, IParentChunkGenerator generator) { if (type == null) { throw new InvalidOperationException(LegacyResources.Block_Type_Not_Specified); } Type = type.Value; Children = children; ChunkGenerator = generator; // Perf: Avoid allocating an enumerator. for (var i = 0; i < Children.Count; i++) { Children[i].Parent = this; } } public IParentChunkGenerator ChunkGenerator { get; } public BlockKind Type { get; } public IReadOnlyList Children { get; } public override bool IsBlock => true; public override SourceLocation Start { get { var child = Children.FirstOrDefault(); if (child == null) { return SourceLocation.Zero; } else { return child.Start; } } } public override int Length => Children.Sum(child => child.Length); public virtual IEnumerable Flatten() { // Perf: Avoid allocating an enumerator. for (var i = 0; i < Children.Count; i++) { var element = Children[i]; var span = element as Span; if (span != null) { yield return span; } else { var block = element as Block; foreach (Span childSpan in block.Flatten()) { yield return childSpan; } } } } public Span FindFirstDescendentSpan() { SyntaxTreeNode current = this; while (current != null && current.IsBlock) { current = ((Block)current).Children.FirstOrDefault(); } return current as Span; } public Span FindLastDescendentSpan() { SyntaxTreeNode current = this; while (current != null && current.IsBlock) { current = ((Block)current).Children.LastOrDefault(); } return current as Span; } public virtual Span LocateOwner(SourceChange change) => LocateOwner(change, Children); protected static Span LocateOwner(SourceChange change, IEnumerable elements) { // Ask each child recursively Span owner = null; foreach (var element in elements) { var span = element as Span; if (span == null) { owner = ((Block)element).LocateOwner(change); } else { if (change.Span.AbsoluteIndex < span.Start.AbsoluteIndex) { // Early escape for cases where changes overlap multiple spans // In those cases, the span will return false, and we don't want to search the whole tree // So if the current span starts after the change, we know we've searched as far as we need to break; } owner = span.EditHandler.OwnsChange(span, change) ? span : owner; } if (owner != null) { break; } } return owner; } public override string ToString() { return string.Format( CultureInfo.CurrentCulture, "{0} Block at {1}::{2} (Gen:{3})", Type, Start, Length, ChunkGenerator); } public override bool Equals(object obj) { var other = obj as Block; return other != null && Type == other.Type && Equals(ChunkGenerator, other.ChunkGenerator) && ChildrenEqual(Children, other.Children); } public override int GetHashCode() { var hashCodeCombiner = HashCodeCombiner.Start(); hashCodeCombiner.Add(Type); hashCodeCombiner.Add(ChunkGenerator); hashCodeCombiner.Add(Children); return hashCodeCombiner; } private static bool ChildrenEqual(IEnumerable left, IEnumerable right) { IEnumerator leftEnum = left.GetEnumerator(); IEnumerator rightEnum = right.GetEnumerator(); while (leftEnum.MoveNext()) { if (!rightEnum.MoveNext() || // More items in left than in right !Equals(leftEnum.Current, rightEnum.Current)) { // Nodes are not equal return false; } } if (rightEnum.MoveNext()) { // More items in right than left return false; } return true; } public override bool EquivalentTo(SyntaxTreeNode node) { var other = node as Block; if (other == null || other.Type != Type) { return false; } return Enumerable.SequenceEqual(Children, other.Children, EquivalenceComparer.Default); } public override int GetEquivalenceHash() { var hashCodeCombiner = HashCodeCombiner.Start(); hashCodeCombiner.Add(Type); foreach (var child in Children) { hashCodeCombiner.Add(child.GetEquivalenceHash()); } return hashCodeCombiner.CombinedHash; } public override void Accept(ParserVisitor visitor) { visitor.VisitBlock(this); } private class EquivalenceComparer : IEqualityComparer { public static readonly EquivalenceComparer Default = new EquivalenceComparer(); private EquivalenceComparer() { } public bool Equals(SyntaxTreeNode nodeX, SyntaxTreeNode nodeY) { if (nodeX == nodeY) { return true; } return nodeX != null && nodeX.EquivalentTo(nodeY); } public int GetHashCode(SyntaxTreeNode node) { if (node == null) { throw new ArgumentNullException(nameof(node)); } return node.GetEquivalenceHash(); } } } }