2015-04-07 09:35:12 +01:00
//---------------------------------------------------------------------
// <copyright file="AliasGenerator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
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
}
}