/// <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);
        }
Exemple #4
0
        /// <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);
        }