Skip to content
Merged
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
5 changes: 5 additions & 0 deletions csharp/.changeset/add-named-types-decorator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'Foundation.Data.Doublets.Cli': minor
---

Added a universal `NamedTypesDecorator` that implements both links operations and named type lookups, with automatic cleanup and uniqueness checks for external-reference names.
235 changes: 235 additions & 0 deletions csharp/Foundation.Data.Doublets.Cli.Tests/NamedTypesDecoratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
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_ReassigningExistingNameMovesNameToNewLink()
{
RunTestWithLinks(links =>
{
var decorator = new NamedTypesDecorator<uint>(links, _tempNamesDbPath);

var firstLink = decorator.GetOrCreate(10u, 11u);
var secondLink = decorator.GetOrCreate(20u, 21u);

decorator.SetName(firstLink, "SharedName");
decorator.SetName(secondLink, "SharedName");

Assert.Null(decorator.GetName(firstLink));
Assert.Equal("SharedName", decorator.GetName(secondLink));
Assert.Equal(secondLink, decorator.GetByName("SharedName"));
});
}

[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_CanConstructFromDatabaseFilename()
{
var tempDbFile = Path.GetTempFileName();
var expectedNamesDb = NamedTypesDecorator<uint>.MakeNamesDatabaseFilename(tempDbFile);

try
{
var decorator = new NamedTypesDecorator<uint>(tempDbFile);
var link = decorator.GetOrCreate(1u, 2u);

decorator.SetName(link, "FromFile");

Assert.Equal(expectedNamesDb, decorator.NamedLinksDatabaseFileName);
Assert.Equal(link, decorator.GetByName("FromFile"));
}
finally
{
if (File.Exists(tempDbFile)) File.Delete(tempDbFile);
if (File.Exists(expectedNamesDb)) File.Delete(expectedNamesDb);
}
}

[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 csharp/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);
}
}
27 changes: 4 additions & 23 deletions csharp/Foundation.Data.Doublets.Cli/NamedLinks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,12 @@ public void RemoveName(TLinkAddress link)
var nameCandidate = _links.GetTarget(nameCandidatePair);
if (_links.GetSource(nameCandidate).Equals(_nameType))
{
// Remove the name link
_links.Delete(nameCandidatePair);
// Remove the nameType->nameSequence link if not used elsewhere
var nameSequence = _links.GetTarget(nameCandidate);
var nameTypeToNameSequenceLink = nameCandidate;
// Check if this nameType->nameSequence is used elsewhere
var queryNameType = new Link<TLinkAddress>(any, _nameType, nameSequence);
var linksToNameSequence = _links.All(queryNameType).ToList();
if (linksToNameSequence.Count == 1) // only this one exists

var nameStillUsed = _links.All(new Link<TLinkAddress>(any, any, nameCandidate)).Any();
if (!nameStillUsed)
{
_links.Delete(nameTypeToNameSequenceLink);
_links.Delete(nameCandidate);
}
}
}
Expand All @@ -122,20 +117,6 @@ public void RemoveName(TLinkAddress link)
public void RemoveNameByExternalReference(TLinkAddress externalReference)
{
var reference = new Hybrid<TLinkAddress>(externalReference, isExternal: true);
// Get the name for this external reference
var name = GetName(reference);
if (name != null)
{
// Remove the mapping from name to external reference
var nameSequence = _createString(name);
var nameTypeToNameSequenceLink = _links.SearchOrDefault(_nameType, nameSequence);
if (!nameTypeToNameSequenceLink.Equals(_links.Constants.Null))
{
// Remove the nameType->nameSequence link if it exists
_links.Delete(nameTypeToNameSequenceLink);
}
}
// Remove the name link (externalRef->nameType->nameSequence)
RemoveName(reference);
}
}
Expand Down
Loading
Loading