Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diffing_Engine : Initial implementation #1150

Merged
merged 11 commits into from
Sep 16, 2019
10 changes: 10 additions & 0 deletions BHoM_Engine.sln
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Analytical_Engine", "Analyt
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Physical_Engine", "Physical_Engine\Physical_Engine.csproj", "{F2073888-9BD8-4D8F-9B3C-27577B388530}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Diffing_Engine", "Diffing_Engine\Diffing_Engine.csproj", "{073DFD36-0829-4792-8C32-67BF692A9413}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -232,6 +234,14 @@ Global
{F2073888-9BD8-4D8F-9B3C-27577B388530}.Release|Any CPU.Build.0 = Release|Any CPU
{F2073888-9BD8-4D8F-9B3C-27577B388530}.Release|x86.ActiveCfg = Release|Any CPU
{F2073888-9BD8-4D8F-9B3C-27577B388530}.Release|x86.Build.0 = Release|Any CPU
{073DFD36-0829-4792-8C32-67BF692A9413}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{073DFD36-0829-4792-8C32-67BF692A9413}.Debug|Any CPU.Build.0 = Debug|Any CPU
{073DFD36-0829-4792-8C32-67BF692A9413}.Debug|x86.ActiveCfg = Debug|Any CPU
{073DFD36-0829-4792-8C32-67BF692A9413}.Debug|x86.Build.0 = Debug|Any CPU
{073DFD36-0829-4792-8C32-67BF692A9413}.Release|Any CPU.ActiveCfg = Release|Any CPU
{073DFD36-0829-4792-8C32-67BF692A9413}.Release|Any CPU.Build.0 = Release|Any CPU
{073DFD36-0829-4792-8C32-67BF692A9413}.Release|x86.ActiveCfg = Release|Any CPU
{073DFD36-0829-4792-8C32-67BF692A9413}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
8 changes: 7 additions & 1 deletion BHoM_Engine/Create/RandomObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,23 @@ private static void LinkInterfaces()
private static object InitialiseObject(Type type, Random rnd, int depth = 0)
{
// Create object
object obj;
object obj = null;
if (type.GetInterface("IImmutable") != null)
return CreateImmutable(type, rnd, depth);
else if (type.IsGenericType)
{
type = GetType(type);
obj = Activator.CreateInstance(type);
}
else if (typeof(BH.oM.Base.IBHoMFragment).IsAssignableFrom(type))
{
// Do not instantiate random Fragments to avoid "missing parameterless constructor" exception
return null;
}
else
obj = Activator.CreateInstance(type);


// Set its public properties
foreach (PropertyInfo prop in type.GetProperties())
{
Expand Down
180 changes: 180 additions & 0 deletions Diffing_Engine/Compute/Diffing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2019, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Base;
using BH.Engine;
using BH.oM.Data.Collections;
using BH.oM.Diffing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.Reflection;
using BH.Engine.Serialiser;
using System.ComponentModel;
using BH.oM.Reflection.Attributes;
using BH.oM.Testing;
using BH.oM.Reflection;

namespace BH.Engine.Diffing
{
public static partial class Compute
{
/***************************************************/
/**** Public Methods ****/
/***************************************************/

[Description("Returns a Delta object with the differences between the Stream objects and the provided object list.")]
[Input("previousStream", "A previous version of a Stream")]
[Input("currentStream", "A new version of a Stream")]
[Input("enablePropertyDiff", "If true, enables the Property-level diffing, which returns the differences down to the individual object properties.")]
[Input("exceptions", "List of strings specifying the names of the properties that should be ignored in the comparison, e.g. 'BHoM_Guid'")]
[Input("useDefaultExceptions", "If true, adds a list of default exceptions: 'BHoM_Guid', 'CustomData', 'Fragments'. Defaults to true.")]
public static Delta Diffing(Stream previousStream, Stream currentStream, bool enablePropertyDiff = true, List<string> exceptions = null, bool useDefaultExceptions = true)
{
// Take the Stream's objects
List<IBHoMObject> currentObjs = currentStream.Objects.ToList();
List<IBHoMObject> readObjs = previousStream.Objects.ToList();

// Make dictionary with object hashes to speed up the next lookups
Dictionary<string, IBHoMObject> readObjs_dict = readObjs.ToDictionary(obj => obj.GetHashFragment().Hash, obj => obj);

// Set exceptions
if (useDefaultExceptions)
Compute.SetDefaultExceptions(ref exceptions);

// Dispatch the objects: new, modified or old
List<IBHoMObject> toBeCreated = new List<IBHoMObject>();
List<IBHoMObject> toBeUpdated = new List<IBHoMObject>();
List<IBHoMObject> toBeDeleted = new List<IBHoMObject>();
var objModifiedProps = new Dictionary<string, Dictionary<string, Tuple<object, object>>>();

foreach (var obj in currentObjs)
{
var hashFragm = obj.GetHashFragment();

if (hashFragm?.PreviousHash == null)
{
toBeCreated.Add(obj); // It's a new object
}

else if (hashFragm.PreviousHash == hashFragm.Hash)
{
// It's NOT been modified
}

else if (hashFragm.PreviousHash != hashFragm.Hash)
{
toBeUpdated.Add(obj); // It's been modified

if (enablePropertyDiff)
{
// Determine changed properties
IBHoMObject oldObjState = null;
readObjs_dict.TryGetValue(hashFragm.PreviousHash, out oldObjState);

if (oldObjState == null) continue;

IsEqualConfig config = new IsEqualConfig();
config.PropertiesToIgnore = exceptions;

var differentProps = BH.Engine.Testing.Query.DifferentProperties(obj, oldObjState, config);

objModifiedProps.Add(hashFragm.Hash, differentProps);
}
}
else
{
throw new Exception("Could not find hash information to perform Diffing on some objects.");
}
}

// If no modified property was found, set the field to null (otherwise will get empty list)
objModifiedProps = objModifiedProps.Count == 0 ? null : objModifiedProps;

// All ReadObjs that cannot be found by hash in the previousHash of the CurrentObjs are toBeDeleted
Dictionary<string, IBHoMObject> CurrentObjs_withPreviousHash_dict = currentObjs
.Where(obj => obj.GetHashFragment().PreviousHash != null)
.ToDictionary(obj => obj.GetHashFragment().PreviousHash, obj => obj);

toBeDeleted = readObjs_dict.Keys.Except(CurrentObjs_withPreviousHash_dict.Keys)
.Where(k => readObjs_dict.ContainsKey(k)).Select(k => readObjs_dict[k]).ToList();

return new Delta(toBeCreated, toBeDeleted, toBeUpdated, objModifiedProps);

}

[Description("Dispatch objects in two sets into the ones exclusive to one set, the other, or both.")]
[Input("setA", "A previous version of a Stream")]
[Input("setB", "A new version of a Stream")]
[Input("exceptions", "List of strings specifying the names of the properties that should be ignored in the comparison, e.g. 'BHoM_Guid'")]
[Input("useDefaultExceptions", "If true, adds a list of default exceptions: 'BHoM_Guid', 'CustomData', 'Fragments'. Defaults to true.")]
[MultiOutputAttribute(0, "OnlySetA", "Object existing exclusively in setA")]
[MultiOutputAttribute(1, "Intersection-A", "Objects existing in both sets. Returns objects originally from set A.")]
[MultiOutputAttribute(2, "Intersection-B", "Objects existing in both sets. Returns objects originally from set B.")]
[MultiOutputAttribute(3, "OnlySetB", "Object existing exclusively in setB")]
public static Output<List<IBHoMObject>, List<IBHoMObject>, List<IBHoMObject>, List<IBHoMObject>> Diffing(IEnumerable<IBHoMObject> setA, IEnumerable<IBHoMObject> setB, List<string> exceptions = null, bool useDefaultExceptions = true)
{
Stream streamA = BH.Engine.Diffing.Create.Stream(setA, null);
Stream streamB = BH.Engine.Diffing.Create.Stream(setB, null);

VennDiagram<IBHoMObject> diagram = Engine.Data.Create.VennDiagram(streamA.Objects, streamB.Objects, new HashFragmComparer<IBHoMObject>());

return new Output<List<IBHoMObject>, List<IBHoMObject>, List<IBHoMObject>, List<IBHoMObject>>
{
Item1 = diagram.OnlySet1,
Item2 = diagram.Intersection.Select(tuple => tuple.Item1).ToList(),
Item3 = diagram.Intersection.Select(tuple => tuple.Item2).ToList(),
Item4 = diagram.OnlySet2,
};
}


/***************************************************/


/***************************************************/
/**** Public Fields ****/
/***************************************************/

public class HashFragmComparer<T> : IEqualityComparer<T> where T : IBHoMObject
{
public bool Equals(T x, T y)
{
string firstHash = x.GetHashFragment().Hash;
string secHash = y.GetHashFragment().Hash;
if (!string.IsNullOrEmpty(firstHash) && !string.IsNullOrEmpty(secHash) && firstHash == secHash)
return true;
else
return false;
}
public int GetHashCode(T obj)
{
return obj.GetHashFragment().GetHashCode();
}
}

/***************************************************/
}
}
75 changes: 75 additions & 0 deletions Diffing_Engine/Compute/DiffingHash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2019, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Base;
using BH.oM.Data.Collections;
using BH.oM.Diffing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.Reflection;
using BH.Engine.Serialiser;
using BH.oM.Reflection.Attributes;
using System.ComponentModel;

namespace BH.Engine.Diffing
{
public static partial class Compute
{
///***************************************************/
///**** Public Fields ****/
///***************************************************/

public static List<string> defaultHashExceptions = new List<string>() { "BHoM_Guid", "CustomData", "Fragments" };

///***************************************************/
///**** Public Methods ****/
///***************************************************/

[Description("Computes the hash code required for the Diffing.")]
[Input("objects", "Objects the hash code should be calculated for")]
[Input("exceptions", "List of strings specifying the names of the properties that should be ignored in the calculation, e.g. BHoM_Guid")]
[Input("useDefaultExceptions", "If true, adds a list of default exceptions: 'BHoM_Guid', 'CustomData', 'Fragments'. Defaults to true.")]
public static string DiffingHash(this IBHoMObject obj, List<string> exceptions = null, bool useDefaultExceptions = true)
{
if (exceptions == null || exceptions.Count == 0 && useDefaultExceptions)
SetDefaultExceptions(ref exceptions);

return Compute.SHA256Hash(obj, exceptions);
}

///***************************************************/
///**** Private Methods ****/
///***************************************************/

internal static void SetDefaultExceptions(ref List<string> exceptions)
{
if (exceptions == null)
exceptions = defaultHashExceptions;
else
exceptions.AddRange(defaultHashExceptions);
}
}
}
74 changes: 74 additions & 0 deletions Diffing_Engine/Compute/SHA256Hash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2019, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Base;
using BH.oM.Data.Collections;
using BH.oM.Diffing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.Reflection;
using BH.Engine.Serialiser;
using BH.oM.Reflection.Attributes;
using System.ComponentModel;

namespace BH.Engine.Diffing
{
public static partial class Compute
{
///***************************************************/
///**** Public Methods ****/
///***************************************************/

[Description("Computes the a SHA 256 hash code representing the object.")]
[Input("objects", "Object the hash code should be calculated for")]
[Input("exceptions", "List of strings specifying the names of the properties that should be ignored in the calculation, e.g. 'BHoM_Guid'")]
public static string SHA256Hash(IBHoMObject obj, List<string> exceptions = null)
{
return SHA256Hash(obj.ToDiffingByteArray(exceptions));
}

///***************************************************/
///**** Private Methods ****/
///***************************************************/

private static string SHA256Hash(byte[] inputObj)
{
StringBuilder sb = new StringBuilder();
foreach (byte b in SHA256_byte(inputObj))
sb.Append(b.ToString("X2"));

return sb.ToString();
}

private static byte[] SHA256_byte(byte[] inputObj)
{
HashAlgorithm algorithm = System.Security.Cryptography.SHA256.Create();
return algorithm.ComputeHash(inputObj);
}


}
}
Loading