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: add "PropertiesToInclude" feature; control over max no of DifferentProperties; refactor #1900

Merged
merged 21 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
26b7153
#1866, #1711, refactor
alelom Jul 13, 2020
59160a3
Refactor to add the "diffing without revision" workflow.
alelom Jul 13, 2020
6bb7bfc
Ready to be tested.
alelom Jul 13, 2020
408a331
Added a "Diffing" interface method. Refactoring that follows. Bug fixes.
alelom Jul 14, 2020
6c9610a
A correction on DiffWithFragmentId.
alelom Jul 15, 2020
d235ea2
Added feature: DiffOneByOne. DifferentPorperites() now filters out pr…
alelom Jul 15, 2020
f2d3d0d
Merge branch 'master' into Diffing_Engine-#1866-PropertiesToInclude-r…
alelom Jul 15, 2020
0a82243
Description
alelom Jul 15, 2020
4853822
Added null check in ListModifiedProperties
alelom Jul 15, 2020
056949d
DifferentProperties-modifiedProperties: better names for props like F…
alelom Jul 15, 2020
e6f0b42
Remove useless Create for the DiffConfig, and removed default params …
alelom Jul 15, 2020
7e3ff36
`DifferentProperties()` - Feature: CustomData to ignore. Fix: for "pa…
alelom Jul 15, 2020
5f96325
Switched to DeepClone for all DiffConfigs for immutability in UI.
alelom Jul 15, 2020
9e407bc
Added Create() method for DiffConfig to remedy the bug in BHoM UI (se…
alelom Jul 15, 2020
50ad590
Sometimes I hate BHoM
alelom Jul 15, 2020
2785d07
Missing propagation of DiffConfig in SetHashFragment
alelom Jul 16, 2020
e55b92b
Correction in DifferentProperties: now correctly remove RenderMesh
alelom Jul 16, 2020
665cb3c
Deepclone in DifferentProperties to ensure object immutability in UI
alelom Jul 16, 2020
bb608e2
Added filterName input in ListModifiedProperties
alelom Jul 17, 2020
976ae7b
Minor correction for key name on DiffWithFragmentId
alelom Jul 23, 2020
71326d8
Merge branch 'master' into Diffing_Engine-#1866-PropertiesToInclude-r…
alelom Jul 31, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions Diffing_Engine/Compute/DiffGenericObjects.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2020, 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.Reflection;
using System.Collections;
using BH.Engine.Base;

namespace BH.Engine.Diffing
{
public static partial class Compute
{
[Description("Computes the diffing for generic objects that do not have any Id or HashFragment assigned." +
"\nShould be seen as last resort if no other diffing method can be applied.")]
[Input("pastObjects", "Past objects. Objects whose creation precedes 'currentObjects'.")]
[Input("currentObjects", "Following objects. Objects that were created after 'pastObjects'.")]
[Input("diffConfig", "Sets configs such as properties to be ignored in the diffing, or enable/disable property-by-property diffing.")]
[Input("useExistingHash", "Advanced setting. If the objects already have an HashFragment assigned, but that only has the 'currentHash' populated. Can be used to avoid recomputing hash in some scenarios.")]
public static Diff DiffGenericObjects(IEnumerable<object> pastObjects, IEnumerable<object> currentObjects, DiffConfig diffConfig = null, bool useExistingHash = false)
{
BH.Engine.Reflection.Compute.RecordNote("This diffing method cannot track modified objects between different revisions." +
"\nIt will simply return the objects that appear exclusively in the past set, in the following set, and in both." +
$"\nConsider using '{nameof(DiffWithCustomId)}', '{nameof(DiffWithFragmentId)}' or '{nameof(DiffRevisions)}' if this feature is needed.");

// Set configurations if diffConfig is null. Clone it for immutability in the UI.
DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : (DiffConfig)diffConfig.DeepClone();

// Clone objects for immutability in the UI.
List<object> pastObjects_cloned = BH.Engine.Base.Query.DeepClone(pastObjects).ToList();
List<object> currentObjects_cloned = BH.Engine.Base.Query.DeepClone(currentObjects).ToList();

if (!useExistingHash)
{
// Clean any existing hash fragment.
// This ensures the hash will be re-computed within this method using the provided DiffConfig.
pastObjects_cloned.OfType<IBHoMObject>().ToList().ForEach(o => o.Fragments.Remove(typeof(HashFragment)));
currentObjects_cloned.OfType<IBHoMObject>().ToList().ForEach(o => o.Fragments.Remove(typeof(HashFragment)));
}

// Compute the "Diffing" by means of a VennDiagram.
// Hashes are computed in the DiffingHashComparer, once per each object (the hash is stored in a hashFragment).
VennDiagram<object> vd = Engine.Data.Create.VennDiagram(pastObjects_cloned, currentObjects_cloned, new DiffingHashComparer<object>(diffConfigCopy, true));

return new Diff(vd.OnlySet2, vd.OnlySet1, null, diffConfigCopy, null, vd.Intersection);
}
}
}

100 changes: 100 additions & 0 deletions Diffing_Engine/Compute/DiffOneByOne.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2020, 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.Reflection;
using System.Collections;
using BH.Engine.Base;

namespace BH.Engine.Diffing
{
public static partial class Compute
{
[Description("Computes the diffing for two lists of objects, comparing objects one by one." +
"\nThis will only identify 'modified' or 'unchanged' objects. For 'modified' objects, the property differences are also returned." +
"\nIt will work correctly only if the input objects are in the same order.")]
[Input("pastObjects", "Past objects. Objects whose creation precedes 'currentObjects'.")]
[Input("currentObjects", "Following objects. Objects that were created after 'pastObjects'.")]
[Input("diffConfig", "Sets configs such as properties to be ignored in the diffing, or enable/disable property-by-property diffing.")]
[Input("useExistingHash", "Advanced setting. If the objects already have an HashFragment assigned, but that only has the 'currentHash' populated. Can be used to avoid recomputing hash in some scenarios.")]
public static Diff DiffOneByOne(IEnumerable<object> pastObjects, IEnumerable<object> currentObjects, DiffConfig diffConfig = null, bool useExistingHash = false)
{
if (pastObjects.Count() != currentObjects.Count())
{
BH.Engine.Reflection.Compute.RecordWarning($"Input collections must be of the same length for '{nameof(DiffOneByOne)}' to work.");
return null;
}

BH.Engine.Reflection.Compute.RecordNote($"This diffing method is equivalent to Equivalent to calling '{nameof(Query.DifferentProperties)}' on the input lists. " +
$"\nThis will only identify 'modified' or 'unchanged' objects. For 'modified' objects, the property differences are also returned." +
$"\nIt will work correctly only if the objects in the lists are in the same order and at most they have been modified (i.e. no new object has been added, no object has been deleted).");

// Set configurations if diffConfig is null. Clone it for immutability in the UI.
DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : (DiffConfig)diffConfig.DeepClone();
diffConfigCopy.EnablePropertyDiffing = true; // must be forced on for this Diffing method to make sense.

// Clone objects for immutability in the UI.
List<object> pastObjects_cloned = BH.Engine.Base.Query.DeepClone(pastObjects).ToList();
List<object> currentObjects_cloned = BH.Engine.Base.Query.DeepClone(currentObjects).ToList();

List<object> modifiedObjects = new List<object>();
List<object> unchangedObjects = new List<object>();

bool anyChangeDetected = false;

var allModifiedProps = new Dictionary<string, Dictionary<string, Tuple<object, object>>>();
for (int i = 0; i < pastObjects_cloned.Count(); i++)
{
var modifiedProps = Query.DifferentProperties(currentObjects_cloned[i], pastObjects_cloned[i], diffConfigCopy);

if (modifiedProps != null && modifiedProps.Any())
{
modifiedObjects.Add(currentObjects_cloned[i]);
anyChangeDetected = true;
}
else if (diffConfig.StoreUnchangedObjects)
unchangedObjects.Add(currentObjects_cloned[i]);

allModifiedProps[$"Object #{i}"] = modifiedProps ?? new Dictionary<string, Tuple<object, object>>();
}

if (!anyChangeDetected)
allModifiedProps = null;

return new Diff(new List<object>(), new List<object>(), modifiedObjects, diffConfigCopy, allModifiedProps, unchangedObjects);
}
}
}

184 changes: 184 additions & 0 deletions Diffing_Engine/Compute/DiffRevisions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2020, 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.Reflection;
using System.Collections;
using BH.Engine.Base;

namespace BH.Engine.Diffing
{
public static partial class Compute
{
[Description("Computes the diffing for Revisions containing objects of any type (also non-BHoMObjects).")]
[Input("pastRevision", "A past Revision. It must have been created before the 'followingRevision'.")]
[Input("followingRevision", "A following Revision. It must have been created after 'pastRevision'.")]
[Input("diffConfig", "Sets configs such as properties to be ignored in the diffing, or enable/disable property-by-property diffing.")]
public static Diff DiffRevisions(Revision pastRevision, Revision followingRevision, DiffConfig diffConfig = null)
{
return DiffRevisionObjects(pastRevision.Objects, followingRevision.Objects, diffConfig);
}

// Computes the diffing for IEnumerable<object>.
// For BHoMObjects, it assumes that they all have a HashFragment assigned (like when they have been passed through a Revision).
// For non-BHoMObjects, it performs the VennDiagram comparision with a HashComparer.
// Results for BHoMObjects and non are concatenated.
private static Diff DiffRevisionObjects(IEnumerable<object> pastRevisionObjs, IEnumerable<object> followingRevisionObjs, DiffConfig diffConfig = null)
{
// Set configurations if diffConfig is null. Clone it for immutability in the UI.
DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : diffConfig.DeepClone() as DiffConfig;

// Dispatch the objects in BHoMObjects and generic objects.
IEnumerable<IBHoMObject> prevObjs_BHoM = pastRevisionObjs.OfType<IBHoMObject>();
IEnumerable<IBHoMObject> currObjs_BHoM = followingRevisionObjs.OfType<IBHoMObject>();

// If all objects are bhomobjects, just call the appropriate method
if (pastRevisionObjs.Count() != 0 && pastRevisionObjs.Count() == prevObjs_BHoM.Count() && followingRevisionObjs.Count() == currObjs_BHoM.Count())
return DiffRevisionObjects(prevObjs_BHoM, currObjs_BHoM, diffConfigCopy);

IEnumerable<object> prevObjs_nonBHoM = pastRevisionObjs.Where(o => !(o is IBHoMObject));
IEnumerable<object> currObjs_nonBHoM = followingRevisionObjs.Where(o => !(o is IBHoMObject));

// Compute the specific Diffing for the BHoMObjects.
Diff diff = Compute.DiffRevisionObjects(prevObjs_BHoM, currObjs_BHoM, diffConfigCopy);

// Compute the generic Diffing for the other objects.
// This is left to the VennDiagram with a HashComparer.
VennDiagram<object> vd = Engine.Data.Create.VennDiagram(prevObjs_nonBHoM, currObjs_nonBHoM, new DiffingHashComparer<object>(diffConfig));

// Concatenate the results of the two diffing operations.
List<object> allPrevObjs = new List<object>();
List<object> allCurrObjs = new List<object>();
List<object> allUnchangedObjs = new List<object>();

allCurrObjs.AddRange(diff.AddedObjects);
allCurrObjs.AddRange(vd.OnlySet1);

allPrevObjs.AddRange(diff.RemovedObjects);
allPrevObjs.AddRange(vd.OnlySet2);

// Create the final, actual diff.
Diff finalDiff = new Diff(allCurrObjs, allPrevObjs, diff.ModifiedObjects, diffConfigCopy, diff.ModifiedPropsPerObject, diff.UnchangedObjects);

return finalDiff;
}

// Computes the Diffing for BHoMObjects that all have a HashFragment assigned (like when they have been passed through a Revision).
private static Diff DiffRevisionObjects(IEnumerable<IBHoMObject> pastObjects, IEnumerable<IBHoMObject> currentObjects, DiffConfig diffConfig = null)
{
// Set configurations if diffConfig is null. Clone it for immutability in the UI.
DiffConfig diffConfigCopy = diffConfig == null ? new DiffConfig() : diffConfig.DeepClone() as DiffConfig;

// Take the Revision's objects
List<IBHoMObject> currentObjs = currentObjects.ToList();
List<IBHoMObject> readObjs = pastObjects.ToList();

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

// Dispatch the objects: new, modified or old
List<IBHoMObject> newObjs = new List<IBHoMObject>();
List<IBHoMObject> modifiedObjs = new List<IBHoMObject>();
List<IBHoMObject> oldObjs = new List<IBHoMObject>();
List<IBHoMObject> unChanged = 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)
{
newObjs.Add(obj); // It's a new object
}

else if (hashFragm.PreviousHash == hashFragm.CurrentHash)
{
// It's NOT been modified
if (diffConfigCopy.StoreUnchangedObjects)
unChanged.Add(obj);
}

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

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

if (oldObjState == null) continue;

var differentProps = Query.DifferentProperties(obj, oldObjState, diffConfigCopy);

objModifiedProps.Add(hashFragm.CurrentHash, 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);

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

return new Diff(newObjs, oldObjs, modifiedObjs, diffConfig, objModifiedProps, unChanged);
}

private static bool AllHaveHashFragment(this IEnumerable<IBHoMObject> bHoMObjects)
{
// Check if objects have hashfragment.
if (bHoMObjects == null
|| bHoMObjects.Count() == 0
|| bHoMObjects.Select(o => o.GetHashFragment()).Where(o => o != null).Count() < bHoMObjects.Count())
return false;

return true;
}
}
}

Loading