1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
//---------------------------------------------------------------------
// <copyright file="AliasGenerator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
namespace System.Data.Common.Utils
{
/// <summary>
/// Generates monotonically increasing names of the form PrefixCounter, where Prefix is an optional prefix string and Counter is the string representation of a monotonically increasing int value that wraps to zero at int.MaxValue
/// </summary>
internal sealed class AliasGenerator
{
// beyond this size - we recycle the cache and regenerate names
// this recycling is in place because CTreeGenerator.GenerateNameForVar has prefix of "columnName" which could be unbounded
private const int MaxPrefixCount = 500;
// beyond this size per prefix, we don't cache the names (really large queries)
private const int CacheSize = 250;
// this caches integer->string so that happens less fequently
private readonly static string[] _counterNames = new string[CacheSize];
// We are using a copy-on-write instead of lock-on-read because dictionary is not multi-reader/single-writer safe.
// safe for frequent multi-thread reading by creating new instances (copy of previous instance) for uncommon writes.
private static Dictionary<string, string[]> _prefixCounter;
private int _counter;
private readonly string _prefix;
private string[] _cache;
/// <summary>
/// Constructs a new AliasGenerator with the specified prefix string
/// </summary>
/// <param name="prefix">The prefix string that will appear as the first part of all aliases generated by this AliasGenerator. May be null to indicate that no prefix should be used</param>
internal AliasGenerator(string prefix) : this(prefix, CacheSize) { }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1309:UseOrdinalStringComparison", MessageId = "System.Collections.Generic.Dictionary`2<System.String,System.String[]>.#ctor(System.Int32,System.Collections.Generic.IEqualityComparer`1<System.String>)")]
internal AliasGenerator(string prefix, int cacheSize)
{
_prefix = prefix ?? String.Empty;
// don't cache all alias, some are truely unique like CommandTree.BindingAliases
if (0 < cacheSize)
{
string[] cache = null;
Dictionary<string, string[]> updatedCache;
Dictionary<string, string[]> prefixCounter;
while ((null == (prefixCounter = _prefixCounter)) || !prefixCounter.TryGetValue(prefix, out _cache))
{
if (null == cache)
{ // we need to create an instance, but it a different thread may win
cache = new string[cacheSize];
}
// grow the cache for prefixes
// We are using a copy-on-write instead of lock-on-read because dictionary is not multi-reader/single-writer safe.
// a)Create a larger dictionary
// b) Copy references from previous dictionary
// c) If previous dictionary changed references, repeat from (a)
// d) We now know the individual cache
int capacity = 1 + ((null != prefixCounter) ? prefixCounter.Count : 0);
updatedCache = new Dictionary<string, string[]>(capacity, StringComparer.InvariantCultureIgnoreCase);
if ((null != prefixCounter) && (capacity < MaxPrefixCount))
{
foreach (KeyValuePair<string, string[]> entry in prefixCounter)
{
updatedCache.Add(entry.Key, entry.Value);
}
}
updatedCache.Add(prefix, cache);
System.Threading.Interlocked.CompareExchange(ref _prefixCounter, updatedCache, prefixCounter);
}
}
}
/// <summary>
/// Generates the next alias and increments the Counter.
/// </summary>
/// <returns>The generated alias</returns>
internal string Next()
{
_counter = Math.Max(unchecked(1+_counter), 0);
return GetName(_counter);
}
/// <summary>
/// Generates the alias for the index.
/// </summary>
/// <param name="index">index to generate the alias for</param>
/// <returns>The generated alias</returns>
internal string GetName(int index)
{
string name;
if ((null == _cache) || unchecked((uint)_cache.Length <= (uint)index))
{ // names are not cached beyond a particlar size
name = String.Concat(_prefix, index.ToString(CultureInfo.InvariantCulture));
}
else if (null == (name = _cache[index]))
{ // name has not been generated and cached yet
if (unchecked((uint)_counterNames.Length <= (uint)index))
{ // integer->string are not cached beyond a particular size
name = index.ToString(CultureInfo.InvariantCulture);
}
else if (null == (name = _counterNames[index]))
{ // generate and cache the integer->string
_counterNames[index] = name = index.ToString(CultureInfo.InvariantCulture);
}
// generate and cache the prefix+integer
_cache[index] = name = String.Concat(_prefix, name);
}
return name;
}
#if DEBUG
internal string Prefix
{
get { return _prefix; }
}
#endif
}
}
|