diff --git a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelElement.cs b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelElement.cs index 51d2906..45e474b 100644 --- a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelElement.cs +++ b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelElement.cs @@ -21,8 +21,7 @@ public interface IExcelElement : IMetadataProvider /// /// Gets OpenXml element. /// - [MaybeNull] - TOpenXmlElement Data { get; } + TOpenXmlElement? Data { get; } /// /// Returns true if is not null. @@ -45,21 +44,14 @@ public class ExcelElement : IExcelElement /// /// Gets OpenXml element. /// - [MaybeNull] - public TOpenXmlElement Data { get; } - - ///// - ///// Gets OpenXml element as . - ///// - ///// Optional OpenXml element. - //public Option AsOption() => Data!; + public TOpenXmlElement? Data { get; } /// /// Initializes a new instance of the class. /// /// OpenXml document that contains this element. /// OpenXml element. - public ExcelElement(SpreadsheetDocument doc, [MaybeNull] TOpenXmlElement data) + public ExcelElement(SpreadsheetDocument doc, TOpenXmlElement? data) { Doc = doc.AssertArgumentNotNull(nameof(doc)); Data = data; @@ -95,15 +87,14 @@ public ExcelElement(SpreadsheetDocument doc, [MaybeNull] TOpenXmlElement data) /// /// Gets OpenXml element. /// - [MaybeNull] - public TOpenXmlElement Data { get; } + public TOpenXmlElement? Data { get; } /// /// Initializes a new instance of the struct. /// /// OpenXml document that contains this element. /// OpenXml element. - public ExcelElementLight(SpreadsheetDocument doc, [MaybeNull] TOpenXmlElement data) + public ExcelElementLight(SpreadsheetDocument doc, TOpenXmlElement? data) { Doc = doc.AssertArgumentNotNull(nameof(doc)); Data = data; diff --git a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelParsing.CellReference.cs b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelParsing.CellReference.cs new file mode 100644 index 0000000..048adef --- /dev/null +++ b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelParsing.CellReference.cs @@ -0,0 +1,75 @@ +// Copyright (c) MicroElements. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; +using DocumentFormat.OpenXml; +using MicroElements.CodeContracts; +using MicroElements.Collections.Cache; + +namespace MicroElements.Metadata.OpenXml.Excel.Parsing +{ + public static partial class ExcelParsingExtensions + { + /// + /// Gets cell reference by column row index. + /// Example: (0,0)->A1. + /// + /// Column index. + /// Row index. + /// Is column and row zero based. + /// Cell reference. + public static StringValue GetCellReference(int column, int row, bool zeroBased = true) + { + int columnIndex = zeroBased ? column : column - 1; + string columnName = GetColumnName(columnIndex); + int rowName = zeroBased ? row + 1 : row; + return new StringValue(string.Concat(columnName, rowName.ToString())); + } + + /// + /// Gets column index (cached). + /// + public static string GetColumnName(int columnIndex = 0) + { + return Cache + .Instance("ColumnName") + .GetOrAdd(columnIndex, i => GetColumnName(string.Empty, i)); + } + + private static string GetColumnName(string prefix, int columnIndex = 0) + { + return columnIndex < 26 + ? $"{prefix}{(char)(65 + columnIndex)}" + : GetColumnName(GetColumnName(prefix, ((columnIndex - (columnIndex % 26)) / 26) - 1), columnIndex % 26); + } + + /// + /// Gets column reference from cell reference. + /// For example: A1->A, CD22->CD. + /// + /// Cell reference. + /// Column reference. + public static string GetColumnReference(this StringValue cellReference) + { + cellReference.AssertArgumentNotNull(nameof(cellReference)); + + return cellReference.Value.GetColumnReference(); + } + + /// + /// Gets column reference from cell reference. + /// For example: A1->A, CD22->CD. + /// + /// Cell reference. + /// Column reference. + public static string GetColumnReference(this string cellReference) + { + cellReference.AssertArgumentNotNull(nameof(cellReference)); + + if (cellReference.Length == 2) + return cellReference.Substring(0, 1); + + return new string(cellReference.TakeWhile(char.IsLetter).ToArray()); + } + } +} diff --git a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelParsingExtensions.cs b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelParsingExtensions.cs index cf3fcf0..ee5f02a 100644 --- a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelParsingExtensions.cs +++ b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Parsing/ExcelParsingExtensions.cs @@ -10,7 +10,9 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using MicroElements.CodeContracts; +using MicroElements.Collections.Cache; using MicroElements.Collections.Extensions.Iterate; +using MicroElements.Collections.TwoLayerCache; using MicroElements.Diagnostics; using MicroElements.Metadata.Parsing; using MicroElements.Validation; @@ -64,17 +66,6 @@ public static IEnumerable> GetColumns(this ExcelElement>(); } - /// - /// Gets rows from sheet. - /// - /// Source sheet. - /// Sheet rows. - public static IEnumerable> GetRows(this ExcelElement sheet) - { - return GetOpenXmlRows(sheet) - .Zip(Enumerable.Repeat(sheet, int.MaxValue), (row, sh) => new ExcelElement(sh.Doc, row)); - } - /// /// Gets rows from sheet. /// @@ -96,65 +87,13 @@ public static IEnumerable GetOpenXmlRows(this ExcelElement sheet) } /// - /// Gets cell reference by column row index. - /// Example: (0,0)->A1. - /// - /// Column index. - /// Row index. - /// Is column and row zero based. - /// Cell reference. - public static StringValue GetCellReference(int column, int row, bool zeroBased = true) - { - int columnIndex = zeroBased ? column : column - 1; - string columnName = GetColumnName(columnIndex); - int rowName = zeroBased ? row + 1 : row; - return new StringValue(string.Concat(columnName, rowName.ToString())); - } - - private static readonly ConcurrentDictionary _columnIndexes = new ConcurrentDictionary(); - - /// - /// Gets column index (cached). - /// - public static string GetColumnName(int columnIndex = 0) - { - return _columnIndexes.GetOrAdd(columnIndex, i => GetColumnName(string.Empty, i)); - } - - private static string GetColumnName(string prefix, int columnIndex = 0) - { - return columnIndex < 26 - ? $"{prefix}{(char)(65 + columnIndex)}" - : GetColumnName(GetColumnName(prefix, ((columnIndex - (columnIndex % 26)) / 26) - 1), columnIndex % 26); - } - - /// - /// Gets column reference from cell reference. - /// For example: A1->A, CD22->CD. - /// - /// Cell reference. - /// Column reference. - public static string GetColumnReference(this StringValue cellReference) - { - cellReference.AssertArgumentNotNull(nameof(cellReference)); - - return cellReference.Value.GetColumnReference(); - } - - /// - /// Gets column reference from cell reference. - /// For example: A1->A, CD22->CD. + /// Gets rows from sheet. /// - /// Cell reference. - /// Column reference. - public static string GetColumnReference(this string cellReference) + /// Source sheet. + /// Sheet rows. + public static IEnumerable> GetRows(this ExcelElement sheet) { - cellReference.AssertArgumentNotNull(nameof(cellReference)); - - if (cellReference.Length == 2) - return cellReference.Substring(0, 1); - - return new string(cellReference.TakeWhile(char.IsLetter).ToArray()); + return GetOpenXmlRows(sheet).Select(row => new ExcelElement(sheet.Doc, row)); } /// @@ -253,7 +192,7 @@ public static ExcelElement[] GetHeaders(this ExcelElement row) if (cell != null) { IPropertyParser? propertyParser = header.GetMetadata(); - rowValues[i] = cell.GetCellValue(nullValue, propertyParser); + rowValues[i] = cell.GetCellValue(nullValue, propertyParser?.TargetType); //StringValue cellReference = cell.Data.CellReference; } @@ -309,28 +248,37 @@ private static string GetFormattedValue(this ExcelElement cell) /// Uses SharedStringTable if needed. /// For DateTime, LocalDate and LocalTime tries to convert double excel value to ISO format. /// - public static string? GetCellValue(this ExcelElement cell, string? nullValue = null, IPropertyParser? propertyParser = null) + public static string? GetCellValue( + this T cellElement, + string? nullValue = null, + Type? targetType = null) + where T : IExcelElement { - Cell? cellData = cell.Data; - string? cellValue = cellData?.CellValue?.InnerText ?? nullValue; + Cell? cell = cellElement.Data; + SharedStringTable sharedStringTable = cellElement.Doc.WorkbookPart.SharedStringTablePart.SharedStringTable; + + string? cellValue = cell?.CellValue?.InnerText ?? nullValue; string? cellTextValue = null; - if (cellData == null) + if (cell == null) return cellValue; - CellValues? dataTypeValue = cellData.DataType?.Value; + CellValues? dataTypeValue = cell.DataType?.Value; if (cellValue != null && dataTypeValue == CellValues.SharedString) { - cellTextValue = cell.Doc.WorkbookPart.SharedStringTablePart.SharedStringTable.ChildElements.GetItem(int.Parse(cellValue)).InnerText; + // cellTextValue = sharedStringTable.ChildElements.GetItem(int.Parse(cellValue)).InnerText; + cellTextValue = sharedStringTable + .GetWeakCache() + .GetOrAdd(cellValue, static (cellValue, sharedStringTable) => sharedStringTable.ChildElements.GetItem(int.Parse(cellValue)).InnerText, sharedStringTable); } if (cellTextValue == null && cellValue != null) { - propertyParser ??= cell.GetMetadata(); - - if (propertyParser != null) - cellTextValue = TryParseAsDateType(cellValue, propertyParser.TargetType); + if (targetType != null) + { + cellTextValue = TryParseAsDateType(cellValue, targetType); + } } return cellTextValue ?? cellValue; @@ -578,11 +526,13 @@ public static partial class ExcelParsingExtensions // Use first row as headers headers = row.GetHeaders(); + var propertyParsers = parserProvider.GetParsers().ToArray(); + // Associate parser for each header foreach (var header in headers) { // TODO: RIGID SEARCH. Use predicate? - var propertyParser = parserProvider.GetParsers().FirstOrDefault(parser => parser.SourceName == header.Data.Name); + var propertyParser = propertyParsers.FirstOrDefault(parser => parser.SourceName == header.Data.Name); if (propertyParser != null) { header.SetMetadata(propertyParser); diff --git a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/ExcelReportBuilder.cs b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/ExcelReportBuilder.cs index f4b2760..b6048fe 100644 --- a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/ExcelReportBuilder.cs +++ b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/ExcelReportBuilder.cs @@ -1,6 +1,7 @@ // Copyright (c) MicroElements. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,6 +9,8 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using MicroElements.CodeContracts; +using MicroElements.Collections.Extensions.Iterate; +using MicroElements.Metadata.Experimental; using MicroElements.Metadata.OpenXml.Excel.Parsing; using NodaTime; using Border = DocumentFormat.OpenXml.Spreadsheet.Border; @@ -357,8 +360,7 @@ private void AddSheetData( CellContext[] cellContexts = new CellContext[columns.Count]; for (var i = 0; i < columns.Count; i++) { - var columnContext = columns[i]; - cellContexts[i] = ConstructCell(columnContext, dataRow, callCustomize: false); + cellContexts[i] = ConstructCell(columns[i], dataRow, callCustomize: false); } // Create row @@ -370,14 +372,7 @@ private void AddSheetData( // Customize cells for (var i = 0; i < columns.Count; i++) { - CellContext cellContext = cellContexts[i]; - var configureCell = ExcelCellMetadata.ConfigureCell.GetFirstDefinedValue( - cellContext.CellMetadata, - cellContext.ColumnContext.ColumnMetadata, - sheetContext.SheetMetadata, - sheetContext.DocumentContext.DocumentMetadata); - - configureCell?.Invoke(cellContext); + ConfigureCell(ref cellContexts[i]); } sheetData.AppendChild(excelRow); @@ -397,8 +392,8 @@ private void AddSheetData( { var column = columns[index]; Row row = excelRows[index]; - Cell cell = ConstructCell(column, dataRow).Cell; - row.AppendChild(cell); + CellContext cellContext = ConstructCell(column, dataRow, callCustomize: true); + row.AppendChild(cellContext.Cell); } } @@ -409,6 +404,21 @@ private void AddSheetData( } } + private static void ConfigureCell(ref CellContext cellContext) + { + // var configureCell = ExcelCellMetadata.ConfigureCell.GetFirstDefinedValue( + // cellContext.CellMetadata, + // cellContext.ColumnContext.ColumnMetadata, + // sheetContext.SheetMetadata, + // sheetContext.DocumentContext.DocumentMetadata); + // configureCell?.Invoke(cellContext); + + // var customizeFunc = cellMetadata?.GetValue(ExcelCellMetadata.ConfigureCell); + // customizeFunc?.Invoke(cellContext); + + cellContext.CellMetadata?.ConfigureCell.ConfigureChain(ref cellContext); + } + private Columns CreateColumns(IReadOnlyList columns) { Columns columnsElement = new Columns(); @@ -470,14 +480,15 @@ private Cell ConstructHeaderCell(ColumnContext columnContext) { Cell headerCell = CreateCell(columnContext.PropertyRenderer.TargetName, CellValues.String); - var propertyRenderer = columnContext.PropertyRenderer; + IPropertyRenderer propertyRenderer = columnContext.PropertyRenderer; ExcelColumnMetadata? excelColumnMetadata = propertyRenderer.GetMetadata(); // External customization var customizeFunc = excelColumnMetadata?.GetValue(ExcelColumnMetadata.ConfigureHeaderCell); if (customizeFunc != null) { - customizeFunc.Invoke(new CellContext(columnContext, excelColumnMetadata!, headerCell)); + // var cellMetadata = propertyRenderer.GetMetadata(); + customizeFunc.Invoke(new CellContext(columnContext, null, headerCell)); } return headerCell; @@ -489,9 +500,12 @@ private CellContext ConstructCell(ColumnContext columnContext, IPropertyContaine // Render value string? textValue = propertyRenderer.Render(source); + + // Use StringProvider for potential string reusing textValue = textValue != null ? _settings.StringProvider.GetString(textValue) : null; - var cellMetadata = propertyRenderer.GetMetadata(); + // Get Cell metadata + var cellMetadata = propertyRenderer.GetMetadataCached(); CellValues dataType = ExcelMetadata.DataType.GetFirstDefinedValue( cellMetadata, @@ -517,8 +531,7 @@ private CellContext ConstructCell(ColumnContext columnContext, IPropertyContaine // External customization if (callCustomize) { - var customizeFunc = cellMetadata?.GetValue(ExcelCellMetadata.ConfigureCell); - customizeFunc?.Invoke(cellContext); + ConfigureCell(ref cellContext); } // TODO: omit empty cells? or omit style if cell is empty diff --git a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/CellContext.cs b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/CellContext.cs index 29fe99d..40b81f0 100644 --- a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/CellContext.cs +++ b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/CellContext.cs @@ -1,6 +1,7 @@ // Copyright (c) MicroElements. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Concurrent; using DocumentFormat.OpenXml.Spreadsheet; using MicroElements.CodeContracts; using MicroElements.Metadata.OpenXml.Excel.Parsing; @@ -20,7 +21,7 @@ public readonly struct CellContext /// /// Gets cell metadata. /// - public IExcelMetadata? CellMetadata { get; } + public ExcelCellMetadata? CellMetadata { get; } /// /// Gets OpenXml cell. @@ -43,7 +44,7 @@ public readonly struct CellContext /// ColumnContext for this cell. /// Cell metadata. /// OpenXml cell. - public CellContext(ColumnContext columnContext, IExcelMetadata? cellMetadata, Cell cell) + public CellContext(ColumnContext columnContext, ExcelCellMetadata? cellMetadata, Cell cell) { columnContext.AssertArgumentNotNull(nameof(columnContext)); @@ -58,7 +59,7 @@ public CellContext(ColumnContext columnContext, IExcelMetadata? cellMetadata, Ce /// Text value. public string? GetCellValue() { - ExcelElement excelCell = new ExcelElement(ColumnContext.SheetContext.DocumentContext.Document, Cell); + var excelCell = new ExcelElementLight(ColumnContext.SheetContext.DocumentContext.Document, Cell); string? cellValue = excelCell.GetCellValue(); return cellValue; } diff --git a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/ConfigureAction.cs b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/ConfigureAction.cs new file mode 100644 index 0000000..fdd5dbd --- /dev/null +++ b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/ConfigureAction.cs @@ -0,0 +1,51 @@ +using System; +using MicroElements.CodeContracts; +using MicroElements.Metadata.Experimental; +using MicroElements.Metadata.OpenXml.Excel.Styling; + +namespace MicroElements.Metadata.OpenXml.Excel.Reporting +{ + public delegate void RefAction(ref T value); + + public sealed record ConfigureAction + { + public RefAction? RefAction { get; } + + public string? Description { get; } + + public ConfigureAction(RefAction configure, string? description = null) + { + RefAction = configure.AssertArgumentNotNull(nameof(configure)); + Description = description; + } + } + + public static class ConfigureExtensions + { + public static ImmutableChain CombineWith(this ImmutableChain? chain, T value, CombineMode combineMode = CombineMode.AppendToEnd) + { + switch (combineMode) + { + case CombineMode.AppendToEnd: + return chain.Append(value); + case CombineMode.AppendToStart: + return chain.Prepend(value); + case CombineMode.Set: + return ImmutableChain.Create(value); + } + + throw new InvalidOperationException(); + } + + public static void ConfigureChain(this ImmutableChain>? configureChain, ref T value) + { + if (configureChain?.Values is { } configureActions) + { + foreach (var configure in configureActions) + { + configure.RefAction?.Invoke(ref value); + } + } + } + } +} diff --git a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/ExcelMetadataExtensions.cs b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/ExcelMetadataExtensions.cs index a1efaf1..40cb83a 100644 --- a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/ExcelMetadataExtensions.cs +++ b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/ExcelMetadataExtensions.cs @@ -30,7 +30,15 @@ public static TContainer WithCombinedConfigure( { Action? existedAction = metadata.GetPropertyValue(property)?.Value; - return metadata.WithValue(property, context => Combine(context, existedAction, action, combineMode)); + return metadata.WithValue(property, GetCombinedAction(existedAction, action, combineMode)); + + static Action GetCombinedAction(Action? existedAction, Action action, CombineMode combineMode) + { + if (existedAction is null && combineMode is CombineMode.AppendToStart or CombineMode.AppendToEnd) + return action; + + return context => Combine(context, existedAction, action, combineMode); + } static void Combine(TContext context, Action? action1, Action action2, CombineMode combineMode) { diff --git a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/IExcelMetadata.cs b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/IExcelMetadata.cs index fbb53b6..00f8c2a 100644 --- a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/IExcelMetadata.cs +++ b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/Model/IExcelMetadata.cs @@ -3,6 +3,7 @@ using System; using DocumentFormat.OpenXml.Spreadsheet; +using MicroElements.Metadata.Experimental; using MicroElements.Metadata.Schema; namespace MicroElements.Metadata.OpenXml.Excel.Reporting @@ -172,7 +173,7 @@ public class ExcelColumnMetadata : MutablePropertyContainer, IExcelMetadata public static readonly IProperty ColumnWidth = ExcelMetadata.ColumnWidth; /// - /// Sheet customization function. + /// Column customization function. /// public static readonly IProperty> ConfigureColumn = new Property>("ConfigureColumn"); @@ -182,10 +183,18 @@ public class ExcelColumnMetadata : MutablePropertyContainer, IExcelMetadata public static readonly IProperty> ConfigureHeaderCell = new Property>("ConfigureHeaderCell"); } + public interface IConfigureCell + { + /// + /// Gets or sets cell customization chain. + /// + ImmutableChain>? ConfigureCell { get; set; } + } + /// /// Excel Cell customizations. /// - public class ExcelCellMetadata : MutablePropertyContainer, IExcelMetadata + public class ExcelCellMetadata : MutablePropertyContainer, IExcelMetadata, IConfigureCell { /// /// Excel data type. @@ -193,8 +202,8 @@ public class ExcelCellMetadata : MutablePropertyContainer, IExcelMetadata public static readonly IProperty DataType = ExcelMetadata.DataType; /// - /// Cell customization function. + /// Gets or sets cell customization chain. /// - public static readonly IProperty> ConfigureCell = new Property>("ConfigureCell"); + public ImmutableChain>? ConfigureCell { get; set; } } } diff --git a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/StylingExtensions.cs b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/StylingExtensions.cs index ad65848..e15fdca 100644 --- a/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/StylingExtensions.cs +++ b/src/MicroElements.Metadata.OpenXml/OpenXml/Excel/Reporting/StylingExtensions.cs @@ -7,7 +7,9 @@ using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Spreadsheet; using MicroElements.CodeContracts; +using MicroElements.Metadata.Experimental; using MicroElements.Metadata.OpenXml.Excel.Styling; +using MicroElements.Reflection.FriendlyName; namespace MicroElements.Metadata.OpenXml.Excel.Reporting { @@ -47,12 +49,36 @@ public static TPropertyRenderer SetExcelType(this TPropertyRe /// The same renderer instance. public static TPropertyRenderer ConfigureCell( this TPropertyRenderer propertyRenderer, - Action configureCell, + RefAction configureCell, CombineMode combineMode = CombineMode.AppendToEnd) where TPropertyRenderer : IPropertyRenderer { - return propertyRenderer.ConfigureMetadata( - metadata => metadata.WithCombinedConfigure(ExcelCellMetadata.ConfigureCell, configureCell, combineMode)); + return propertyRenderer + .ConfigureMetadata(metadata => + { + metadata.ConfigureCell = metadata.ConfigureCell.CombineWith(new ConfigureAction(configureCell), combineMode); + }); + } + + /// + /// Configures cell. + /// + /// Property renderer type. + /// Property renderer. + /// Cell customization action. + /// Combine mode. Default: AppendToEnd. + /// The same renderer instance. + public static TPropertyRenderer ConfigureCell( + this TPropertyRenderer propertyRenderer, + ConfigureAction configureCell, + CombineMode combineMode = CombineMode.AppendToEnd) + where TPropertyRenderer : IPropertyRenderer + { + return propertyRenderer + .ConfigureMetadata(metadata => + { + metadata.ConfigureCell = metadata.ConfigureCell.CombineWith(configureCell, combineMode); + }); } /// @@ -115,31 +141,29 @@ public static TMetadataProvider ConfigureRow( /// Property renderer type. /// Property renderer. /// Function. Input: CellValue, Result: StyleName. - /// StyleApply mode. Default: Merge. + /// Configure combine mode. Default: AppendToEnd. + /// StyleApply mode. Default: Merge. /// The same renderer instance. public static TPropertyRenderer ConfigureCellStyle( this TPropertyRenderer propertyRenderer, Func getCellStyle, - MergeMode mergeMode = MergeMode.Merge) + CombineMode configureMode = CombineMode.AppendToEnd, + MergeMode styleApplyMode = MergeMode.Merge) where TPropertyRenderer : IPropertyRenderer { propertyRenderer.AssertArgumentNotNull(nameof(propertyRenderer)); getCellStyle.AssertArgumentNotNull(nameof(getCellStyle)); - if (mergeMode == MergeMode.Set) - { - propertyRenderer.ConfigureMetadata( - metadata => metadata.SetValue(ExcelCellMetadata.ConfigureCell, context => ConfigureCellStyleInternal(context, getCellStyle, mergeMode))); - } - else + propertyRenderer.ConfigureMetadata(metadata => { - propertyRenderer.ConfigureMetadata(metadata => - metadata.WithCombinedConfigure(ExcelCellMetadata.ConfigureCell, context => ConfigureCellStyleInternal(context, getCellStyle, mergeMode))); - } + metadata.ConfigureCell = metadata.ConfigureCell.CombineWith( + new ConfigureAction((ref CellContext context) => ConfigureCellStyleInternal(context, getCellStyle, styleApplyMode), description: "Dynamic style"), + configureMode); + }); return propertyRenderer; - static void ConfigureCellStyleInternal(CellContext context, Func getCellStyle, MergeMode styleApply) + static void ConfigureCellStyleInternal(in CellContext context, Func getCellStyle, MergeMode styleApply) { string? cellValue = context.GetCellValue(); string? cellStyle = getCellStyle(cellValue); @@ -149,6 +173,35 @@ static void ConfigureCellStyleInternal(CellContext context, Func + /// Configures cell style. + /// + /// Property renderer type. + /// Property renderer. + /// StyleName. + /// Configure combine mode. Default: AppendToEnd. + /// StyleApply mode. Default: Merge. + /// The same renderer instance. + public static TPropertyRenderer ConfigureCellStyle( + this TPropertyRenderer propertyRenderer, + string cellStyle, + CombineMode configureMode = CombineMode.AppendToEnd, + MergeMode styleApplyMode = MergeMode.Merge) + where TPropertyRenderer : IPropertyRenderer + { + propertyRenderer.AssertArgumentNotNull(nameof(propertyRenderer)); + cellStyle.AssertArgumentNotNull(nameof(cellStyle)); + + propertyRenderer.ConfigureMetadata(metadata => + { + metadata.ConfigureCell = metadata.ConfigureCell.CombineWith( + new ConfigureAction((ref CellContext context) => context.ApplyStyleToCell(cellStyle, styleApplyMode), description: $"Style: {cellStyle}"), + configureMode); + }); + + return propertyRenderer; + } + /// /// Applies styling from . /// @@ -163,7 +216,9 @@ public static TPropertyRenderer ApplyStyle( bool autoRegister = true) where TPropertyRenderer : IPropertyRenderer { - return propertyRenderer.ConfigureCell(context => + return propertyRenderer.ConfigureCell(new ConfigureAction(ApplyStyleInternal, $"CellFormatProvider: {formatProvider.GetType().GetFriendlyName()}")); + + void ApplyStyleInternal(ref CellContext context) { var formatName = formatProvider.Name; @@ -178,7 +233,7 @@ public static TPropertyRenderer ApplyStyle( } context.ApplyStyleToCell(formatName); - }); + } } /// @@ -195,7 +250,9 @@ public static TPropertyRenderer ApplyStyle( bool autoRegister = true) where TPropertyRenderer : IPropertyRenderer { - return propertyRenderer.ConfigureCell(context => + return propertyRenderer.ConfigureCell(new ConfigureAction(ApplyStyleInternal, $"NumberingFormatProvider: {formatProvider.GetType().GetFriendlyName()}")); + + void ApplyStyleInternal(ref CellContext context) { var numberingFormatName = formatProvider.Name; var cellFormatName = $"CellFormat.{numberingFormatName}"; @@ -223,7 +280,7 @@ public static TPropertyRenderer ApplyStyle( } context.ApplyStyleToCell(cellFormatName); - }); + } } /// @@ -243,7 +300,7 @@ public static TPropertyRenderer SetExcelSerialDateFormat(this object? valueUntyped = container.GetValueUntyped(property, container.SearchOptions.UseDefaultValue(false).ReturnNull()); return valueUntyped.ToExcelSerialDateAsString(); }) - .ConfigureCell(context => context.ApplyStyleToCell(registeredStyleName)); + .ConfigureCell(new ConfigureAction((ref CellContext context) => context.ApplyStyleToCell(registeredStyleName), $"Style: {registeredStyleName}")); return propertyRenderer; } @@ -380,7 +437,7 @@ public static TExcelMetadata WithExcelDocumentStyles(this TExcel /// Cell context. /// Style name to apply. /// Apply style. - public static void ApplyStyleToCell(this CellContext context, string applyStyleName, MergeMode mergeMode = MergeMode.Merge) + public static void ApplyStyleToCell(this in CellContext context, string applyStyleName, MergeMode mergeMode = MergeMode.Merge) { var documentContext = context.ColumnContext.SheetContext.DocumentContext; diff --git a/src/MicroElements.Metadata/Metadata/ComponentModel/IValueObject.cs b/src/MicroElements.Metadata/Metadata/ComponentModel/IValueObject.cs new file mode 100644 index 0000000..c386ed0 --- /dev/null +++ b/src/MicroElements.Metadata/Metadata/ComponentModel/IValueObject.cs @@ -0,0 +1,85 @@ +using System; + +namespace MicroElements.Metadata.ComponentModel +{ + public interface IValueObject + { + object? Value { get; } + } + + public interface IValueObject : IValueObject + { + /// + object? IValueObject.Value => Value; + + TBaseValue? Value { get; } + } + + /// + /// Generic builder interface. + /// + public interface IValueObjectBuilder + { + } + + /// + /// Generic builder that knows how to build with the provided component type. + /// It's supposed to be used with immutable objects. + /// For mutable objects use . + /// + /// Component type. + public interface IValueObjectBuilder : IValueObjectBuilder + { + /// + /// Creates a copy of the source with provided . + /// + /// The component. + /// The source copy or the source itself in case of mutable object. + object Create(TBaseValue component); + } + + public interface IValueObjectBuilder : + IValueObjectBuilder, + IValueObject + { + /// + object IValueObjectBuilder.Create(TBaseValue component) => Create(component); + + /// + /// Creates a copy of the source with provided . + /// + /// The component. + /// The source copy or the source itself in case of mutable object. + TValueObject Create(TBaseValue value); + + TValueObject CreateAuto(TBaseValue value) + { + return (TValueObject)Activator.CreateInstance(typeof(TValueObject), value); + } + + //static abstract TValueObject CreateStatic(TComponent component); + } + + // public static class aaaa + // { + // public static T Create() + // { + // + // } + // } + + public readonly record struct Cur(string Value) : IValueObjectBuilder + { + /// + public Cur Create(string value) => new Cur(value); + } + + public static class aaa + { + public static T As(this string value) + where T : struct, IValueObjectBuilder + { + return default(T).Create(value); + } + } +} diff --git a/src/MicroElements.Metadata/Metadata/Experimental/ImmutableChain.cs b/src/MicroElements.Metadata/Metadata/Experimental/ImmutableChain.cs new file mode 100644 index 0000000..5a46d62 --- /dev/null +++ b/src/MicroElements.Metadata/Metadata/Experimental/ImmutableChain.cs @@ -0,0 +1,135 @@ +// Copyright (c) MicroElements. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using MicroElements.Text.StringFormatter; + +#pragma warning disable CS1591 +#pragma warning disable SA1401: Fields should be private +#pragma warning disable SA1611: Element parameters should be documented +#pragma warning disable SA1615: Element return value should be documented +#pragma warning disable SA1618: Generic type parameters should be documented +#pragma warning disable SA1600: Elements should be documented + +namespace MicroElements.Metadata.Experimental +{ + /// + /// Immutable object chain. + /// + /// The type of elements in the chain. + public class ImmutableChain : IReadOnlyList + { + public static readonly ImmutableChain Empty = new(value: default, left: null, right: null, isValue: false); + + internal readonly T? Value; + internal readonly ImmutableChain? Left; + internal readonly ImmutableChain? Right; + + // Allows to cache values in a list for cheap iterations. + private readonly Lazy> _lazyValues; + + internal ImmutableChain(T? value = default, ImmutableChain? left = null, ImmutableChain? right = null, bool isValue = true) + { + if (isValue && left == null && right == null) + (Value, Left, Right) = (value, this, null); + else + (Value, Left, Right) = (value, left, right); + + _lazyValues = new(this.ToArray); + } + + /// + /// Gets cached values as list. Enumeration is expensive in hot paths. + /// + public IReadOnlyList Values => _lazyValues.Value; + + /// + public IEnumerator GetEnumerator() + { + if (Left != null) + { + if (Left == this) + { + yield return Value!; + } + else + { + foreach (var value in Left) + { + yield return value; + } + } + } + + if (Right != null) + { + foreach (var value in Right) + { + yield return value; + } + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public int Count => Values.Count; + + /// + public T this[int index] => Values[index]; + + /// + public override string ToString() => this.FormatAsTuple(); + } + + /// + /// ImmutableChain extensions. + /// + public static class ImmutableChain + { + /// Gets value indicating that chain is null or empty. + public static bool IsNullOrEmpty([NotNullWhen(false)] this ImmutableChain? chain) + => chain is null || (chain.Left == null && chain.Right == null); + + /// Creates chain with provided elements. Can create empty collection. + public static ImmutableChain Create(params T[]? values) => + values == null || values.Length == 0 + ? ImmutableChain.Empty + : values.Aggregate(ImmutableChain.Empty, (current, value) => current.Append(value)); + + /// + /// Appends to the end of collection. + /// + /// Prev collection instance. + /// Value to append. + /// Value type. + /// New collection instance. + public static ImmutableChain Append(this ImmutableChain? chain, T value) + => chain.Append(new ImmutableChain(value)); + + /// + /// Appends other chain to the source. + /// + /// The source collection. + /// Values to append. + /// Value type. + /// New collection instance. + public static ImmutableChain Append(this ImmutableChain? chain, ImmutableChain other) + => chain.IsNullOrEmpty() ? other : new ImmutableChain(left: chain, right: other); + + /// + /// Prepends collection with provided value. + /// + /// Prev collection instance. + /// Value to prepend. + /// Value type. + /// New collection instance. + public static ImmutableChain Prepend(this ImmutableChain? chain, T value) + => chain.IsNullOrEmpty() ? new ImmutableChain(value) : new ImmutableChain(value).Append(chain); + } +} diff --git a/src/MicroElements.Metadata/Metadata/IPropertyValue.cs b/src/MicroElements.Metadata/Metadata/IPropertyValue.cs index 23157e7..90b72ca 100644 --- a/src/MicroElements.Metadata/Metadata/IPropertyValue.cs +++ b/src/MicroElements.Metadata/Metadata/IPropertyValue.cs @@ -42,6 +42,12 @@ public interface IPropertyValue : IPropertyValue /// Gets property value. /// T? Value { get; } + + /// + IProperty IPropertyValue.PropertyUntyped => Property; + + /// + object? IPropertyValue.ValueUntyped => Source == ValueSource.NotDefined ? null : Value; } /// diff --git a/src/MicroElements.Metadata/Metadata/Indexed.cs b/src/MicroElements.Metadata/Metadata/Indexed.cs new file mode 100644 index 0000000..99909ac --- /dev/null +++ b/src/MicroElements.Metadata/Metadata/Indexed.cs @@ -0,0 +1,211 @@ +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace MicroElements.Metadata; + +public interface IIndexedPropertyContainer +{ + IPropertyValue? GetPropertyValue(IProperty property); + + int GetPropertyIndex(IProperty property); + //IPropertyValue? GetPropertyValue(int index); +} + +public class PropertyIndex +{ + private Dictionary _propertyIndex; + + public IReadOnlyList Properties { get; } + + public IEqualityComparer PropertyComparer { get; } + + public IPropertyValueFactory PropertyValueFactory { get; } + + + public PropertyIndex(IPropertySet propertySet, IEqualityComparer propertyComparer) + { + if (propertySet.GetProperties() is IReadOnlyList properties) + { + Properties = properties; + } + else + { + Properties = propertySet.GetProperties().ToArray(); + } + + PropertyComparer = propertyComparer; + + _propertyIndex = new Dictionary(PropertyComparer); + } + + public int GetPropertyIndex(IProperty property) + { + return _propertyIndex.GetValueOrDefault(property, -1); + } +} + +public class IndexedPropertyContainer2 : IPropertyContainer, IIndexedPropertyContainer +{ + private readonly PropertyIndex _propertyIndex; + + private readonly object[] _values; + private readonly ValueSource[] _valueSource; + + public IndexedPropertyContainer2(PropertyIndex propertyIndex) + { + _propertyIndex = propertyIndex; + + int propertiesCount = _propertyIndex.Properties.Count; + _values = new object[propertiesCount]; + _valueSource = new ValueSource[propertiesCount]; + } + + /// + public IPropertyValue? GetPropertyValue(IProperty property) + { + int propertyIndex = _propertyIndex.GetPropertyIndex(property); + + if (propertyIndex >= 0) + { + return PropertyValue.Create(_propertyIndex.Properties[propertyIndex], _values[propertyIndex], _valueSource[propertyIndex]); + } + + return null; + } + + /// + public int GetPropertyIndex(IProperty property) + { + throw new System.NotImplementedException(); + } + + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < _propertyIndex.Properties.Count; i++) + { + yield return PropertyValue.Create(_propertyIndex.Properties[i], _values[i], _valueSource[i]); + } + } + + /// + public IReadOnlyCollection Properties => this.ToArray(); + + /// + public int Count => _values.Length; + + /// + public IPropertyContainer? ParentSource { get; } + + /// + public SearchOptions SearchOptions { get; } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + +public sealed class IndexedPropertyContainer : IPropertyContainer, IIndexedPropertyContainer +{ + public static SearchOptions DefaultSearchOptions = SearchOptions.ExistingOnly.WithPropertyComparer(ByTypeAndNamePropertyComparer.Strict); + private readonly IPropertyContainer _propertyContainer; + private readonly Dictionary _propertyValuesDictionary; + private readonly SearchOptions _searchOptions; + + public IndexedPropertyContainer(IPropertyContainer propertyContainer, SearchOptions? searchOptions = null) + { + _propertyContainer = propertyContainer; + _searchOptions = searchOptions ?? propertyContainer.SearchOptions; + _propertyValuesDictionary = MakeIndex(propertyContainer); + } + + private Dictionary MakeIndex(IPropertyContainer propertyContainer) + { + var propertyValues = new Dictionary(_searchOptions.PropertyComparer); + + foreach (IPropertyValue propertyValue in propertyContainer) + { + propertyValues[propertyValue.PropertyUntyped] = propertyValue; + } + + return propertyValues; + } + + public IPropertyValue? GetPropertyValue(IProperty property) + { + return _propertyValuesDictionary.GetValueOrDefault(property); + } + + /// + public int GetPropertyIndex(IProperty property) + { + throw new System.NotImplementedException(); + } + + public SearchOptions SearchOptions => _searchOptions; + + public IPropertyContainer? ParentSource => _propertyContainer.ParentSource; + + public IReadOnlyCollection Properties => _propertyContainer.Properties; + + public int Count => _propertyContainer.Count; + + public IPropertyContainer GetMetadataContainer(bool autoCreate = false) + => _propertyContainer.GetMetadataContainer(autoCreate); + + public void SetMetadataContainer(IPropertyContainer metadata) + => _propertyContainer.SetMetadataContainer(metadata); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() => _propertyContainer.GetEnumerator(); +} + +public sealed class IndexedSearch : ISearchAlgorithm +{ + private ISearchAlgorithm _searchAlgorithm; + private ConcurrentDictionary, ConcurrentDictionary> _index = new (); + + public IndexedSearch(ISearchAlgorithm searchAlgorithm) + { + _searchAlgorithm = searchAlgorithm; + } + + public IPropertyValue? SearchPropertyValueUntyped(IPropertyContainer propertyContainer, IProperty property, SearchOptions? searchOptions = null) + { + if (propertyContainer is IIndexedPropertyContainer indexed) + { + return indexed.GetPropertyValue(property); + } + + return _searchAlgorithm.SearchPropertyValueUntyped(propertyContainer, property, searchOptions); + } + + public IPropertyValue? GetPropertyValue(IPropertyContainer propertyContainer, IProperty property, SearchOptions? searchOptions = null) + { + if (propertyContainer is IIndexedPropertyContainer indexed) + { + return (IPropertyValue)indexed.GetPropertyValue(property); + } + + return _searchAlgorithm.GetPropertyValue(propertyContainer, property, searchOptions); + } + + /// + public void GetPropertyValue2(IPropertyContainer propertyContainer, IProperty property, SearchOptions? searchOptions, + out PropertyValueData result) + { + throw new System.NotImplementedException(); + } +} + +public static class IndexedExtensions +{ + public static IPropertyContainer? Indexed(this IPropertyContainer? propertyContainer, SearchOptions? searchOptions) + { + if (propertyContainer == null) + return null; + return new IndexedPropertyContainer(propertyContainer, searchOptions ?? IndexedPropertyContainer.DefaultSearchOptions); + } +} diff --git a/src/MicroElements.Metadata/Metadata/MetadataProvider.Cached.cs b/src/MicroElements.Metadata/Metadata/MetadataProvider.Cached.cs new file mode 100644 index 0000000..002df12 --- /dev/null +++ b/src/MicroElements.Metadata/Metadata/MetadataProvider.Cached.cs @@ -0,0 +1,22 @@ +using MicroElements.Collections.TwoLayerCache; + +namespace MicroElements.Metadata; + +public static partial class MetadataProviderExtensions +{ + /// + /// Cached version of . + /// + /// + public static TMetadata? GetMetadataCached( + this IMetadataProvider metadataProvider, + string? metadataName = null, + TMetadata? defaultValue = default, + bool searchInSchema = false) + { + return TwoLayerCache + .Instance<(IMetadataProvider MetadataProvider, string? MetadataName, bool SearchInSchema), TMetadata?>() + .GetOrAdd((metadataProvider, metadataName, searchInSchema), (key, def) => + key.MetadataProvider.GetMetadata(key.MetadataName, searchInSchema: key.SearchInSchema, defaultValue: def), defaultValue); + } +} diff --git a/src/MicroElements.Metadata/Metadata/MetadataProviderExtensions.cs b/src/MicroElements.Metadata/Metadata/MetadataProviderExtensions.cs index b3ec322..6a8043f 100644 --- a/src/MicroElements.Metadata/Metadata/MetadataProviderExtensions.cs +++ b/src/MicroElements.Metadata/Metadata/MetadataProviderExtensions.cs @@ -11,7 +11,7 @@ namespace MicroElements.Metadata /// /// Provides extension methods for metadata providers. /// - public static class MetadataProviderExtensions + public static partial class MetadataProviderExtensions { /// /// Returns a value indicating whether the metadata provider has any metadata. diff --git a/src/MicroElements.Metadata/Metadata/Parsing/IParseResult.cs b/src/MicroElements.Metadata/Metadata/Parsing/IParseResult.cs index ec2310d..1644b64 100644 --- a/src/MicroElements.Metadata/Metadata/Parsing/IParseResult.cs +++ b/src/MicroElements.Metadata/Metadata/Parsing/IParseResult.cs @@ -47,7 +47,7 @@ public interface IParseResult /// Strong typed parse result. /// /// Value type. - public interface IParseResult : IParseResult + public interface IParseResult : IParseResult { /// /// Gets result value. diff --git a/src/MicroElements.Metadata/Metadata/PropertyCalculator.cs b/src/MicroElements.Metadata/Metadata/PropertyCalculator.cs index 81f2960..7428cc4 100644 --- a/src/MicroElements.Metadata/Metadata/PropertyCalculator.cs +++ b/src/MicroElements.Metadata/Metadata/PropertyCalculator.cs @@ -12,6 +12,27 @@ namespace MicroElements.Metadata /// Value type. public delegate T? CalculateDelegate(ref CalculationContext context); + public delegate T? CalculateDelegate(ref CalculationContext context, TState state); + + public sealed class PropertyCalculator : IPropertyCalculator + { + private readonly CalculateDelegate? _calculate; + private readonly TState _state; + + public PropertyCalculator(CalculateDelegate? calculate, TState state) + { + _calculate = calculate; + _state = state; + } + + /// + public T? Calculate(ref CalculationContext context) + { + var calculationResult = _calculate(ref context, _state); + return calculationResult; + } + } + /// /// Property calculator that accepts evaluate func in simple and full form. /// diff --git a/src/MicroElements.Metadata/Metadata/PropertyContainer.cs b/src/MicroElements.Metadata/Metadata/PropertyContainer.cs index 1a1aaf0..687503d 100644 --- a/src/MicroElements.Metadata/Metadata/PropertyContainer.cs +++ b/src/MicroElements.Metadata/Metadata/PropertyContainer.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using MicroElements.Metadata.Formatting; @@ -55,22 +54,22 @@ public PropertyContainer( } /// - public IEnumerator GetEnumerator() => Properties.GetEnumerator(); + public IReadOnlyCollection Properties { get; } /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IPropertyContainer? ParentSource { get; } /// - public int Count => Properties.Count; + public SearchOptions SearchOptions { get; } /// - public IPropertyContainer? ParentSource { get; } + public int Count => Properties.Count; /// - public IReadOnlyCollection Properties { get; } + public IEnumerator GetEnumerator() => Properties.GetEnumerator(); /// - public SearchOptions SearchOptions { get; } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// public override string ToString() diff --git a/src/MicroElements.Metadata/Metadata/PropertyExtensions.Map.cs b/src/MicroElements.Metadata/Metadata/PropertyExtensions.Map.cs new file mode 100644 index 0000000..5f15997 --- /dev/null +++ b/src/MicroElements.Metadata/Metadata/PropertyExtensions.Map.cs @@ -0,0 +1,62 @@ +using System; +using MicroElements.Metadata.Schema; +using MicroElements.Reflection.ObjectExtensions; + +namespace MicroElements.Metadata; + +public static partial class PropertyExtensions +{ + public static IProperty MapNew( + this IProperty property, + Func map, + bool allowMapNull = false, + bool allowMapUndefined = false, + Func? configureSearch = null) + { + var state = new MapState(property, map, allowMapNull, allowMapUndefined, configureSearch); + var propertyCalculator = new PropertyCalculator>( + calculate: static (ref CalculationContext context, MapState mapState) + => MapInternal(ref context, mapState), state); + + return new Property(property.Name) + .With(description: property.Description, alias: property.Alias) + .WithPropertyCalculator(propertyCalculator); + } + + private sealed record MapState( + IProperty property, + Func map, + bool allowMapNull = false, + bool allowMapUndefined = false, + Func? configureSearch = null); + + private static TResult? MapInternal(ref CalculationContext context, MapState state) + { + var search = state.configureSearch?.Invoke(context.SearchOptions) ?? context.SearchOptions; + //IPropertyValue? sourcePropertyValue = context.PropertyContainer.GetPropertyValue(state.property, search); + DefaultSearchAlgorithm.Instance.GetPropertyValue2(context.PropertyContainer, state.property, search, out var sourcePropertyValue); + + if (sourcePropertyValue.Source != ValueSource.NotDefined) + { + TSource? sourceValue = sourcePropertyValue.Value; + if (!sourceValue.IsNull() || state.allowMapNull) + { + TResult? resultValue1 = state.map(sourceValue); + context.ValueSource = ValueSource.Calculated; + return resultValue1; + } + } + else + { + if (state.allowMapUndefined) + { + TResult? resultValue2 = state.map(default); + context.ValueSource = ValueSource.Calculated; + return resultValue2; + } + } + + context.ValueSource = ValueSource.NotDefined; + return default; + } +} diff --git a/src/MicroElements.Metadata/Metadata/PropertyExtensions.cs b/src/MicroElements.Metadata/Metadata/PropertyExtensions.cs index 279cdd4..83307ba 100644 --- a/src/MicroElements.Metadata/Metadata/PropertyExtensions.cs +++ b/src/MicroElements.Metadata/Metadata/PropertyExtensions.cs @@ -11,7 +11,7 @@ namespace MicroElements.Metadata /// /// Property extensions. /// - public static class PropertyExtensions + public static partial class PropertyExtensions { /// /// Gets property name and possible aliases. @@ -100,7 +100,7 @@ public static IEnumerable GetNameAndAliases(this IProperty property) return new Property(property.Name) .With(description: property.Description, alias: property.Alias) - .WithCalculate(ConvertValue); + .WithCalculate((container, options) => ConvertValue(container, options)); } /// diff --git a/src/MicroElements.Metadata/Metadata/PropertySet.cs b/src/MicroElements.Metadata/Metadata/PropertySet.cs index 1c59ca9..fbe778d 100644 --- a/src/MicroElements.Metadata/Metadata/PropertySet.cs +++ b/src/MicroElements.Metadata/Metadata/PropertySet.cs @@ -10,7 +10,7 @@ namespace MicroElements.Metadata /// public sealed class PropertySet : IPropertySet { - private readonly List _properties = new List(); + private readonly List _properties = new(); /// /// Gets the property list. @@ -26,7 +26,7 @@ public sealed class PropertySet : IPropertySet /// Property list. public PropertySet(params IProperty[]? properties) { - if (properties != null && properties.Length > 0) + if (properties != null) _properties.AddRange(properties); } diff --git a/src/MicroElements.Metadata/Metadata/PropertyValue.cs b/src/MicroElements.Metadata/Metadata/PropertyValue.cs index ed3dae0..891fcdd 100644 --- a/src/MicroElements.Metadata/Metadata/PropertyValue.cs +++ b/src/MicroElements.Metadata/Metadata/PropertyValue.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Diagnostics.CodeAnalysis; using MicroElements.CodeContracts; using MicroElements.Metadata.Formatting; using MicroElements.Reflection.TypeExtensions; @@ -21,12 +20,6 @@ public sealed class PropertyValue : IPropertyValue /// public T? Value { get; } - /// - public IProperty PropertyUntyped => Property; - - /// - public object? ValueUntyped => Source == ValueSource.NotDefined ? null : Value; - /// public ValueSource Source { get; } @@ -47,8 +40,7 @@ public PropertyValue(IProperty property, T? value, ValueSource? source = null /// Implicitly converts to . /// /// PropertyValue. - [return: MaybeNull] - public static implicit operator T(PropertyValue propertyValue) => propertyValue.Value; + public static implicit operator T?(PropertyValue propertyValue) => propertyValue.Value; /// public override string ToString() => $"{Property.Name}: {Value.FormatValue("null")}"; @@ -57,7 +49,7 @@ public PropertyValue(IProperty property, T? value, ValueSource? source = null /// /// Represents value source. /// - public sealed class ValueSource + public sealed record ValueSource { /// /// Value is not set. @@ -93,18 +85,6 @@ public ValueSource(string sourceName) SourceName = sourceName.AssertArgumentNotNull(nameof(sourceName)); } - private bool Equals(ValueSource other) => SourceName == other.SourceName; - - /// - public override bool Equals(object? obj) => ReferenceEquals(this, obj) || (obj is ValueSource other && Equals(other)); - - /// - public override int GetHashCode() => SourceName.GetHashCode(); - - public static bool operator ==(ValueSource? left, ValueSource? right) => Equals(left, right); - - public static bool operator !=(ValueSource? left, ValueSource? right) => !Equals(left, right); - /// public override string ToString() => SourceName; } diff --git a/src/MicroElements.Metadata/Metadata/PropertyValueData.cs b/src/MicroElements.Metadata/Metadata/PropertyValueData.cs new file mode 100644 index 0000000..21d57a9 --- /dev/null +++ b/src/MicroElements.Metadata/Metadata/PropertyValueData.cs @@ -0,0 +1,15 @@ +namespace MicroElements.Metadata; + +public struct PropertyValueData : IPropertyValue +{ + public IProperty Property { get; } + public T? Value { get; } + public ValueSource Source { get; } + + public PropertyValueData(IProperty property, T? value, ValueSource source) + { + Property = property; + Value = value; + Source = source; + } +} diff --git a/src/MicroElements.Metadata/Metadata/PropertyValueFactory.cs b/src/MicroElements.Metadata/Metadata/PropertyValueFactory.cs index 1e9533f..4adb31a 100644 --- a/src/MicroElements.Metadata/Metadata/PropertyValueFactory.cs +++ b/src/MicroElements.Metadata/Metadata/PropertyValueFactory.cs @@ -116,6 +116,21 @@ public static Expression + public IPropertyValue Create(IProperty property, T? value, ValueSource? valueSource = null) + { + return null; + } + + /// + public IPropertyValue CreateUntyped(IProperty property, object? value, ValueSource? valueSource = null) + { + return null; + } + } + /// /// Cached factory. /// diff --git a/src/MicroElements.Metadata/Metadata/Schema/INameAlias.cs b/src/MicroElements.Metadata/Metadata/Schema/INameAlias.cs index 3c7c7e3..c6180d3 100644 --- a/src/MicroElements.Metadata/Metadata/Schema/INameAlias.cs +++ b/src/MicroElements.Metadata/Metadata/Schema/INameAlias.cs @@ -78,6 +78,19 @@ public static TSchema WithNameAlias(this TSchema source, INameAlias nam return source.WithSchemaComponent(nameAlias); } + /// + /// Creates schema copy with provided name alias. + /// + /// Schema type. + /// Source schema. + /// Description. + /// New schema instance with provided name alias. + public static TSchema WithNameAlias(this TSchema source, string nameAlias) + where TSchema : ISchemaBuilder, ISchema + { + return source.WithSchemaComponent(new NameAlias(nameAlias)); + } + /// /// Gets Alias from . /// diff --git a/src/MicroElements.Metadata/Metadata/Searching/ByTypeAndNamePropertyComparer.cs b/src/MicroElements.Metadata/Metadata/Searching/ByTypeAndNamePropertyComparer.cs index 3a9ec8a..04c1c6c 100644 --- a/src/MicroElements.Metadata/Metadata/Searching/ByTypeAndNamePropertyComparer.cs +++ b/src/MicroElements.Metadata/Metadata/Searching/ByTypeAndNamePropertyComparer.cs @@ -12,19 +12,21 @@ namespace MicroElements.Metadata /// public sealed partial class ByTypeAndNamePropertyComparer : IEqualityComparer { - private readonly StringComparison _typeNameComparison; + private readonly StringComparison _nameComparison; + private readonly StringComparer _nameComparer; private readonly bool _ignoreTypeNullability; /// /// Initializes a new instance of the class. /// - /// StringComparison for property name comparing. + /// StringComparison for property name comparing. /// Compare types ignore Nullable wrapper type. public ByTypeAndNamePropertyComparer( - StringComparison typeNameComparison = StringComparison.Ordinal, + StringComparison nameComparison = StringComparison.Ordinal, bool ignoreTypeNullability = false) { - _typeNameComparison = typeNameComparison; + _nameComparison = nameComparison; + _nameComparer = StringComparer.FromComparison(nameComparison); _ignoreTypeNullability = ignoreTypeNullability; } @@ -37,10 +39,18 @@ public bool Equals(IProperty? x, IProperty? y) if (ReferenceEquals(x, y)) return true; - Type typeX = _ignoreTypeNullability && x.Type.GetTypeInfo().IsValueType ? Nullable.GetUnderlyingType(x.Type) ?? x.Type : x.Type; - Type typeY = _ignoreTypeNullability && y.Type.GetTypeInfo().IsValueType ? Nullable.GetUnderlyingType(y.Type) ?? y.Type : y.Type; + Type typeX = x.Type; + Type typeY = y.Type; - return typeX == typeY && x.Name.Equals(y.Name, _typeNameComparison); + if (_ignoreTypeNullability) + { + if (typeX.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(typeX) is { } underlyingTypeX) + typeX = underlyingTypeX; + if (typeY.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(typeY) is { } underlyingTypeY) + typeY = underlyingTypeY; + } + + return typeX == typeY && x.Name.Equals(y.Name, _nameComparison); } /// @@ -49,10 +59,8 @@ public int GetHashCode(IProperty property) string propertyName = property.Name; Type propertyType = property.Type; - if (_typeNameComparison == StringComparison.OrdinalIgnoreCase || - _typeNameComparison == StringComparison.InvariantCultureIgnoreCase || - _typeNameComparison == StringComparison.CurrentCultureIgnoreCase) - propertyName = propertyName.ToLower(); + HashCode hashCode = default; + hashCode.Add(propertyName, _nameComparer); if (_ignoreTypeNullability && propertyType.GetTypeInfo().IsValueType @@ -61,7 +69,9 @@ public int GetHashCode(IProperty property) propertyType = underlyingType; } - return HashCode.Combine(propertyName, propertyType); + hashCode.Add(propertyType); + + return hashCode.ToHashCode(); } } @@ -73,16 +83,16 @@ public sealed partial class ByTypeAndNamePropertyComparer /// /// Gets property comparer by Type and Name. /// - public static IEqualityComparer Strict { get; } = new ByTypeAndNamePropertyComparer(typeNameComparison: StringComparison.Ordinal, ignoreTypeNullability: false); + public static IEqualityComparer Strict { get; } = new ByTypeAndNamePropertyComparer(nameComparison: StringComparison.Ordinal, ignoreTypeNullability: false); /// /// Gets property comparer by Type and Name ignore case. /// - public static IEqualityComparer IgnoreNameCase { get; } = new ByTypeAndNamePropertyComparer(typeNameComparison: StringComparison.OrdinalIgnoreCase, ignoreTypeNullability: false); + public static IEqualityComparer IgnoreNameCase { get; } = new ByTypeAndNamePropertyComparer(nameComparison: StringComparison.OrdinalIgnoreCase, ignoreTypeNullability: false); /// /// Gets property comparer by Type and Name ignoring name case and ignoring wrapper. /// - public static IEqualityComparer IgnoreNameCaseIgnoreNullability { get; } = new ByTypeAndNamePropertyComparer(typeNameComparison: StringComparison.OrdinalIgnoreCase, ignoreTypeNullability: true); + public static IEqualityComparer IgnoreNameCaseIgnoreNullability { get; } = new ByTypeAndNamePropertyComparer(nameComparison: StringComparison.OrdinalIgnoreCase, ignoreTypeNullability: true); } } diff --git a/src/MicroElements.Metadata/Metadata/Searching/DefaultSearchAlgorithm.cs b/src/MicroElements.Metadata/Metadata/Searching/DefaultSearchAlgorithm.cs index f871d4f..522ce05 100644 --- a/src/MicroElements.Metadata/Metadata/Searching/DefaultSearchAlgorithm.cs +++ b/src/MicroElements.Metadata/Metadata/Searching/DefaultSearchAlgorithm.cs @@ -17,6 +17,8 @@ public sealed class DefaultSearchAlgorithm : ISearchAlgorithm /// public static readonly ISearchAlgorithm Instance = new DefaultSearchAlgorithm(); + // TODO: Вынести IPropertyValueFactory в SearchOptions? + // TODO: IPropertyValue возвращать из Calculator? private readonly IPropertyValueFactory _propertyValueFactory = PropertyValueFactory.Default; private IPropertyValueFactoryProvider _factoryProvider = new PropertyValueFactoryProvider(comparer => PropertyValueFactory.Default); @@ -35,27 +37,35 @@ public sealed class DefaultSearchAlgorithm : ISearchAlgorithm // Search property by EqualityComparer IPropertyValue? propertyValue = null; - var properties = propertyContainer.Properties; - if (properties is IList propertyValues) + + if (propertyContainer is IIndexedPropertyContainer indexed) + { + propertyValue = indexed.GetPropertyValue(property); + } + else { - // For is for performance reason here - for (int i = 0; i < propertyValues.Count; i++) + var properties = propertyContainer.Properties; + if (properties is IList propertyValues) { - if (search.PropertyComparer.Equals(propertyValues[i].PropertyUntyped, property)) + // For is for performance reason here + for (int i = 0; i < propertyValues.Count; i++) { - propertyValue = propertyValues[i]; - break; + if (search.PropertyComparer.Equals(propertyValues[i].PropertyUntyped, property)) + { + propertyValue = propertyValues[i]; + break; + } } } - } - else - { - foreach (IPropertyValue? pv in propertyContainer.Properties) + else { - if (search.PropertyComparer.Equals(pv.PropertyUntyped, property)) + foreach (IPropertyValue? pv in propertyContainer.Properties) { - propertyValue = pv; - break; + if (search.PropertyComparer.Equals(pv.PropertyUntyped, property)) + { + propertyValue = pv; + break; + } } } } @@ -107,7 +117,7 @@ public sealed class DefaultSearchAlgorithm : ISearchAlgorithm var calculationContext = new CalculationContext(propertyContainer, search); var calculatedValue = calculator.Calculate(ref calculationContext); var calculatedValueSource = calculationContext.ValueSource ?? ValueSource.NotDefined; - var calculatedPropertyValue = _propertyValueFactory.Create(property, calculatedValue, calculatedValueSource); + IPropertyValue? calculatedPropertyValue = _propertyValueFactory.Create(property, calculatedValue, calculatedValueSource); if (calculatedPropertyValue.Source == ValueSource.NotDefined && !search.ReturnNotDefined) calculatedPropertyValue = null; @@ -131,5 +141,58 @@ public sealed class DefaultSearchAlgorithm : ISearchAlgorithm // Return null or NotDefined return search.ReturnNotDefined ? _propertyValueFactory.Create(property, default, ValueSource.NotDefined) : null; } + + /// + public void GetPropertyValue2( + IPropertyContainer propertyContainer, + IProperty property, + SearchOptions? searchOptions, + out PropertyValueData result) + { + SearchOptions search = searchOptions ?? propertyContainer.SearchOptions; + + // Base search by ByReferenceComparer. + IPropertyValue? propertyValue = SearchPropertyValueUntyped(propertyContainer, property, _fastSearchOptions) as IPropertyValue; + + // Good job - return result! + if (propertyValue != null) + { + result = new PropertyValueData(property, propertyValue.Value, propertyValue.Source); + return; + } + + // Property can be calculated. + if (search.CalculateValue && property.GetCalculator() is { } calculator) + { + var calculationContext = new CalculationContext(propertyContainer, search); + var calculatedValue = calculator.Calculate(ref calculationContext); + var calculatedValueSource = calculationContext.ValueSource ?? ValueSource.NotDefined; + + result = new PropertyValueData(property, calculatedValue, calculatedValueSource); + return; + } + + // Search by provided options. + propertyValue = SearchPropertyValueUntyped(propertyContainer, property, search + .UseDefaultValue(false) + .ReturnNull()) as IPropertyValue; + + // Nice. We have result! + if (propertyValue != null) + { + result = new PropertyValueData(property, propertyValue.Value, propertyValue.Source); + return; + } + + // Maybe default value? + if (search.UseDefaultValue && property.DefaultValue is { } defaultValue) + { + result = new PropertyValueData(property, defaultValue.Value, ValueSource.DefaultValue); + return; + } + + // Return null or NotDefined + result = new PropertyValueData(property, default, ValueSource.NotDefined); + } } } diff --git a/src/MicroElements.Metadata/Metadata/Searching/ISearchAlgorithm.cs b/src/MicroElements.Metadata/Metadata/Searching/ISearchAlgorithm.cs index d818df4..2d6a3b9 100644 --- a/src/MicroElements.Metadata/Metadata/Searching/ISearchAlgorithm.cs +++ b/src/MicroElements.Metadata/Metadata/Searching/ISearchAlgorithm.cs @@ -32,5 +32,11 @@ public interface ISearchAlgorithm IPropertyContainer propertyContainer, IProperty property, SearchOptions? searchOptions = null); + + void GetPropertyValue2( + IPropertyContainer propertyContainer, + IProperty property, + SearchOptions? searchOptions, + out PropertyValueData result); } } diff --git a/src/MicroElements.Metadata/Metadata/Searching/PropertyComparer.cs b/src/MicroElements.Metadata/Metadata/Searching/PropertyComparer.cs index 4e08e40..c788ad7 100644 --- a/src/MicroElements.Metadata/Metadata/Searching/PropertyComparer.cs +++ b/src/MicroElements.Metadata/Metadata/Searching/PropertyComparer.cs @@ -38,12 +38,12 @@ public static class PropertyComparer /// /// Property comparer by Type and Name ignore case. /// - public static IEqualityComparer ByTypeAndNameIgnoreCaseComparer { get; } = new ByTypeAndNamePropertyComparer(typeNameComparison: StringComparison.OrdinalIgnoreCase, ignoreTypeNullability: false); + public static IEqualityComparer ByTypeAndNameIgnoreCaseComparer { get; } = new ByTypeAndNamePropertyComparer(nameComparison: StringComparison.OrdinalIgnoreCase, ignoreTypeNullability: false); /// /// Property comparer by Type and Name ignoring name case and ignoring wrapper. /// - public static IEqualityComparer ByTypeAndNameIgnoreCaseIgnoreNullability { get; } = new ByTypeAndNamePropertyComparer(typeNameComparison: StringComparison.OrdinalIgnoreCase, ignoreTypeNullability: true); + public static IEqualityComparer ByTypeAndNameIgnoreCaseIgnoreNullability { get; } = new ByTypeAndNamePropertyComparer(nameComparison: StringComparison.OrdinalIgnoreCase, ignoreTypeNullability: true); /// /// Property comparer by or ignore case. diff --git a/src/MicroElements.Metadata/Metadata/Searching/SearchExtensions.cs b/src/MicroElements.Metadata/Metadata/Searching/SearchExtensions.cs index b3f8e5c..f5b90d7 100644 --- a/src/MicroElements.Metadata/Metadata/Searching/SearchExtensions.cs +++ b/src/MicroElements.Metadata/Metadata/Searching/SearchExtensions.cs @@ -157,7 +157,7 @@ public static partial class SearchExtensions /// Property container. /// Search conditions. /// or null. - public static IPropertyValue? GetPropertyValue(this IPropertyContainer propertyContainer, [DisallowNull] SearchOptions search) + public static IPropertyValue? GetPropertyValue(this IPropertyContainer propertyContainer, SearchOptions search) { if (search.SearchProperty == null) throw new InvalidOperationException("SearchProperty must be set in SearchOptions."); @@ -174,7 +174,7 @@ public static partial class SearchExtensions /// Property container. /// Search conditions. /// or null. - public static IPropertyValue? SearchPropertyValueUntyped(this IPropertyContainer propertyContainer, [DisallowNull] SearchOptions search) + public static IPropertyValue? SearchPropertyValueUntyped(this IPropertyContainer propertyContainer, SearchOptions search) { if (search.SearchProperty == null) throw new InvalidOperationException("SearchProperty must be set in SearchOptions."); diff --git a/src/MicroElements.Metadata/MicroElements.Metadata.csproj b/src/MicroElements.Metadata/MicroElements.Metadata.csproj index 4115b7d..466c3af 100644 --- a/src/MicroElements.Metadata/MicroElements.Metadata.csproj +++ b/src/MicroElements.Metadata/MicroElements.Metadata.csproj @@ -5,13 +5,13 @@ netstandard2.1 MicroElements - 9.0 + 10 enable - + diff --git a/src/MicroElements.Metadata/Validation/ValidationResult.cs b/src/MicroElements.Metadata/Validation/ValidationResult.cs index ed20dc5..d7f8bc1 100644 --- a/src/MicroElements.Metadata/Validation/ValidationResult.cs +++ b/src/MicroElements.Metadata/Validation/ValidationResult.cs @@ -36,6 +36,17 @@ public ValidationResult(T data, IReadOnlyCollection? validationMessages ValidationMessages = validationMessages ?? Array.Empty(); } + /// + /// Initializes a new instance of the struct. + /// + /// Data that was validated. + /// Validation messages. + public ValidationResult(T data, params Message[]? validationMessages) + { + Data = data.AssertArgumentNotNull(nameof(data)); + ValidationMessages = validationMessages ?? Array.Empty(); + } + /// /// Validation result can be implicitly cast to its Data type. /// diff --git a/test/MicroElements.Metadata.Benchmarks/MapBench.cs b/test/MicroElements.Metadata.Benchmarks/MapBench.cs new file mode 100644 index 0000000..afad214 --- /dev/null +++ b/test/MicroElements.Metadata.Benchmarks/MapBench.cs @@ -0,0 +1,41 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +namespace MicroElements.Metadata.Benchmarks +{ + [MemoryDiagnoser] + [Config(typeof(CustomConfig))] + [Orderer(SummaryOrderPolicy.FastestToSlowest)] + public class MapBench + { + public static Property Prop1 = new Property("Prop1"); + public static Property Name = new Property("Name"); + public static IPropertyContainer Container = new MutablePropertyContainer() + .WithValue(Prop1, "Prop1") + .WithValue(Name, "Alex"); + + IProperty mappedName1 = Name.Map(name => name); + IProperty mappedName2 = Name.MapNew(name => name); + + IProperty mappedName3 = Name.Map(value => ""); + + [Benchmark] + public string NoMap() + { + return Container.GetValue(Name); + } + + [Benchmark] + public string Map() + { + return Container.GetValue(mappedName1); + } + + [Benchmark(Baseline = true)] + public string MapNew() + { + DefaultSearchAlgorithm.Instance.GetPropertyValue2(Container, mappedName2, null, out var result); + return result.Value; + } + } +} diff --git a/test/MicroElements.Metadata.Benchmarks/Program.cs b/test/MicroElements.Metadata.Benchmarks/Program.cs index c0313d7..ad56bc6 100644 --- a/test/MicroElements.Metadata.Benchmarks/Program.cs +++ b/test/MicroElements.Metadata.Benchmarks/Program.cs @@ -8,7 +8,7 @@ public class Program { public static void Main(string[] args) { - var summary = BenchmarkRunner.Run(); + var summary = BenchmarkRunner.Run(); } } diff --git a/test/MicroElements.Metadata.Benchmarks/StructInRef.cs b/test/MicroElements.Metadata.Benchmarks/StructInRef.cs new file mode 100644 index 0000000..2f9fdc9 --- /dev/null +++ b/test/MicroElements.Metadata.Benchmarks/StructInRef.cs @@ -0,0 +1,63 @@ +using System; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +namespace MicroElements.Metadata.Benchmarks +{ + [MemoryDiagnoser] + [Config(typeof(CustomConfig))] + [Orderer(SummaryOrderPolicy.FastestToSlowest)] + public class ConfigureStructOrStructRef + { + public class MutableData + { + public string Text { get; set; } + public double Number { get; set; } + } + + public readonly struct ExcelMeta + { + public MutableData Data1 { get; } + public MutableData Data2 { get; } + public MutableData Data3 { get; } + public MutableData Data4 { get; } + public MutableData Data5 { get; } + + public ExcelMeta(MutableData data1, MutableData data2, MutableData data3, MutableData data4, MutableData data5) + { + Data1 = data1; + Data2 = data2; + Data3 = data3; + Data4 = data4; + Data5 = data5; + } + } + + public delegate void RefAction(ref T value); + + public static Action Configure = (meta => meta.Data1.Text = "text"); + public static RefAction ConfigureRef = (ref ExcelMeta meta) => meta.Data1.Text = "text"; + + public static MutableData Data1 = new MutableData(); + public static MutableData Data2 = new MutableData(); + public static MutableData Data3 = new MutableData(); + public static MutableData Data4 = new MutableData(); + public static MutableData Data5 = new MutableData(); + + [Benchmark(Baseline = true)] + public string ConfigureStruct() + { + ExcelMeta excelMeta = new ExcelMeta(Data1, Data2, Data3, Data4, Data5); + Configure(excelMeta); + return excelMeta.Data1.Text; + } + + [Benchmark] + public string ConfigureStructRef() + { + ExcelMeta excelMeta = new ExcelMeta(Data1, Data2, Data3, Data4, Data5); + ConfigureRef(ref excelMeta); + return excelMeta.Data1.Text; + } + } +} diff --git a/test/MicroElements.Metadata.Tests/MetadataTests.cs b/test/MicroElements.Metadata.Tests/MetadataTests.cs index 95ca00b..3e6746b 100644 --- a/test/MicroElements.Metadata.Tests/MetadataTests.cs +++ b/test/MicroElements.Metadata.Tests/MetadataTests.cs @@ -91,8 +91,8 @@ public void DynamicContainer() [Fact] public void PropertySetTests() { - PropertySet propertySet = new PropertySet(new[] {new Property("prop1"), new Property("prop2"),}); - propertySet.Properties.Count().Should().Be(2); + PropertySet propertySet = new(new Property("prop1"), new Property("prop2")); + propertySet.Properties.Count.Should().Be(2); propertySet.Properties.Last().Name.Should().Be("prop2"); } diff --git a/test/MicroElements.Metadata.Tests/PropertyMapTests.cs b/test/MicroElements.Metadata.Tests/PropertyMapTests.cs index 7c1bcd6..2ffd7e0 100644 --- a/test/MicroElements.Metadata.Tests/PropertyMapTests.cs +++ b/test/MicroElements.Metadata.Tests/PropertyMapTests.cs @@ -26,6 +26,10 @@ public void calculate_value_if_base_property_value_provided() .WithValue(Name, "Alex"); container.GetValue(Name.Map(name => $"Calculated {name}")).Should().Be("Calculated Alex"); + container.GetValue(Name.MapNew(name => $"Calculated {name}")).Should().Be("Calculated Alex"); + + DefaultSearchAlgorithm.Instance.GetPropertyValue2(container, Name.MapNew(name => $"Calculated {name}"), null, out var result); + result.Value.Should().Be("Calculated Alex"); } [Fact] @@ -136,7 +140,7 @@ public void TestComplexProperty() IProperty nullableDouble = new Property("nullableDouble"); IProperty deNullify = nullableDouble.DeNullify(); var property = deNullify.UseDefaultForUndefined(); - + var propertyContainer = new PropertyContainer(); var propertyValue = propertyContainer.GetPropertyValue(property); diff --git a/test/MicroElements.Metadata.Tests/PropertyTests.cs b/test/MicroElements.Metadata.Tests/PropertyTests.cs index b2dc400..4e79dd0 100644 --- a/test/MicroElements.Metadata.Tests/PropertyTests.cs +++ b/test/MicroElements.Metadata.Tests/PropertyTests.cs @@ -4,6 +4,7 @@ using FluentAssertions; using MicroElements.Diagnostics; using MicroElements.Metadata.ComponentModel; +using MicroElements.Metadata.Experimental; using MicroElements.Metadata.Schema; using MicroElements.Metadata.Xml; using Xunit; @@ -12,6 +13,27 @@ namespace MicroElements.Metadata.Tests { public class PropertyTests { + [Fact] + public static void ImmutableChainTest() + { + ImmutableChain? chain_null = null; + ImmutableChain chain_null_1 = chain_null.Append(1); + chain_null_1.Values.Should().Equal(1); + + ImmutableChain chain_1 = ImmutableChain.Create(1); + chain_1.Values.Should().Equal(1); + ImmutableChain chain_1_2 = chain_1.Append(2); + ImmutableChain chain_1_2_3 = chain_1_2.Append(3); + ImmutableChain chain_1_2_3_4 = chain_1_2_3.Append(4); + chain_1_2_3_4.Values.Should().Equal(1, 2, 3, 4); + + ImmutableChain chain_1_2_1_2 = chain_1_2.Append(chain_1_2); + chain_1_2_1_2.Values.Should().Equal(1, 2, 1, 2); + + ImmutableChain chain_3_1_2 = chain_1_2.Prepend(3); + chain_3_1_2.Values.Should().Equal(3, 1, 2); + } + [Fact] public void empty_property_should_be_empty() { @@ -49,7 +71,7 @@ public void property_with_name_should_change_name() var test4 = CreateFilledProperty().WithNameUntyped("test4"); ((IProperty)test4).ShouldBeFilled(name: "test4"); - var test5 = CreateFilledProperty().WithAliasUntyped("alias5"); + var test5 = CreateFilledProperty().WithNameAlias("alias5");// .WithAliasUntyped("alias5"); ((IProperty)test5).ShouldBeFilled(alias: "alias5"); } @@ -167,7 +189,7 @@ public XmlLineInfo(int lineNumber, int linePosition) [Fact] public void message_builder1() - { + { string? parseResultErrorMessage = "ParseError"; var message = ValueMessageBuilder @@ -271,7 +293,7 @@ public void property_calculator() { Property withCalculate = new Property("Currency") .WithCalculate(container => "EUR"); - + } [Fact] @@ -346,7 +368,7 @@ public static Property CreateFilledProperty() } public static void ShouldBeFilled( - this IProperty property, + this IProperty property, string? name = "test", string? alias = "alias") { diff --git a/test/MicroElements.Metadata.Tests/PropertyValueTests.cs b/test/MicroElements.Metadata.Tests/PropertyValueTests.cs index 2d4e54d..cc0024f 100644 --- a/test/MicroElements.Metadata.Tests/PropertyValueTests.cs +++ b/test/MicroElements.Metadata.Tests/PropertyValueTests.cs @@ -8,9 +8,14 @@ public class PropertyValueTests [Fact] public void not_defined_property_value_should_return_null() { - new PropertyValue(new Property("IntDefined"), 1).ValueUntyped.Should().Be(1); - new PropertyValue(new Property("IntDefined"), 0).ValueUntyped.Should().Be(0); - new PropertyValue(new Property("IntNotDefined"), 0, ValueSource.NotDefined).ValueUntyped.Should().Be(null); + IPropertyValue intDefined = new PropertyValue(new Property("IntDefined"), 1); + intDefined.ValueUntyped.Should().Be(1); + + IPropertyValue intDefined0 = new PropertyValue(new Property("IntDefined"), 0); + intDefined0.ValueUntyped.Should().Be(0); + + IPropertyValue intNotDefined = new PropertyValue(new Property("IntNotDefined"), 0, ValueSource.NotDefined); + intNotDefined.ValueUntyped.Should().Be(null); } } }