// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.ClearScript.Util;
namespace Microsoft.ClearScript
{
internal static class CanonicalRefTable
{
private static readonly Dictionary table = new Dictionary();
public static object GetCanonicalRef(object obj)
{
if (obj is ValueType)
{
var map = GetMap(obj);
if (map != null)
{
obj = map.GetRef(obj);
}
}
return obj;
}
private static ICanonicalRefMap GetMap(object obj)
{
var type = obj.GetType();
lock (table)
{
if (!table.TryGetValue(type, out var map))
{
if (type.IsEnum ||
type.IsNumeric() ||
type == typeof(DateTime) ||
type == typeof(DateTimeOffset) ||
type == typeof(TimeSpan) ||
type.GetCustomAttributes(typeof(ImmutableValueAttribute), false).Any())
{
map = (ICanonicalRefMap)typeof(CanonicalRefMap).MakeGenericType(type).CreateInstance();
}
table.Add(type, map);
}
return map;
}
}
#region Nested type: ICanonicalRefMap
private interface ICanonicalRefMap
{
object GetRef(object obj);
}
#endregion
#region Nested type: CanonicalRefMapBase
private abstract class CanonicalRefMapBase : ICanonicalRefMap
{
protected const int CompactionThreshold = 256 * 1024;
protected static readonly TimeSpan CompactionInterval = TimeSpan.FromMinutes(2);
#region ICanonicalRefMap implementation (abstract)
public abstract object GetRef(object obj);
#endregion
}
#endregion
#region Nested type: CanonicalRefMap
private sealed class CanonicalRefMap : CanonicalRefMapBase
{
private readonly Dictionary map = new Dictionary();
private DateTime lastCompactionTime = DateTime.MinValue;
private object GetRefInternal(object obj)
{
var value = (T)obj;
object result;
if (map.TryGetValue(value, out var weakRef))
{
result = weakRef.Target;
if (result == null)
{
result = obj;
weakRef.Target = result;
}
}
else
{
result = obj;
map.Add(value, new WeakReference(result));
}
return result;
}
private void CompactIfNecessary()
{
if (map.Count >= CompactionThreshold)
{
var now = DateTime.UtcNow;
if ((lastCompactionTime + CompactionInterval) !pair.Value.IsAlive).ToList().ForEach(pair => map.Remove(pair.Key));
lastCompactionTime = now;
}
}
}
#region CanonicalRefMapBase overrides
public override object GetRef(object obj)
{
lock (map)
{
var result = GetRefInternal(obj);
CompactIfNecessary();
return result;
}
}
#endregion
}
#endregion
}
}