536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
214 lines
6.7 KiB
C#
214 lines
6.7 KiB
C#
//---------------------------------------------------------------------
|
|
// <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();
|
|
}
|
|
}
|
|
}
|