1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
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
{
/// <summary>
/// The firebase cache.
/// </summary>
/// <typeparam name="T"> Type of top-level entities in the cache. </typeparam>
public class FirebaseCache<T> : IEnumerable<FirebaseObject<T>>
{
private readonly IDictionary<string, T> dictionary;
private readonly bool isDictionaryType;
private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings
{
ObjectCreationHandling = ObjectCreationHandling.Replace
};
/// <summary>
/// Initializes a new instance of the <see cref="FirebaseCache{T}" /> class.
/// </summary>
public FirebaseCache()
: this(new Dictionary<string, T>())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FirebaseCache{T}" /> class and populates it with existing data.
/// </summary>
/// <param name="existingItems"> The existing items. </param>
public FirebaseCache(IDictionary<string, T> existingItems)
{
dictionary = existingItems;
isDictionaryType = typeof(IDictionary).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo());
}
/// <summary>
/// The push data.
/// </summary>
/// <param name="path"> The path of incoming data, separated by slash. </param>
/// <param name="data"> The data in json format as returned by firebase. </param>
/// <returns> Collection of top-level entities which were affected by the push. </returns>
public IEnumerable<FirebaseObject<T>> PushData(string path, string data, bool removeEmptyEntries = true)
{
object obj = this.dictionary;
Action<object> 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<JsonPropertyAttribute>()?.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<T>(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<T>(item.Key, (T) item.Object);
}
// nested dictionary changed
if (pathElements.Any())
{
this.dictionary[pathElements[0]] = this.dictionary[pathElements[0]];
yield return new FirebaseObject<T>(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<T>(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<FirebaseObject<T>> GetEnumerator()
{
return dictionary.Select(p => new FirebaseObject<T>(p.Key, p.Value)).GetEnumerator();
}
#endregion
}
}
|