Skip to content

Commit

Permalink
ObjectSpaces: added annotations for building objects (and unit tests)
Browse files Browse the repository at this point in the history
  • Loading branch information
HermanSchoenfeld committed Apr 30, 2024
1 parent fb7a918 commit 1203a17
Show file tree
Hide file tree
Showing 16 changed files with 239 additions and 48 deletions.
28 changes: 22 additions & 6 deletions src/Hydrogen/Comparers/ComparerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ public static void RegisterDefaults(ComparerFactory comparerFactory) {

}

public IEqualityComparer<string> GetEqualityComparer<T>()
=> (IEqualityComparer<string>)GetEqualityComparer(typeof(T));
public IEqualityComparer<T> GetEqualityComparer<T>()
=> (IEqualityComparer<T>)GetEqualityComparer(typeof(T));

public object GetEqualityComparer(Type type) {
// Get registered comparer
Expand Down Expand Up @@ -177,8 +177,8 @@ public object GetEqualityComparer(Type type) {
return defaultComparer;
}

public IComparer<string> GetComparer<T>()
=> (IComparer<string>)GetComparer(typeof(T));
public IComparer<T> GetComparer<T>()
=> (IComparer<T>)GetComparer(typeof(T));

public object GetComparer(Type type) {
// Get registered comparer
Expand All @@ -198,12 +198,28 @@ public object GetComparer(Type type) {
=> RegisterComparer(new TComparer());

public void RegisterComparer<T>(IComparer<T> comparer)
=> _comparers.Add(typeof(T), comparer);
=> RegisterComparer(typeof(T), comparer);

public void RegisterComparer(Type type, object comparer) {
Guard.ArgumentNotNull(type, nameof(type));
Guard.ArgumentNotNull(comparer, nameof(comparer));
Guard.Argument(comparer.GetType().IsSubtypeOfGenericType(typeof(IComparer<>), out var comparerInterfaceType), nameof(comparer), $"Not an {typeof(IComparer<>).ToStringCS()}");
Guard.Argument(comparerInterfaceType.GenericTypeArguments[0] == type, nameof(comparer), $"Not an {typeof(IComparer<>).MakeGenericType(type).ToStringCS()}");
_comparers.Add(type, comparer);
}

public void RegisterEqualityComparer<T, TEqualityComparer>() where TEqualityComparer : IEqualityComparer<T>, new()
=> RegisterEqualityComparer(new TEqualityComparer());

public void RegisterEqualityComparer<T>(IEqualityComparer<T> equalityComparer)
=> _equalityComparers.Add(typeof(T), equalityComparer);
=> RegisterEqualityComparer(typeof(T), equalityComparer);

public void RegisterEqualityComparer(Type type, object equalityComparer) {
Guard.ArgumentNotNull(type, nameof(type));
Guard.ArgumentNotNull(equalityComparer, nameof(equalityComparer));
Guard.Argument(equalityComparer.GetType().IsSubtypeOfGenericType(typeof(IEqualityComparer<>), out var comparerInterfaceType), nameof(equalityComparer), $"Not an {typeof(IEqualityComparer<>).ToStringCS()}");
Guard.Argument(comparerInterfaceType.GenericTypeArguments[0] == type, nameof(equalityComparer), $"Not an {typeof(IEqualityComparer<>).MakeGenericType(type).ToStringCS()}");
_equalityComparers.Add(type, equalityComparer);
}

}
7 changes: 7 additions & 0 deletions src/Hydrogen/ObjectSpaces/Annotations/DimensionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System;

namespace Hydrogen;

[AttributeUsage(AttributeTargets.Class)]
public class DimensionAttribute : Attribute {
}
9 changes: 9 additions & 0 deletions src/Hydrogen/ObjectSpaces/Annotations/IdentityAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace Hydrogen;

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class IdentityAttribute : Attribute {
public string IndexName { get; set; } = null;
}

6 changes: 6 additions & 0 deletions src/Hydrogen/ObjectSpaces/Annotations/IndexAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System;

namespace Hydrogen;

public class IndexAttribute : IndexAttributeBase {
}
10 changes: 10 additions & 0 deletions src/Hydrogen/ObjectSpaces/Annotations/IndexAttributeBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Hydrogen;

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public abstract class IndexAttributeBase : Attribute {
public string IndexName { get; set; } = null;

public IndexNullPolicy NullPolicy { get; set; } = IndexNullPolicy.IgnoreNull;
}
6 changes: 6 additions & 0 deletions src/Hydrogen/ObjectSpaces/Annotations/UniqueIndexAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System;

namespace Hydrogen;

public class UniqueIndexAttribute: IndexAttributeBase {
}
11 changes: 11 additions & 0 deletions src/Hydrogen/ObjectSpaces/Annotations/UseEqualityComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;

namespace Hydrogen;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class EqualityComparerAttribute(Type type) : Attribute {

public Type EqualityComparerType { get; } = type;

}
11 changes: 11 additions & 0 deletions src/Hydrogen/ObjectSpaces/Builder/IObjectSpaceDimensionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//
// This notice must not be removed when duplicating this file or its contents, in whole or in part.

using Hydrogen.Mapping;
using System;
using System.Collections.Generic;

Expand All @@ -21,6 +22,16 @@ public interface IObjectSpaceDimensionBuilder {

IObjectSpaceDimensionBuilder Merkleized();

IObjectSpaceDimensionBuilder UsingComparer(object comparer);

IObjectSpaceDimensionBuilder UsingEqualityComparer(object comparer);

IObjectSpaceDimensionBuilder WithIdentifier(Member member, string indexName = null);

IObjectSpaceDimensionBuilder WithIndexOn(Member member, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull);

IObjectSpaceDimensionBuilder WithUniqueIndexOn(Member member, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull);

IObjectSpaceDimensionBuilder OptimizeAssumingAverageItemSize(int bytes);

ObjectSpaceDefinition.DimensionDefinition BuildDefinition();
Expand Down
41 changes: 38 additions & 3 deletions src/Hydrogen/ObjectSpaces/Builder/ObjectSpaceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ public ObjectSpaceBuilder UsingEqualityComparer<TItem>(IEqualityComparer<TItem>
return this;
}

public ObjectSpaceBuilder UsingEqualityComparer(Type type, object comparer) {
Guard.Against(_usingCustomComparerFactory, ErrMsgUsingComparer);
_specifiedCustomComparer = true;
_comparerFactory.RegisterEqualityComparer(type, comparer);
return this;
}

public ObjectSpaceBuilder UsingComparer<TItem, TComparer>() where TComparer : IComparer<TItem>, new() {
Guard.Against(_usingCustomComparerFactory, ErrMsgUsingComparer);
_specifiedCustomComparer = true;
Expand All @@ -190,10 +197,38 @@ public ObjectSpaceBuilder UsingComparer<TItem>(IComparer<TItem> comparer) {
return this;
}

public ObjectSpaceDimensionBuilder<T> AddDimension<T>() {
var dimensionBuilder = new ObjectSpaceDimensionBuilder<T>(this);
public ObjectSpaceBuilder UsingComparer(Type type, object comparer) {
Guard.Against(_usingCustomComparerFactory, ErrMsgUsingComparer);
_specifiedCustomComparer = true;
_comparerFactory.RegisterComparer(type, comparer);
return this;
}

public ObjectSpaceDimensionBuilder<T> AddDimension<T>(bool ignoreAnnotations = false)
=> (ObjectSpaceDimensionBuilder<T>)AddDimension(typeof(T), ignoreAnnotations);

public IObjectSpaceDimensionBuilder AddDimension(Type type, bool ignoreAnnotations = false) {
var dimensionBuilder = (IObjectSpaceDimensionBuilder)typeof(ObjectSpaceDimensionBuilder<>).MakeGenericType(type).ActivateWithCompatibleArgs(this);
_dimensions.Add(dimensionBuilder);
return Configure<T>();

if (!ignoreAnnotations) {

if (type.TryGetCustomAttributeOfType<EqualityComparerAttribute>(false, out var equalityComparerAttribute))
dimensionBuilder.UsingEqualityComparer(equalityComparerAttribute.EqualityComparerType.ActivateWithCompatibleArgs());

foreach(var member in SerializationHelper.GetSerializableMembers(type)) {
if (member.MemberInfo.TryGetCustomAttributeOfType<IdentityAttribute>(false, out var identityAttribute))
dimensionBuilder.WithIdentifier(member, identityAttribute.IndexName);

if (member.MemberInfo.TryGetCustomAttributeOfType<IndexAttribute>(false, out var indexAttribute))
dimensionBuilder.WithIndexOn(member, indexAttribute.IndexName, indexAttribute.NullPolicy);

if (member.MemberInfo.TryGetCustomAttributeOfType<UniqueIndexAttribute>(false, out var uniqueIndexAttribute))
dimensionBuilder.WithUniqueIndexOn(member, uniqueIndexAttribute.IndexName, uniqueIndexAttribute.NullPolicy);

}
}
return dimensionBuilder;
}

public ObjectSpaceDimensionBuilder<T> Configure<T>() {
Expand Down
51 changes: 44 additions & 7 deletions src/Hydrogen/ObjectSpaces/Builder/ObjectSpaceDimensionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,15 @@ public ObjectSpaceDimensionBuilder<T> UsingComparer(IComparer<T> comparer) {
_parent.UsingComparer(comparer);
return this;
}


public ObjectSpaceDimensionBuilder<T> UsingComparer(object comparer)
=> (ObjectSpaceDimensionBuilder<T>)((IObjectSpaceDimensionBuilder)this).UsingComparer(comparer);

IObjectSpaceDimensionBuilder IObjectSpaceDimensionBuilder.UsingComparer(object comparer) {
_parent.UsingComparer(typeof(T), comparer);
return this;
}

public ObjectSpaceDimensionBuilder<T> UsingEqualityComparer<TComparer>() where TComparer : IEqualityComparer<T>, new() {
_parent.UsingEqualityComparer<T, TComparer>();
return this;
Expand All @@ -62,8 +70,23 @@ public ObjectSpaceDimensionBuilder<T> UsingEqualityComparer(IEqualityComparer<T>
return this;
}

public ObjectSpaceDimensionBuilder<T> WithIdentifier<TMember>(Expression<Func<T, TMember>> memberExpression, string indexName = null) {
var member = memberExpression.ToMember();
public ObjectSpaceDimensionBuilder<T> UsingEqualityComparer(object comparer)
=> (ObjectSpaceDimensionBuilder<T>)((IObjectSpaceDimensionBuilder)this).UsingEqualityComparer(comparer);

IObjectSpaceDimensionBuilder IObjectSpaceDimensionBuilder.UsingEqualityComparer(object comparer) {
_parent.UsingEqualityComparer(typeof(T), comparer);
return this;
}

public ObjectSpaceDimensionBuilder<T> WithIdentifier<TMember>(Expression<Func<T, TMember>> memberExpression, string indexName = null)
=> WithIdentifier(memberExpression.ToMember(), indexName);

public new ObjectSpaceDimensionBuilder<T> WithIdentifier(Member member, string indexName = null)
=> (ObjectSpaceDimensionBuilder<T>)((IObjectSpaceDimensionBuilder)this).WithIdentifier(member, indexName);

IObjectSpaceDimensionBuilder IObjectSpaceDimensionBuilder.WithIdentifier(Member member, string indexName) {
Guard.ArgumentNotNull(member, nameof(member));
Guard.Argument(member.DeclaringType == typeof(T), nameof(member), $"Not a member of {typeof(T).ToStringCS()}");
var index = new ObjectSpaceDefinition.IndexDefinition {
Type = ObjectSpaceDefinition.IndexType.Identifier,
Name = indexName ?? member.Name,
Expand All @@ -73,9 +96,16 @@ public ObjectSpaceDimensionBuilder<T> WithIdentifier<TMember>(Expression<Func<T,
_indexes.Add(index);
return this;
}

public ObjectSpaceDimensionBuilder<T> WithIndexOn<TMember>(Expression<Func<T, TMember>> memberExpression, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull)
=> WithIndexOn(memberExpression.ToMember(), indexName, nullPolicy);

public ObjectSpaceDimensionBuilder<T> WithIndexOn(Member member, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull)
=> (ObjectSpaceDimensionBuilder<T>)((IObjectSpaceDimensionBuilder)this).WithIndexOn(member, indexName, nullPolicy);

public ObjectSpaceDimensionBuilder<T> WithIndexOn<TMember>(Expression<Func<T, TMember>> memberExpression, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull) {
var member = memberExpression.ToMember();
IObjectSpaceDimensionBuilder IObjectSpaceDimensionBuilder.WithIndexOn(Member member, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull) {
Guard.ArgumentNotNull(member, nameof(member));
Guard.Argument(member.DeclaringType == typeof(T), nameof(member), $"Not a member of {typeof(T).ToStringCS()}");
var index = new ObjectSpaceDefinition.IndexDefinition {
Type = ObjectSpaceDefinition.IndexType.Index,
Name = indexName ?? member.Name,
Expand All @@ -86,8 +116,15 @@ public ObjectSpaceDimensionBuilder<T> WithIndexOn<TMember>(Expression<Func<T, TM
return this;
}

public ObjectSpaceDimensionBuilder<T> WithUniqueIndexOn<TMember>(Expression<Func<T, TMember>> memberExpression, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull) {
var member = memberExpression.ToMember();
public ObjectSpaceDimensionBuilder<T> WithUniqueIndexOn<TMember>(Expression<Func<T, TMember>> memberExpression, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull)
=> WithUniqueIndexOn(memberExpression.ToMember(), indexName, nullPolicy);

public ObjectSpaceDimensionBuilder<T> WithUniqueIndexOn(Member member, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull)
=> (ObjectSpaceDimensionBuilder<T>)((IObjectSpaceDimensionBuilder)this).WithUniqueIndexOn(member, indexName, nullPolicy);

IObjectSpaceDimensionBuilder IObjectSpaceDimensionBuilder.WithUniqueIndexOn(Member member, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull) {
Guard.ArgumentNotNull(member, nameof(member));
Guard.Argument(member.DeclaringType == typeof(T), nameof(member), $"Not a member of {typeof(T).ToStringCS()}");
var index = new ObjectSpaceDefinition.IndexDefinition {
Type = ObjectSpaceDefinition.IndexType.UniqueKey,
Name = indexName ?? member.Name,
Expand Down
17 changes: 0 additions & 17 deletions src/Hydrogen/ObjectSpaces/UniquePropertyAttribute.cs

This file was deleted.

47 changes: 41 additions & 6 deletions tests/Hydrogen.Tests/ObjectSpaces/ObjectSpacesBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public void CannotRegisterSerializerWhenUsingCustomSerializerFactory_1() {
var objectSpaceBuilder = new ObjectSpaceBuilder();
objectSpaceBuilder
.UsingSerializerFactory(customSerializerFactory)
.AddDimension<Account>();
.AddDimension<Account>(true);

var serializer = SerializerBuilder.For<Account>().Build();
Assert.That(() => objectSpaceBuilder.Configure<Account>().UsingSerializer(serializer), Throws.InvalidOperationException);
Expand All @@ -39,7 +39,7 @@ public void CannotRegisterSerializerWhenUsingCustomSerializerFactory_2() {
var objectSpaceBuilder = new ObjectSpaceBuilder();
objectSpaceBuilder
.UsingSerializerFactory(customSerializerFactory)
.AddDimension<Account>();
.AddDimension<Account>(true);

Assert.That(() => objectSpaceBuilder.Configure<Account>().UsingSerializer<DummyAccountSerializer>(), Throws.InvalidOperationException);
}
Expand All @@ -64,7 +64,7 @@ public void CannotRegisterComparerWhenUsingCustomComparerFactory_1() {
var objectSpaceBuilder = new ObjectSpaceBuilder();
objectSpaceBuilder
.UsingComparerFactory(customComparerFactory)
.AddDimension<Account>();
.AddDimension<Account>(true);

var comparer = ComparerBuilder.For<Account>();
Assert.That(() => objectSpaceBuilder.Configure<Account>().UsingComparer(comparer), Throws.InvalidOperationException);
Expand All @@ -77,7 +77,7 @@ public void CannotRegisterComparerWhenUsingCustomComparerFactory_2() {
var objectSpaceBuilder = new ObjectSpaceBuilder();
objectSpaceBuilder
.UsingComparerFactory(customComparerFactory)
.AddDimension<Account>();
.AddDimension<Account>(true);

Assert.That(() => objectSpaceBuilder.Configure<Account>().UsingComparer<DummyAccountComparer>(), Throws.InvalidOperationException);
}
Expand All @@ -89,7 +89,7 @@ public void CannotRegisterEqualityComparerWhenUsingCustomComparerFactory_1() {
var objectSpaceBuilder = new ObjectSpaceBuilder();
objectSpaceBuilder
.UsingComparerFactory(customComparerFactory)
.AddDimension<Account>();
.AddDimension<Account>(true);

var comparer = EqualityComparerBuilder.For<Account>();
Assert.That(() => objectSpaceBuilder.Configure<Account>().UsingEqualityComparer(comparer), Throws.InvalidOperationException);
Expand All @@ -102,11 +102,46 @@ public void CannotRegisterEqualityComparerWhenUsingCustomComparerFactory_2() {
var objectSpaceBuilder = new ObjectSpaceBuilder();
objectSpaceBuilder
.UsingComparerFactory(customComparerFactory)
.AddDimension<Account>();
.AddDimension<Account>(true);

Assert.That(() => objectSpaceBuilder.Configure<Account>().UsingEqualityComparer<DummyAccountEqualityComparer>(), Throws.InvalidOperationException);
}

[Test]
public void Annotations() {
var customComparerFactory = new ComparerFactory(ComparerFactory.Default);

var objectSpaceBuilder = new ObjectSpaceBuilder();
objectSpaceBuilder
.UseMemoryStream()
.AddDimension<Account>().Done()
.AddDimension<Identity>().Done();


var definition = objectSpaceBuilder.BuildDefinition();

Assert.That(definition.Dimensions[0].Indexes.Count, Is.EqualTo(3));

// Account
Assert.That(definition.Dimensions[0].Indexes[1].Type, Is.EqualTo(ObjectSpaceDefinition.IndexType.Identifier));
Assert.That(definition.Dimensions[0].Indexes[1].Member, Is.EqualTo(Tools.Mapping.GetMember<Account>(x => x.Name)));

Assert.That(definition.Dimensions[0].Indexes[2].Type, Is.EqualTo(ObjectSpaceDefinition.IndexType.UniqueKey));
Assert.That(definition.Dimensions[0].Indexes[2].Member, Is.EqualTo(Tools.Mapping.GetMember<Account>(x => x.UniqueNumber)));

// Identity
Assert.That(definition.Dimensions[1].Indexes[1].Type, Is.EqualTo(ObjectSpaceDefinition.IndexType.UniqueKey));
Assert.That(definition.Dimensions[1].Indexes[1].Member, Is.EqualTo(Tools.Mapping.GetMember<Identity>(x => x.Key)));

Assert.That(definition.Dimensions[1].Indexes[2].Type, Is.EqualTo(ObjectSpaceDefinition.IndexType.Index));
Assert.That(definition.Dimensions[1].Indexes[2].Member, Is.EqualTo(Tools.Mapping.GetMember<Identity>(x => x.Group)));

// Test comparer
var objectSpace = objectSpaceBuilder.Build();
Assert.That( () => objectSpace.Comparers.GetEqualityComparer<Account>(), Is.InstanceOf<AccountEqualityComparer>());

}

internal class DummyAccountSerializer : ItemSerializerBase<Account> {

public override long CalculateSize(SerializationContext context, Account item) {
Expand Down
Loading

0 comments on commit 1203a17

Please sign in to comment.