Skip to content

Commit

Permalink
Fix InternMap and add test cases (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
maroontress-tomohisa authored Jun 19, 2022
1 parent 22aa1d8 commit 3c05d1f
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 15 deletions.
4 changes: 4 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ jobs:
curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
apt-get -y install git-lfs
git lfs install
- run:
name: Install SSH client
command: |
apt-get -y install ssh-client
- checkout
- run: pwd
- run: dotnet restore
Expand Down
49 changes: 49 additions & 0 deletions Oxbind.Test/Util/InternMapTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace Maroontress.Util.Test
{
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public sealed class InternMapTest
{
[TestMethod]
public void Intern_Func_1()
{
var map = new InternMap<int, string>();
var key = 12;
var v1 = "12";
var v2 = string.Join("", new[] { "1", "2" });
Assert.AreNotSame(v1, v2);
var c1 = map.Intern(key, () => v1);
Assert.AreSame(v1, c1);
var c2 = map.Intern(key, () => v2);
Assert.AreSame(c1, c2);
}

[TestMethod]
public void Intern_Func_2()
{
var map = new InternMap<int, string>();
var key = 12;
var c1 = map.Intern(key, k => k.ToString());
var c2 = map.Intern(key, k => k.ToString());
Assert.AreSame(c1, c2);
}

[TestMethod]
public void Intern_Func_1_Null()
{
var map = new InternMap<int, string>();
Assert.ThrowsException<ArgumentNullException>(
() => _ = map.Intern(12, (Func<string>)null!));
}

[TestMethod]
public void Intern_Func_2_Null()
{
var map = new InternMap<int, string>();
Assert.ThrowsException<ArgumentNullException>(
() => _ = map.Intern(12, (Func<int, string>)null!));
}
}
}
72 changes: 57 additions & 15 deletions Oxbind/Maroontress/Util/InternMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Maroontress.Util
using System.Collections.Concurrent;

/// <summary>
/// Provides canonical value objects corresponding to the key.
/// Provides the canonical value object corresponding to the key.
/// </summary>
/// <typeparam name="K">
/// The type of the key.
Expand All @@ -13,18 +13,58 @@ namespace Maroontress.Util
/// The type of the value.
/// </typeparam>
public sealed class InternMap<K, V>
where K : class
where K : notnull
where V : class
{
private const int DefaultCapacity = 31;

/// <summary>
/// Initializes a new instance of the <see cref="InternMap{K, V}"/>
/// class.
/// </summary>
public InternMap()
: this(DefaultCapacity, DefaultConcurrencyLevel)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="InternMap{K, V}"/>
/// class.
/// </summary>
/// <param name="initialCapacity">
/// The initial capacity.
/// </param>
public InternMap(int initialCapacity)
: this(initialCapacity, DefaultConcurrencyLevel)
{
}

/// <summary>
/// The map from a key to the value.
/// Initializes a new instance of the <see cref="InternMap{K, V}"/>
/// class.
/// </summary>
private readonly ConcurrentDictionary<K, V> map
= new ConcurrentDictionary<K, V>();
/// <param name="initialCapacity">
/// The initial capacity.
/// </param>
/// <param name="concurrencyLevel">
/// The concurrency level.
/// </param>
public InternMap(int initialCapacity, int concurrencyLevel)
{
Map = new(concurrencyLevel, initialCapacity);
}

private static int DefaultConcurrencyLevel { get; }
= Environment.ProcessorCount;

/// <summary>
/// Returns the canonical value object corresponding to the specified
/// key object. If the canonical value object does not exist in the
/// Gets the map from a key to the value.
/// </summary>
private ConcurrentDictionary<K, V> Map { get; }

/// <summary>
/// Gets the canonical value object corresponding to the specified key
/// object. If the canonical value object does not exist in the
/// internal object pool, creates a new value object with the specified
/// function.
/// </summary>
Expand All @@ -45,12 +85,16 @@ private readonly ConcurrentDictionary<K, V> map
/// </returns>
public V Intern(K key, Func<K, V> newValue)
{
return Intern(key, () => newValue(key));
if (newValue is null)
{
throw new ArgumentNullException(nameof(newValue));
}
return Map.GetOrAdd(key, newValue);
}

/// <summary>
/// Returns the canonical value object corresponding to the specified
/// key object. If the canonical value object does not exist in the
/// Gets the canonical value object corresponding to the specified key
/// object. If the canonical value object does not exist in the
/// internal object pool, creates a new value object with the specified
/// supplier.
/// </summary>
Expand All @@ -71,13 +115,11 @@ public V Intern(K key, Func<K, V> newValue)
/// </returns>
public V Intern(K key, Func<V> supplier)
{
if (map.TryGetValue(key, out var value))
if (supplier is null)
{
return value;
throw new ArgumentNullException(nameof(supplier));
}
value = supplier();
var canonical = map.GetOrAdd(key, value);
return (canonical == value) ? value : canonical;
return Map.GetOrAdd(key, k => supplier());
}
}
}

0 comments on commit 3c05d1f

Please sign in to comment.