File: Internal\Metadata\NativeFormat\Writer\MetadataBlob.cs
Web Access
Project: ILCompiler.MetadataTransform.csproj (ILCompiler.MetadataTransform)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Internal.NativeFormat;

namespace Internal.Metadata.NativeFormat.Writer;

public sealed class MetadataBlob : IEquatable<MetadataBlob>
{
    private readonly List<byte> _bytes = new List<byte>();
    private readonly List<HandleReference> _references = new List<HandleReference>();
    private bool _isReadOnly;

    private readonly struct HandleReference(int offset, MetadataRecord record, bool isTyped)
    {
        public readonly int Offset = offset;
        public readonly MetadataRecord Record = record;
        public readonly bool IsTyped = isTyped;

        public uint EncodedValue => Record is null ? 0 :
            IsTyped ? (uint)Record.HandleOffset : (uint)Record.HandleType | ((uint)Record.HandleOffset << 7);
    }

    public void WriteByte(byte value)
    {
        Debug.Assert(!_isReadOnly);
        _bytes.Add(value);
    }

    public void WriteBytes(ReadOnlySpan<byte> bytes)
    {
        Debug.Assert(!_isReadOnly);
        foreach (byte value in bytes)
            _bytes.Add(value);
    }

    public void WriteString(ConstantStringValue value)
    {
        Debug.Assert(!_isReadOnly);
        Debug.Assert(value is null || value.Value is not null);
        _references.Add(new HandleReference(_bytes.Count, value, isTyped: true));
    }

    public void WriteType(MetadataRecord value)
    {
        Debug.Assert(!_isReadOnly);
        Debug.Assert(value is null || value.HandleType is
            HandleType.TypeDefinition or HandleType.TypeReference or HandleType.TypeSpecification);
        _references.Add(new HandleReference(_bytes.Count, value, isTyped: false));
    }

    internal void Visit(MetadataRecord owner, IRecordVisitor visitor)
    {
        _isReadOnly = true;
        for (int i = 0; i < _references.Count; i++)
        {
            HandleReference reference = _references[i];
            _references[i] = new HandleReference(
                reference.Offset, visitor.Visit(owner, reference.Record), reference.IsTyped);
        }
    }

    public bool Equals(MetadataBlob other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (other is null || _bytes.Count != other._bytes.Count || _references.Count != other._references.Count)
            return false;

        for (int i = 0; i < _bytes.Count; i++)
        {
            if (_bytes[i] != other._bytes[i])
                return false;
        }

        for (int i = 0; i < _references.Count; i++)
        {
            HandleReference left = _references[i];
            HandleReference right = other._references[i];
            if (left.Offset != right.Offset || left.IsTyped != right.IsTyped || !Equals(left.Record, right.Record))
                return false;
        }

        return true;
    }

    public override bool Equals(object obj) => obj is MetadataBlob other && Equals(other);

    public override int GetHashCode()
    {
        _isReadOnly = true;
        int hash = 17;
        foreach (byte value in _bytes)
            hash = unchecked(hash * 31 + value);

        foreach (HandleReference reference in _references)
        {
            hash = unchecked(hash * 31 + reference.Offset);
            hash = unchecked(hash * 31 + (reference.IsTyped ? 1 : 0));
            hash = unchecked(hash * 31 + (reference.Record?.GetHashCode() ?? 0));
        }

        return hash;
    }

    internal void Save(NativeWriter writer)
    {
        uint length = (uint)_bytes.Count;
        foreach (HandleReference reference in _references)
            length = checked(length + (uint)NativePrimitiveEncoder.GetUnsignedEncodingSize(reference.EncodedValue));

        writer.WriteUnsigned(length);
        int offset = 0;
        foreach (HandleReference reference in _references)
        {
            while (offset < reference.Offset)
                writer.WriteByte(_bytes[offset++]);

            writer.WriteUnsigned(reference.EncodedValue);
        }

        while (offset < _bytes.Count)
            writer.WriteByte(_bytes[offset++]);
    }
}