/// <summary> /// Unsubscribe from model object changes /// </summary> /// <param name="model">Model object to unsubscribe from</param> private static void UnsubscribeFromModelObject(ModelObject model) { if (model == null) { return; } PropertyChangedEventHandler changeHandler = _propertyChangedHandlers[model]; if (changeHandler == null) { // Already unregistered - don't bother to continue return; } model.PropertyChanged -= changeHandler; _propertyChangedHandlers.Remove(model); bool unregisterPropertyChanging = false; foreach (PropertyInfo property in model.GetType().GetProperties()) { object value = property.GetValue(model); if (property.PropertyType.IsSubclassOf(typeof(ModelObject))) { if (value != null) { UnsubscribeFromModelObject((ModelObject)value); } unregisterPropertyChanging |= (property.SetMethod != null); } else if (ModelCollection.GetItemType(property.PropertyType, out Type itemType)) { if (ModelGrowingCollection.TypeMatches(property.PropertyType)) { UnsubscribeFromGrowingModelCollection(value); } else { UnsubscribeFromModelCollection(value, property.PropertyType.GetGenericArguments()[0]); } } else if (property.PropertyType.IsGenericType && typeof(ModelDictionary <>) == property.PropertyType.GetGenericTypeDefinition()) { UnsubscribeFromModelDictionary(value); } } if (unregisterPropertyChanging) { // Same here - unregister the event handler only where required model.PropertyChanging -= VariableModelObjectChanging; } }
/// <summary> /// Subscribe to changes of the given model object /// </summary> /// <param name="model">Object to subscribe to</param> /// <param name="path">Collection path</param> private static void SubscribeToModelObject(ModelObject model, object[] path) { if (model == null) { return; } bool hasVariableModelObjects = false; foreach (PropertyInfo property in model.GetType().GetProperties()) { string propertyName = JsonNamingPolicy.CamelCase.ConvertName(property.Name); object value = property.GetValue(model); if (property.PropertyType.IsSubclassOf(typeof(ModelObject))) { if (value != null) { SubscribeToModelObject((ModelObject)value, AddToPath(path, propertyName)); } hasVariableModelObjects |= (property.SetMethod != null); } else if (ModelCollection.GetItemType(property.PropertyType, out Type itemType)) { if (ModelGrowingCollection.TypeMatches(property.PropertyType)) { SubscribeToGrowingModelCollection(value, propertyName, path); } else { SubscribeToModelCollection(value, itemType, propertyName, path); } } else if (property.PropertyType.IsGenericType && typeof(ModelDictionary <>) == property.PropertyType.GetGenericTypeDefinition()) { SubscribeToModelDictionary(value, AddToPath(path, propertyName)); } } PropertyChangedEventHandler changeHandler = PropertyChanged(hasVariableModelObjects, path); model.PropertyChanged += changeHandler; _propertyChangedHandlers[model] = changeHandler; if (hasVariableModelObjects) { // This is barely needed so only register it where it is actually required. // It makes sure that events are removed again when a ModelObject instance is replaced model.PropertyChanging += VariableModelObjectChanging; } }
/// <summary> /// Internal function to find a specific object in the object model /// </summary> /// <param name="partialModel">Partial object model</param> /// <param name="partialFilter">Array consisting of item indices or case-insensitive property names</param> /// <returns>Dictionary or list holding the result or null if nothing could be found</returns> private static object InternalGetFiltered(object partialModel, object[] partialFilter) { // Cannot proceed if there is nothing more to do... if (partialModel == null || partialFilter.Length == 0) { return(null); } object currentFilter = partialFilter[0]; partialFilter = partialFilter.Skip(1).ToArray(); // Check what kind of item to expect if (currentFilter is string propertyName) { if (partialModel is ModelObject model) { Dictionary <string, object> result = new Dictionary <string, object>(); foreach (KeyValuePair <string, PropertyInfo> property in model.JsonProperties) { if (propertyName == "*" || property.Key == propertyName) { if (partialFilter.Length == 0 || (partialFilter.Length == 1 && partialFilter[0] is string partialStringFilter && partialStringFilter == "**")) { // This is a property we've been looking for result.Add(property.Key, property.Value.GetValue(model)); continue; } if (property.Value.PropertyType.IsSubclassOf(typeof(ModelObject)) || typeof(IList).IsAssignableFrom(property.Value.PropertyType)) { // Property is somewhere deeper object propertyValue = property.Value.GetValue(model); object subResult = InternalGetFiltered(propertyValue, partialFilter); if (subResult != null) { result.Add(property.Key, subResult); } } } } return((result.Count != 0) ? result : null); } } else if (currentFilter is int itemIndex) { if (partialModel is IList list && itemIndex >= -1 && itemIndex < list.Count) { bool isModelObjectList = false, isListList = false; if (ModelCollection.GetItemType(partialModel.GetType(), out Type itemType)) { isModelObjectList = itemType.IsSubclassOf(typeof(ModelObject)); isListList = typeof(IList).IsAssignableFrom(itemType); } // If this is a value list or the list we've been looking for, return it immediately if ((!isModelObjectList && !isListList) || (itemIndex == -1 && partialFilter.Length == 0)) { return(list); } // This is an object list, return either the filter results or dummy objects List <object> results = new List <object>(new object[list.Count]); for (int i = 0; i < list.Count; i++) { object item = list[i]; if (itemIndex == -1 || i == itemIndex) { if (partialFilter.Length == 0) { // This is one of the items we've been looking for results[i] = item; } else if (item != null) { // Property is somewhere deeper object subResult = InternalGetFiltered(item, partialFilter); if (subResult != null) { // Got a result results[i] = subResult; } else { // Set placeholder results[i] = isModelObjectList ? (object)new Dictionary <string, object>() : new List <object>(); } } } else if (item != null) { // Set placeholder results[i] = isModelObjectList ? (object)new Dictionary <string, object>() : new List <object>(); } } return(results); } } // Nothing found return(null); }
/// <summary> /// Find the diffs between two lists /// </summary> /// <param name="oldList">Old list</param> /// <param name="newList">New list</param> /// <param name="itemType">Item type</param> /// <returns>Differences of the lists or null if they are equal</returns> public static IList FindDiffs(IList oldList, IList newList, Type itemType) { bool hadDiffs = (oldList.Count != newList.Count); IList diffs = new object[newList.Count]; if (itemType.IsSubclassOf(typeof(ModelObject))) { for (int i = 0; i < newList.Count; i++) { if (i < oldList.Count) { ModelObject oldItem = (ModelObject)oldList[i], newItem = (ModelObject)newList[i]; if (oldItem == null || newItem == null || oldItem.GetType() != newItem.GetType()) { if (oldItem != newItem) { hadDiffs = true; diffs[i] = newItem; } else { diffs[i] = new Dictionary <string, object>(); } } else { object diff = newItem.MakePatch(oldItem); if (diff != null) { hadDiffs = true; diffs[i] = diff; } else { diffs[i] = new Dictionary <string, object>(); } } } else { diffs[i] = newList[i]; } } } else if (ModelCollection.GetItemType(itemType, out Type subItemType)) { for (int i = 0; i < newList.Count; i++) { if (i < oldList.Count) { IList oldItem = (IList)oldList[i], newItem = (IList)newList[i]; if (oldItem == null || newItem == null || oldItem.GetType() != newItem.GetType()) { if (oldItem != newItem) { hadDiffs = true; diffs[i] = newItem; } else { diffs[i] = new Dictionary <string, object>(); } } else { object diff = FindDiffs(oldItem, newItem, subItemType); if (diff != null) { hadDiffs = true; diffs[i] = diff; } else { diffs[i] = new Dictionary <string, object>(); } } } else { diffs[i] = newList[i]; } } } else { diffs = newList; if (!hadDiffs) { for (int i = 0; i < newList.Count; i++) { if (!oldList[i].Equals(newList[i])) { hadDiffs = true; break; } } } } return(hadDiffs ? diffs : null); }