/
RealmSchema.cs
223 lines (197 loc) · 8.9 KB
/
RealmSchema.cs
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Realms.Helpers;
namespace Realms.Schema
{
/// <summary>
/// Describes the complete set of classes which may be stored in a Realm, either from assembly declarations or,
/// dynamically, by evaluating a Realm from disk.
/// </summary>
/// <remarks>
/// By default this will be all the <see cref="RealmObject"/>s and <see cref="EmbeddedObject"/>s in all your assemblies
/// unless you restrict with <see cref="RealmConfigurationBase.ObjectClasses"/>. Just because a given class <em>may</em>
/// be stored in a Realm doesn't imply much overhead. There will be a small amount of metadata but objects only start to
/// take up space once written.
/// </remarks>
public class RealmSchema : IReadOnlyCollection<ObjectSchema>
{
private static readonly HashSet<Type> _defaultTypes = new HashSet<Type>();
private static readonly Lazy<RealmSchema> _default = new Lazy<RealmSchema>(GetSchema);
private readonly ReadOnlyDictionary<string, ObjectSchema> _objects;
/// <summary>
/// Adds a collection of types to the default schema.
/// </summary>
/// <param name="types">Types to be added to the default schema.</param>
/// <exception cref="NotSupportedException">Thrown if the schema has already materialized.</exception>
public static void AddDefaultTypes(IEnumerable<Type> types)
{
Argument.NotNull(types, nameof(types));
foreach (var type in types)
{
if (_defaultTypes.Add(type) &&
_default.IsValueCreated)
{
throw new NotSupportedException("AddDefaultTypes should be called before creating a Realm instance with the default schema. If you see this error, please report it to help@realm.io.");
}
}
}
/// <summary>
/// Gets the number of known classes in the schema.
/// </summary>
/// <value>The number of known classes.</value>
public int Count => _objects.Count;
internal static RealmSchema Default => _default.Value;
internal static RealmSchema Empty => new RealmSchema(Enumerable.Empty<ObjectSchema>());
private RealmSchema(IEnumerable<ObjectSchema> objects)
{
_objects = new ReadOnlyDictionary<string, ObjectSchema>(objects.ToDictionary(o => o.Name));
}
/// <summary>
/// Finds the definition of a class in this schema.
/// </summary>
/// <param name="name">A valid class name which may be in this schema.</param>
/// <exception cref="ArgumentException">Thrown if a name is not supplied.</exception>
/// <returns>An <see cref="ObjectSchema"/> or <c>null</c> to indicate not found.</returns>
public ObjectSchema Find(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Object schema name must be a non-empty string", nameof(name));
}
_objects.TryGetValue(name, out var obj);
return obj;
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<ObjectSchema> GetEnumerator()
{
return _objects.Values.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
internal static RealmSchema CreateSchemaForClasses(IEnumerable<Type> classes, RealmSchema existing = null)
{
var builder = new Builder();
var classNames = new HashSet<string>();
if (existing != null)
{
builder.AddRange(existing);
foreach (var item in existing)
{
classNames.Add(item.Name);
}
}
foreach (var @class in classes)
{
var typeInfo = @class.GetTypeInfo();
var objectSchema = ObjectSchema.FromType(@class.GetTypeInfo());
if (classNames.Add(objectSchema.Name))
{
builder.Add(objectSchema);
}
else
{
var duplicateType = builder.FirstOrDefault(s => s.Name == objectSchema.Name).Type;
if (typeInfo.FullName != duplicateType.FullName)
{
var errorMessage = "The names (without namespace) of objects persisted in Realm must be unique." +
$"The duplicate types are {@class.FullName} and {duplicateType.FullName}. Either rename one" +
" of them or explicitly specify ObjectClasses on your RealmConfiguration.";
throw new NotSupportedException(errorMessage);
}
}
}
return builder.Build();
}
internal static RealmSchema GetSchema()
{
if (_defaultTypes.Count == 0)
{
// this was introduced because Unity's IL2CPP won't behave as expected with module initializers
// so we manually do what .Net-like frameworks usually do with module initializers
try
{
var moduleInitializers = AppDomain.CurrentDomain.GetAssemblies()
.Select(assembly => assembly.GetType("RealmModuleInitializer")?.GetMethod("Initialize"))
.Where(method => method != null);
foreach (var moduleInitializer in moduleInitializers)
{
moduleInitializer.Invoke(null, null);
}
}
catch
{
}
}
return CreateSchemaForClasses(_defaultTypes);
}
internal static RealmSchema CreateFromObjectStoreSchema(Native.Schema nativeSchema)
{
var objects = new ObjectSchema[nativeSchema.objects_len];
for (var i = 0; i < nativeSchema.objects_len; i++)
{
var objectSchema = Marshal.PtrToStructure<Native.SchemaObject>(IntPtr.Add(nativeSchema.objects, i * Native.SchemaObject.Size));
var builder = new ObjectSchema.Builder(objectSchema.name, objectSchema.is_embedded);
for (var n = objectSchema.properties_start; n < objectSchema.properties_end; n++)
{
var nativeProperty = Marshal.PtrToStructure<Native.SchemaProperty>(IntPtr.Add(nativeSchema.properties, n * Native.SchemaProperty.Size));
builder.Add(new Property
{
Name = nativeProperty.name,
Type = nativeProperty.type,
ObjectType = nativeProperty.object_type,
LinkOriginPropertyName = nativeProperty.link_origin_property_name,
IsPrimaryKey = nativeProperty.is_primary,
IsIndexed = nativeProperty.is_indexed
});
}
objects[i] = builder.Build();
}
return new RealmSchema(objects);
}
internal class Builder : List<ObjectSchema>
{
public RealmSchema Build()
{
if (Count == 0)
{
throw new InvalidOperationException(
"No RealmObjects. Has linker stripped them? See https://realm.io/docs/xamarin/latest/#linker-stripped-schema");
}
return new RealmSchema(this);
}
}
}
}