// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Schemas { public sealed class FieldCollection : Cloneable> where T : IField { public static readonly FieldCollection Empty = new FieldCollection(); private static readonly Dictionary EmptyById = new Dictionary(); private static readonly Dictionary EmptyByString = new Dictionary(); private T[] fieldsOrdered; private Dictionary? fieldsById; private Dictionary? fieldsByName; public IReadOnlyList Ordered { get => fieldsOrdered; } public IReadOnlyDictionary ById { get { if (fieldsById == null) { if (fieldsOrdered.Length == 0) { fieldsById = EmptyById; } else { fieldsById = fieldsOrdered.ToDictionary(x => x.Id); } } return fieldsById; } } public IReadOnlyDictionary ByName { get { if (fieldsByName == null) { if (fieldsOrdered.Length == 0) { fieldsByName = EmptyByString; } else { fieldsByName = fieldsOrdered.ToDictionary(x => x.Name); } } return fieldsByName; } } private FieldCollection() { fieldsOrdered = Array.Empty(); } public FieldCollection(T[] fields) { Guard.NotNull(fields, nameof(fields)); fieldsOrdered = fields; } protected override void OnCloned() { fieldsById = null; fieldsByName = null; } [Pure] public FieldCollection Remove(long fieldId) { if (!ById.TryGetValue(fieldId, out _)) { return this; } return Clone(clone => { clone.fieldsOrdered = fieldsOrdered.Where(x => x.Id != fieldId).ToArray(); }); } [Pure] public FieldCollection Reorder(List ids) { Guard.NotNull(ids, nameof(ids)); if (ids.Count != fieldsOrdered.Length || ids.Any(x => !ById.ContainsKey(x))) { throw new ArgumentException("Ids must cover all fields.", nameof(ids)); } if (ids.SequenceEqual(fieldsOrdered.Select(x => x.Id))) { return this; } return Clone(clone => { clone.fieldsOrdered = fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToArray(); }); } [Pure] public FieldCollection Add(T field) { Guard.NotNull(field, nameof(field)); if (ByName.ContainsKey(field.Name)) { throw new ArgumentException($"A field with name '{field.Name}' already exists.", nameof(field)); } if (ById.ContainsKey(field.Id)) { throw new ArgumentException($"A field with id {field.Id} already exists.", nameof(field)); } return Clone(clone => { clone.fieldsOrdered = clone.fieldsOrdered.Union(Enumerable.Repeat(field, 1)).ToArray(); }); } [Pure] public FieldCollection Update(long fieldId, Func updater) { Guard.NotNull(updater, nameof(updater)); if (!ById.TryGetValue(fieldId, out var field)) { return this; } var newField = updater(field); if (ReferenceEquals(newField, field)) { return this; } if (newField is not T) { throw new InvalidOperationException($"Field must be of type {typeof(T)}"); } return Clone(clone => { clone.fieldsOrdered = clone.fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x).ToArray(); }); } } }