Skip to content

Commit

Permalink
(+) #1141 Add support for object instances in ObjectIdGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfdezsauco committed Jan 26, 2018
1 parent 766ff2b commit 7f7cc0a
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 19 deletions.
3 changes: 2 additions & 1 deletion doc/history.txt
Expand Up @@ -39,10 +39,11 @@ Classes / members marked as obsolete:
Added/fixed:
============
(+) #333 CTL-271 Support generic lists in ServiceLocator
(+) #1141 Add support for object instances in ObjectIdGenerator
(*) #1127 Use TypeCache instead AppDomain.CurrentDomain in ServiceLocatorDependencyRegistrationManager implementation
(*) #1143 UserControlLogic should dispose view models if they implement IDispose
(*) #1144 Improve [Save|Cancel]AndCloseViewModelAsync behavior when calling on an already closing view model
(*) #1146 Reuse identifiers in UniqueIdentifierHelper
(*) #1146 Reuse identifiers in ObjectIdGenerator
(*) #1150 DataWindow uses Dispatcher to subscribe to Closing event in order to allow derived types to subscribe first
(x) #1142 LogicBase calls CloseViewModel twice on view model that is already closing

Expand Down
Expand Up @@ -15,6 +15,7 @@ namespace Catel.Services
/// The entity type.
/// </typeparam>
public class GuidObjectIdGenerator<TObjectType> : ObjectIdGenerator<TObjectType, Guid>
where TObjectType : class
{
/// <inheritdoc />
protected override Guid GenerateUniqueIdentifier()
Expand Down
Expand Up @@ -6,6 +6,8 @@

namespace Catel.Services
{
using System;

/// <summary>
/// The object id generator service.
/// </summary>
Expand All @@ -15,6 +17,7 @@ public interface IObjectIdGenerator<TUniqueIdentifier>
/// <summary>
/// Gets the unique identifier for the specified type.
/// </summary>
/// <param name="reuse">Indicates whether the id will be returned from released id pool</param>
/// <returns>A new unique identifier but if <paramref name="reuse"/> is <c>true</c> will return a released identifier.</returns>
TUniqueIdentifier GetUniqueIdentifier(bool reuse = false);

Expand All @@ -29,7 +32,20 @@ public interface IObjectIdGenerator<TUniqueIdentifier>
/// </summary>
/// <typeparam name="TObjectType">The object type</typeparam>
/// <typeparam name="TUniqueIdentifier">The unique identifier type</typeparam>
public interface IObjectIdGenerator<TObjectType, TUniqueIdentifier> : IObjectIdGenerator<TUniqueIdentifier>
public interface IObjectIdGenerator<in TObjectType, TUniqueIdentifier> : IObjectIdGenerator<TUniqueIdentifier>
where TObjectType : class
{
/// <summary>
/// Gets the unique identifier for the specified instance.
/// </summary>
/// <param name="instance">The instance</param>
/// <param name="reuse">Indicates whether the id will be returned from released id pool</param>
/// <returns></returns>
TUniqueIdentifier GetUniqueIdentifierForInstance(TObjectType instance, bool reuse = false);

/// <summary>
/// Gets and sets the instance check interval.
/// </summary>
TimeSpan? InstanceCheckInterval { get; set; }
}
}
Expand Up @@ -14,6 +14,7 @@ namespace Catel.Services
/// <typeparam name="TObjectType">The object type.</typeparam>
/// <typeparam name="TUniqueIdentifier">The unique identifier type.</typeparam>
public abstract class NumericBasedObjectIdGenerator<TObjectType, TUniqueIdentifier> : ObjectIdGenerator<TObjectType, TUniqueIdentifier>
where TObjectType : class
{
static NumericBasedObjectIdGenerator()
{
Expand Down
Expand Up @@ -14,6 +14,7 @@ namespace Catel.Services
/// The object type.
/// </typeparam>
public sealed class IntegerObjectIdGenerator<TObjectType> : NumericBasedObjectIdGenerator<TObjectType, int>
where TObjectType : class
{
/// <inheritdoc />
protected override int GenerateUniqueIdentifier()
Expand All @@ -29,6 +30,7 @@ protected override int GenerateUniqueIdentifier()
/// The object type.
/// </typeparam>
public sealed class LongObjectIdGenerator<TObjectType> : NumericBasedObjectIdGenerator<TObjectType, long>
where TObjectType : class
{
/// <inheritdoc />
protected override long GenerateUniqueIdentifier()
Expand All @@ -44,6 +46,7 @@ protected override long GenerateUniqueIdentifier()
/// The object type.
/// </typeparam>
public sealed class ULongObjectIdGenerator<TObjectType> : NumericBasedObjectIdGenerator<TObjectType, ulong>
where TObjectType : class
{
/// <inheritdoc />
protected override ulong GenerateUniqueIdentifier()
Expand Down
85 changes: 85 additions & 0 deletions src/Catel.Core/Catel.Core.Shared/Services/ObjectIdGenerator.cs
Expand Up @@ -7,19 +7,32 @@

namespace Catel.Services
{
using System;
using System.Collections.Generic;
using System.Linq;

using Catel.Threading;

/// <summary>
/// The ObjectIdGenerator class.
/// </summary>
/// <typeparam name="TObjectType">The object type</typeparam>
/// <typeparam name="TUniqueIdentifier">The unique identifier type</typeparam>
public abstract class ObjectIdGenerator<TObjectType, TUniqueIdentifier> : IObjectIdGenerator<TObjectType, TUniqueIdentifier>
where TObjectType : class
{
private static Queue<TUniqueIdentifier> _releasedUniqueIdentifiers;

private static readonly object _syncObj = new object();

private static SortedDictionary<TUniqueIdentifier, WeakReference<TObjectType>> _allocatedUniqueIdentifierPerInstances = new SortedDictionary<TUniqueIdentifier, WeakReference<TObjectType>>();

private static readonly TimeSpan DefaultInterval = TimeSpan.FromMinutes(1);

private static TimeSpan? _interval;

private static Timer _timer;

/// <inheritdoc />
public TUniqueIdentifier GetUniqueIdentifier(bool reuse = false)
{
Expand Down Expand Up @@ -51,6 +64,78 @@ public void ReleaseIdentifier(TUniqueIdentifier identifier)
}
}

/// <inheritdoc />
public TUniqueIdentifier GetUniqueIdentifierForInstance(TObjectType instance, bool reuse = false)
{
Argument.IsNotNull("instance", instance);

var uniqueIdentifier = GetUniqueIdentifier(reuse);

lock (_syncObj)
{
if (_allocatedUniqueIdentifierPerInstances == null)
{
_allocatedUniqueIdentifierPerInstances = new SortedDictionary<TUniqueIdentifier, WeakReference<TObjectType>>();
}

_allocatedUniqueIdentifierPerInstances.Add(uniqueIdentifier, new WeakReference<TObjectType>(instance));

var interval = _interval ?? DefaultInterval;
if (_timer == null)
{
_timer = new Timer(OnTimerElapsed, null, interval, interval);
}
else
{
_timer.Change(interval, interval);
}
}

return uniqueIdentifier;
}

/// <inheritdoc />
public TimeSpan? InstanceCheckInterval
{
get
{
lock (_syncObj)
{
return _interval;
}
}
set
{
lock (_syncObj)
{
_interval = value;
}
}
}

private void OnTimerElapsed(object state)
{
lock (_syncObj)
{
var uniqueIdentifiers = _allocatedUniqueIdentifierPerInstances.Where(pair => !pair.Value.TryGetTarget(out var _)).Select(pair => pair.Key).ToList();
if (uniqueIdentifiers.Any() && _releasedUniqueIdentifiers == null)
{
_releasedUniqueIdentifiers = new Queue<TUniqueIdentifier>();
}

foreach (var uniqueIdentifier in uniqueIdentifiers)
{
_allocatedUniqueIdentifierPerInstances.Remove(uniqueIdentifier);
_releasedUniqueIdentifiers.Enqueue(uniqueIdentifier);
}

if (_timer != null && _allocatedUniqueIdentifierPerInstances.Count == 0)
{
_timer.Change(Timeout.Infinite, Timeout.Infinite);
}
}
}

/// <summary>
/// Generates the unique identifier.
/// </summary>
Expand Down
Expand Up @@ -4,9 +4,11 @@
// </copyright>
// --------------------------------------------------------------------------------------------------------------------


namespace Catel.Test.Services
{
using System;
using System.Threading;

using Catel.Services;

Expand All @@ -17,6 +19,35 @@ public class GuidObjectIdGeneratorFacts
[TestFixture]
public class The_GetUniqueIdentifier_Method
{
[Test]
public void Returns_New_UniqueIdentifier_Even_If_Are_Generated_By_Different_Instances()
{
IObjectIdGenerator<Guid> generator1 = new GuidObjectIdGenerator<PersonViewModel1>();
IObjectIdGenerator<Guid> generator2 = new GuidObjectIdGenerator<PersonViewModel1>();

Assert.AreNotEqual(generator1.GetUniqueIdentifier(), generator2.GetUniqueIdentifier());
}

[Test]
public void Returns_A_Released_Identifier_If_Requested()
{
IObjectIdGenerator<Guid> generator = new GuidObjectIdGenerator<PersonViewModel2>();
var uniqueIdentifier = generator.GetUniqueIdentifier();

generator.ReleaseIdentifier(uniqueIdentifier);

Assert.AreEqual(uniqueIdentifier, generator.GetUniqueIdentifier(true));
}

[Test]
public void Returns_Unique_Identifier_For_DiferentTypes()
{
IObjectIdGenerator<Guid> generator1 = new GuidObjectIdGenerator<PersonViewModel3>();
IObjectIdGenerator<Guid> generator2 = new GuidObjectIdGenerator<PersonViewModel4>();

Assert.AreNotEqual(generator1.GetUniqueIdentifier(), generator2.GetUniqueIdentifier());
}

public class PersonViewModel2
{
}
Expand All @@ -32,33 +63,52 @@ public class PersonViewModel1
public class PersonViewModel3
{
}
}

[TestFixture]
public class GetUniqueIdentifierForInstance_Method
{
[Test]
public void Returns_New_UniqueIdentifier_Even_If_Are_Generated_By_Different_Instances()
public void Returns_A_Released_Identifier_If_The_Instance_Is_Released_And_Reuse_Is_True()
{
IObjectIdGenerator<Guid> generator1 = new GuidObjectIdGenerator<PersonViewModel1>();
IObjectIdGenerator<Guid> generator2 = new GuidObjectIdGenerator<PersonViewModel1>();
GuidObjectIdGenerator<PersonViewModel1> generator = new GuidObjectIdGenerator<PersonViewModel1> { InstanceCheckInterval = TimeSpan.FromSeconds(5) };
var uniqueIdentifierForInstance = generator.GetUniqueIdentifierForInstance(new PersonViewModel1());

Assert.AreNotEqual(generator1.GetUniqueIdentifier(), generator2.GetUniqueIdentifier());
GC.Collect();

Thread.Sleep(TimeSpan.FromSeconds(20));

Assert.AreEqual(uniqueIdentifierForInstance, generator.GetUniqueIdentifierForInstance(new PersonViewModel1(), true));
}

[Test]
public void Returns_A_Released_Identifier_If_Requested()
public void Returns_A_Unique_Identifier_Even_When_An_Instance_Is_Released_But_Reuse_Is_False()
{
IObjectIdGenerator<Guid> generator = new GuidObjectIdGenerator<PersonViewModel2>();
var uniqueIdentifier = generator.GetUniqueIdentifier();
GuidObjectIdGenerator<PersonViewModel2> generator = new GuidObjectIdGenerator<PersonViewModel2> { InstanceCheckInterval = TimeSpan.FromSeconds(5) };
var uniqueIdentifierForInstance = generator.GetUniqueIdentifierForInstance(new PersonViewModel2());

generator.ReleaseIdentifier(uniqueIdentifier);
GC.Collect();

Assert.AreEqual(uniqueIdentifier, generator.GetUniqueIdentifier(true));
Thread.Sleep(TimeSpan.FromSeconds(20));

Assert.AreNotEqual(uniqueIdentifierForInstance, generator.GetUniqueIdentifierForInstance(new PersonViewModel2()));
}

[Test]
public void Returns_Unique_Identifier_For_DiferentTypes()
public void Returns_A_Unique_Identifier_For_Diferent_Instances()
{
IObjectIdGenerator<Guid> generator1 = new GuidObjectIdGenerator<PersonViewModel3>();
IObjectIdGenerator<Guid> generator2 = new GuidObjectIdGenerator<PersonViewModel4>();
GuidObjectIdGenerator<PersonViewModel3> generator = new GuidObjectIdGenerator<PersonViewModel3> { InstanceCheckInterval = TimeSpan.FromSeconds(5) };
Assert.AreNotEqual(generator.GetUniqueIdentifierForInstance(new PersonViewModel3()), generator.GetUniqueIdentifierForInstance(new PersonViewModel3()));
}

Assert.AreNotEqual(generator1.GetUniqueIdentifier(), generator2.GetUniqueIdentifier());
public class PersonViewModel1
{
}
public class PersonViewModel2
{
}
public class PersonViewModel3
{
}
}
}
Expand Down

0 comments on commit 7f7cc0a

Please sign in to comment.