Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 193 additions & 0 deletions Foundation.Data.Doublets.Cli.Tests/NamedTypesDecoratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using Xunit;
using Platform.Memory;
using Platform.Data.Doublets;
using Platform.Data.Doublets.Memory.United.Generic;
using Foundation.Data.Doublets.Cli;
using DoubletLink = Platform.Data.Doublets.Link<uint>;

namespace Foundation.Data.Doublets.Cli.Tests
{
public class NamedTypesDecoratorTests : IDisposable
{
private readonly string _tempDbPath;
private readonly string _tempNamesDbPath;

public NamedTypesDecoratorTests()
{
_tempDbPath = Path.GetTempFileName();
_tempNamesDbPath = Path.GetTempFileName();
}

public void Dispose()
{
if (File.Exists(_tempDbPath)) File.Delete(_tempDbPath);
if (File.Exists(_tempNamesDbPath)) File.Delete(_tempNamesDbPath);
}

private static void RunTestWithLinks(Action<ILinks<uint>> testAction)
{
var tempFile = Path.GetTempFileName();
try
{
using var memory = new FileMappedResizableDirectMemory(tempFile, UnitedMemoryLinks<uint>.DefaultLinksSizeStep);
using var unitedMemoryLinks = new UnitedMemoryLinks<uint>(memory);
var linksDecoratedWithAutomaticUniquenessResolution = unitedMemoryLinks.DecorateWithAutomaticUniquenessAndUsagesResolution();
testAction(linksDecoratedWithAutomaticUniquenessResolution);
}
finally
{
if (File.Exists(tempFile)) File.Delete(tempFile);
}
}

[Fact]
public void NamedTypesDecorator_ImplementsILinks()
{
RunTestWithLinks(links =>
{
var decorator = new NamedTypesDecorator<uint>(links, _tempNamesDbPath);

Assert.True(decorator is ILinks<uint>);
});
}

[Fact]
public void NamedTypesDecorator_ImplementsINamedTypes()
{
RunTestWithLinks(links =>
{
var decorator = new NamedTypesDecorator<uint>(links, _tempNamesDbPath);

Assert.True(decorator is INamedTypes<uint>);
});
}

[Fact]
public void NamedTypesDecorator_CanSetAndGetNames()
{
RunTestWithLinks(links =>
{
var decorator = new NamedTypesDecorator<uint>(links, _tempNamesDbPath);

var link1 = decorator.GetOrCreate(10u, 20u);
var link2 = decorator.GetOrCreate(30u, 40u);

var nameLink1 = decorator.SetName(link1, "TestLink1");
var nameLink2 = decorator.SetName(link2, "TestLink2");

Assert.NotEqual(links.Constants.Null, nameLink1);
Assert.NotEqual(links.Constants.Null, nameLink2);

var retrievedName1 = decorator.GetName(link1);
var retrievedName2 = decorator.GetName(link2);

Assert.Equal("TestLink1", retrievedName1);
Assert.Equal("TestLink2", retrievedName2);
});
}

[Fact]
public void NamedTypesDecorator_CanGetLinkByName()
{
RunTestWithLinks(links =>
{
var decorator = new NamedTypesDecorator<uint>(links, _tempNamesDbPath);

var link = decorator.GetOrCreate(50u, 60u);
decorator.SetName(link, "UniqueTestName");

var retrievedLink = decorator.GetByName("UniqueTestName");

Assert.Equal(link, retrievedLink);
});
}

[Fact]
public void NamedTypesDecorator_CanRemoveNames()
{
RunTestWithLinks(links =>
{
var decorator = new NamedTypesDecorator<uint>(links, _tempNamesDbPath);

var link = decorator.GetOrCreate(70u, 80u);
decorator.SetName(link, "TemporaryName");

var nameBeforeRemoval = decorator.GetName(link);
Assert.Equal("TemporaryName", nameBeforeRemoval);

decorator.RemoveName(link);

var nameAfterRemoval = decorator.GetName(link);
Assert.Null(nameAfterRemoval);

var linkByName = decorator.GetByName("TemporaryName");
Assert.Equal(links.Constants.Null, linkByName);
});
}

[Fact]
public void NamedTypesDecorator_CanOverwriteNames()
{
RunTestWithLinks(links =>
{
var decorator = new NamedTypesDecorator<uint>(links, _tempNamesDbPath);

var link = decorator.GetOrCreate(90u, 100u);
decorator.SetName(link, "FirstName");

var firstRetrievedName = decorator.GetName(link);
Assert.Equal("FirstName", firstRetrievedName);

decorator.SetName(link, "SecondName");

var secondRetrievedName = decorator.GetName(link);
Assert.Equal("SecondName", secondRetrievedName);

var linkByFirstName = decorator.GetByName("FirstName");
Assert.Equal(links.Constants.Null, linkByFirstName);

var linkBySecondName = decorator.GetByName("SecondName");
Assert.Equal(link, linkBySecondName);
});
}

[Fact]
public void NamedTypesDecorator_DeleteRemovesAssociatedNames()
{
RunTestWithLinks(links =>
{
var decorator = new NamedTypesDecorator<uint>(links, _tempNamesDbPath);

var link = decorator.GetOrCreate(110u, 120u);
decorator.SetName(link, "LinkToDelete");

var nameBeforeDeletion = decorator.GetName(link);
Assert.Equal("LinkToDelete", nameBeforeDeletion);

decorator.Delete(new uint[] { link }, null);

var linkByName = decorator.GetByName("LinkToDelete");
Assert.Equal(links.Constants.Null, linkByName);
});
}

[Fact]
public void NamedTypesDecorator_HandlesNonexistentNames()
{
RunTestWithLinks(links =>
{
var decorator = new NamedTypesDecorator<uint>(links, _tempNamesDbPath);

var linkByNonexistentName = decorator.GetByName("NonexistentName");
Assert.Equal(links.Constants.Null, linkByNonexistentName);

var nameOfNonexistentLink = decorator.GetName(999999u);
Assert.Null(nameOfNonexistentLink);
});
}
}
}
13 changes: 13 additions & 0 deletions Foundation.Data.Doublets.Cli/INamedTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Numerics;

namespace Foundation.Data.Doublets.Cli
{
public interface INamedTypes<TLinkAddress>
where TLinkAddress : IUnsignedNumber<TLinkAddress>
{
string? GetName(TLinkAddress link);
TLinkAddress SetName(TLinkAddress link, string name);
TLinkAddress GetByName(string name);
void RemoveName(TLinkAddress link);
}
}
136 changes: 136 additions & 0 deletions Foundation.Data.Doublets.Cli/NamedTypesDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System.Numerics;
using Platform.Delegates;
using Platform.Memory;
using Platform.Data;
using Platform.Data.Doublets;
using Platform.Data.Doublets.Decorators;
using Platform.Data.Doublets.Memory;
using Platform.Data.Doublets.Memory.United.Generic;
using System;
using System.Linq;
using System.Collections.Generic;

namespace Foundation.Data.Doublets.Cli
{
public class NamedTypesDecorator<TLinkAddress> : LinksDecoratorBase<TLinkAddress>, INamedTypes<TLinkAddress>
where TLinkAddress : struct,
IUnsignedNumber<TLinkAddress>,
IComparisonOperators<TLinkAddress, TLinkAddress, bool>,
IShiftOperators<TLinkAddress, int, TLinkAddress>,
IBitwiseOperators<TLinkAddress, TLinkAddress, TLinkAddress>,
IMinMaxValue<TLinkAddress>
{
private readonly bool _tracingEnabled;
public readonly NamedLinks<TLinkAddress> NamedLinks;
public readonly string NamedLinksDatabaseFileName;

public static ILinks<TLinkAddress> MakeLinks(string databaseFilename)
{
var links = new UnitedMemoryLinks<TLinkAddress>(databaseFilename);
return links.DecorateWithAutomaticUniquenessAndUsagesResolution();
}

public static string MakeNamesDatabaseFilename(string databaseFilename)
{
var filenameWithoutExtension = Path.GetFileNameWithoutExtension(databaseFilename);
var directory = Path.GetDirectoryName(databaseFilename);
var namesDatabaseFilename = Path.Combine(directory ?? string.Empty, $"{filenameWithoutExtension}.names.links");
return namesDatabaseFilename;
}

public NamedTypesDecorator(ILinks<TLinkAddress> links, string namesDatabaseFilename, bool tracingEnabled = false) : base(links)
{
_tracingEnabled = tracingEnabled;
if (_tracingEnabled) Console.WriteLine($"[Trace] Constructing NamedTypesDecorator with names DB: {namesDatabaseFilename}");
var namesConstants = new LinksConstants<TLinkAddress>(enableExternalReferencesSupport: true);
var namesMemory = new FileMappedResizableDirectMemory(namesDatabaseFilename, UnitedMemoryLinks<TLinkAddress>.DefaultLinksSizeStep);
var namesLinks = new UnitedMemoryLinks<TLinkAddress>(namesMemory, UnitedMemoryLinks<TLinkAddress>.DefaultLinksSizeStep, namesConstants, IndexTreeType.Default);
var decoratedNamesLinks = namesLinks.DecorateWithAutomaticUniquenessAndUsagesResolution();
NamedLinks = new UnicodeStringStorage<TLinkAddress>(decoratedNamesLinks).NamedLinks;
NamedLinksDatabaseFileName = namesDatabaseFilename;
}

public NamedTypesDecorator(string databaseFilename, bool tracingEnabled = false)
: this(MakeLinks(databaseFilename), MakeNamesDatabaseFilename(databaseFilename), tracingEnabled)
{
}

public string? GetName(TLinkAddress link)
{
if (_tracingEnabled) Console.WriteLine($"[Trace] GetName called for link: {link}");
var result = NamedLinks.GetNameByExternalReference(link);
if (_tracingEnabled) Console.WriteLine($"[Trace] GetName result: {result}");
return result;
}

public TLinkAddress SetName(TLinkAddress link, string name)
{
if (_tracingEnabled) Console.WriteLine($"[Trace] SetName called for link: {link} with name: '{name}'");
RemoveName(link);
var result = NamedLinks.SetNameForExternalReference(link, name);
if (_tracingEnabled) Console.WriteLine($"[Trace] SetName result: {result}");
return result;
}

public TLinkAddress GetByName(string name)
{
if (_tracingEnabled) Console.WriteLine($"[Trace] GetByName called for name: '{name}'");
var result = NamedLinks.GetExternalReferenceByName(name);
if (_tracingEnabled) Console.WriteLine($"[Trace] GetByName result: {result}");
return result;
}

public void RemoveName(TLinkAddress link)
{
if (_tracingEnabled) Console.WriteLine($"[Trace] RemoveName called for link: {link}");
NamedLinks.RemoveNameByExternalReference(link);
if (_tracingEnabled) Console.WriteLine($"[Trace] RemoveName completed for link: {link}");
}

public override TLinkAddress Update(IList<TLinkAddress>? restriction, IList<TLinkAddress>? substitution, WriteHandler<TLinkAddress>? handler)
{
if (_tracingEnabled)
{
var req = restriction == null ? "null" : string.Join(",", restriction);
Console.WriteLine($"[Trace] Update called with restriction: [{req}]");
}
var @continue = _links.Constants.Continue;
var result = _links.Update(restriction, substitution, (IList<TLinkAddress>? before, IList<TLinkAddress>? after) =>
{
if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: handlerWrapper invoked - before={(before == null ? "null" : string.Join(",", before))}, after={(after == null ? "null" : string.Join(",", after))}");
if (before != null && after == null)
{
var deletedLinkIndex = _links.GetIndex(link: before);
RemoveName(deletedLinkIndex);
}
return handler == null ? @continue : handler(before, after);
});
if (_tracingEnabled) Console.WriteLine($"[Trace] Update result: {result}");
return result;
}

public override TLinkAddress Delete(IList<TLinkAddress>? restriction, WriteHandler<TLinkAddress>? handler)
{
if (_tracingEnabled)
{
var formattedRestriction = restriction == null ? "null" : string.Join(",", restriction);
Console.WriteLine($"[Trace] Delete called with restriction: [{formattedRestriction}]");
}
if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: this._links is of type: {_links.GetType()}");
var @continue = _links.Constants.Continue;
if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: Calling underlying _links.Delete");
TLinkAddress result = _links.Delete(restriction, (IList<TLinkAddress>? before, IList<TLinkAddress>? after) =>
{
if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: handlerWrapper invoked - before={(before == null ? "null" : string.Join(",", before))}, after={(after == null ? "null" : string.Join(",", after))}");
if (before != null && after == null)
{
var deletedLinkIndex = _links.GetIndex(link: before);
RemoveName(deletedLinkIndex);
}
return handler == null ? @continue : handler(before, after);
});
if (_tracingEnabled) Console.WriteLine($"[Trace] Debug: Delete result: {result}");
return result;
}
}
}