using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using Firebase.Database.Http; using Newtonsoft.Json; namespace Firebase.Database.Streaming { /// /// The firebase cache. /// /// Type of top-level entities in the cache. public class FirebaseCache : IEnumerable> { private readonly IDictionary dictionary; private readonly bool isDictionaryType; private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }; /// /// Initializes a new instance of the class. /// public FirebaseCache() : this(new Dictionary()) { } /// /// Initializes a new instance of the class and populates it with existing data. /// /// The existing items. public FirebaseCache(IDictionary existingItems) { dictionary = existingItems; isDictionaryType = typeof(IDictionary).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()); } /// /// The push data. /// /// The path of incoming data, separated by slash. /// The data in json format as returned by firebase. /// Collection of top-level entities which were affected by the push. public IEnumerable> PushData(string path, string data, bool removeEmptyEntries = true) { object obj = this.dictionary; Action primitiveObjSetter = null; Action objDeleter = null; var pathElements = path.Split(new[] {"/"}, removeEmptyEntries ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None); // first find where we should insert the data to foreach (var element in pathElements) if (obj is IDictionary) { // if it's a dictionary, then it's just a matter of inserting into it / accessing existing object by key var dictionary = obj as IDictionary; var valueType = obj.GetType().GenericTypeArguments[1]; primitiveObjSetter = d => dictionary[element] = d; objDeleter = () => dictionary.Remove(element); if (dictionary.Contains(element)) { obj = dictionary[element]; } else { dictionary[element] = CreateInstance(valueType); obj = dictionary[element]; } } else { // if it's not a dictionary, try to find the property of current object with the matching name var objParent = obj; var property = objParent .GetType() .GetRuntimeProperties() .First(p => p.Name.Equals(element, StringComparison.OrdinalIgnoreCase) || element == p.GetCustomAttribute()?.PropertyName); objDeleter = () => property.SetValue(objParent, null); primitiveObjSetter = d => property.SetValue(objParent, d); obj = property.GetValue(obj); if (obj == null) { obj = CreateInstance(property.PropertyType); property.SetValue(objParent, obj); } } // if data is null (=empty string) delete it if (string.IsNullOrWhiteSpace(data) || data == "null") { var key = pathElements[0]; var target = dictionary[key]; objDeleter(); yield return new FirebaseObject(key, target); yield break; } // now insert the data if (obj is IDictionary && !isDictionaryType) { // insert data into dictionary and return it as a collection of FirebaseObject var dictionary = obj as IDictionary; var valueType = obj.GetType().GenericTypeArguments[1]; var objectCollection = data.GetObjectCollection(valueType); foreach (var item in objectCollection) { dictionary[item.Key] = item.Object; // top level dictionary changed if (!pathElements.Any()) yield return new FirebaseObject(item.Key, (T) item.Object); } // nested dictionary changed if (pathElements.Any()) { this.dictionary[pathElements[0]] = this.dictionary[pathElements[0]]; yield return new FirebaseObject(pathElements[0], this.dictionary[pathElements[0]]); } } else { // set the data on a property of the given object var valueType = obj.GetType(); // firebase sends strings without double quotes var targetObject = valueType == typeof(string) ? data : JsonConvert.DeserializeObject(data, valueType); if ((valueType.GetTypeInfo().IsPrimitive || valueType == typeof(string)) && primitiveObjSetter != null) // handle primitive (value) types separately primitiveObjSetter(targetObject); else JsonConvert.PopulateObject(data, obj, serializerSettings); dictionary[pathElements[0]] = dictionary[pathElements[0]]; yield return new FirebaseObject(pathElements[0], dictionary[pathElements[0]]); } } public bool Contains(string key) { return dictionary.Keys.Contains(key); } private object CreateInstance(Type type) { if (type == typeof(string)) return string.Empty; return Activator.CreateInstance(type); } #region IEnumerable IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator> GetEnumerator() { return dictionary.Select(p => new FirebaseObject(p.Key, p.Value)).GetEnumerator(); } #endregion } }