|
//---------------------------------------------------------------------
// <copyright file="MetadataMappingHasherVisitor.HashSourceBuilder.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Data.Common.Utils;
using System.Security.Cryptography;
using System.Globalization;
using System.IO;
namespace System.Data.Mapping
{
/// <summary>
/// This class keeps recomputing the hash and adding it to the front of the
/// builder when the length of the string gets too long
/// </summary>
internal class CompressingHashBuilder : StringHashBuilder
{
// this max comes from the value that Md5Hasher uses for a buffer size when it is reading
// from a stream
private const int HashCharacterCompressionThreshold = 0x1000 / 2; // num bytes / 2 to convert to typical unicode char size
private const int SpacesPerIndent = 4;
private int _indent = 0;
// we are starting the buffer at 1.5 times the number of bytes
// for the threshold
internal CompressingHashBuilder(HashAlgorithm hashAlgorithm)
: base(hashAlgorithm, (HashCharacterCompressionThreshold + (HashCharacterCompressionThreshold / 2)) * 2)
{
}
internal override void Append(string content)
{
base.Append(string.Empty.PadLeft(SpacesPerIndent * _indent, ' '));
base.Append(content);
CompressHash();
}
internal override void AppendLine(string content)
{
base.Append(string.Empty.PadLeft(SpacesPerIndent * _indent, ' '));
base.AppendLine(content);
CompressHash();
}
/// <summary>
/// add string like "typename Instance#1"
/// </summary>
/// <param name="objectIndex"></param>
internal void AppendObjectStartDump(object o, int objectIndex)
{
base.Append(string.Empty.PadLeft(SpacesPerIndent * _indent, ' '));
base.Append(o.GetType().ToString());
base.Append(" Instance#");
base.AppendLine(objectIndex.ToString(CultureInfo.InvariantCulture));
CompressHash();
this._indent++;
}
internal void AppendObjectEndDump()
{
Debug.Assert(this._indent > 0, "Indent and unindent should be paired");
this._indent--;
}
private void CompressHash()
{
if(base.CharCount >= HashCharacterCompressionThreshold)
{
string hash = ComputeHash();
Clear();
base.Append(hash);
}
}
}
/// <summary>
/// this class collects several strings together, and allows you to (
/// </summary>
internal class StringHashBuilder
{
private HashAlgorithm _hashAlgorithm;
private const string NewLine = "\n";
List<string> _strings = new List<string>();
int _totalLength;
byte[] _cachedBuffer;
internal StringHashBuilder(HashAlgorithm hashAlgorithm)
{
_hashAlgorithm = hashAlgorithm;
}
internal StringHashBuilder(HashAlgorithm hashAlgorithm, int startingBufferSize)
:this(hashAlgorithm)
{
Debug.Assert(startingBufferSize > 0, "should be a non zero positive integer");
_cachedBuffer = new byte[startingBufferSize];
}
internal int CharCount { get { return _totalLength; } }
internal virtual void Append(string s)
{
InternalAppend(s);
}
internal virtual void AppendLine(string s)
{
InternalAppend(s);
InternalAppend(NewLine);
}
private void InternalAppend(string s)
{
if (s.Length == 0)
return;
_strings.Add(s);
_totalLength += s.Length;
}
internal string ComputeHash()
{
int byteCount = GetByteCount();
if(_cachedBuffer == null)
{
// assume it is a one time use, and
// it will grow later if needed
_cachedBuffer = new byte[byteCount];
}
else if (_cachedBuffer.Length < byteCount)
{
// grow it by what is needed at a minimum, or 1.5 times bigger
// if that is bigger than what is needed this time. We
// make it 1.5 times bigger in hopes to reduce the number of allocations (consider the
// case where the next one it 1 bigger)
int bufferSize = Math.Max(_cachedBuffer.Length + (_cachedBuffer.Length / 2), byteCount);
_cachedBuffer = new byte[bufferSize];
}
int start = 0;
foreach (string s in _strings)
{
start += Encoding.Unicode.GetBytes(s, 0, s.Length, _cachedBuffer, start);
}
Debug.Assert(start == byteCount, "Did we use a different calculation for these?");
byte[] hash = _hashAlgorithm.ComputeHash(_cachedBuffer, 0, byteCount);
return ConvertHashToString(hash);
}
internal void Clear()
{
_strings.Clear();
_totalLength = 0;
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
_strings.ForEach(s => builder.Append(s));
return builder.ToString();
}
private int GetByteCount()
{
int count = 0;
foreach (string s in _strings)
{
count += Encoding.Unicode.GetByteCount(s);
}
return count;
}
private static string ConvertHashToString(byte[] hash)
{
StringBuilder stringData = new StringBuilder(hash.Length * 2);
// Loop through each byte of the data and format each one as a
// hexadecimal string
for (int i = 0; i < hash.Length; i++)
{
stringData.Append(hash[i].ToString("x2", CultureInfo.InvariantCulture));
}
return stringData.ToString();
}
public static string ComputeHash(HashAlgorithm hashAlgorithm, string source)
{
StringHashBuilder builder = new StringHashBuilder(hashAlgorithm);
builder.Append(source);
return builder.ComputeHash();
}
}
}
|