Skip to content

Commit 861efa2

Browse files
Decoupled ParseObject subclassing
Similar to the subclassing architecture used on iOS, this abstracts and cleans up all of the code relating to subclassing that currently resides inside of ParseObject.
1 parent 76021a3 commit 861efa2

19 files changed

+323
-173
lines changed

Parse/Compat/Tuple.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
using System.Text;
55

66
namespace System {
7-
internal static class Tuple {
7+
internal static class Tuple {
8+
// This is useful because it allows for type inference, which normally cannot be done with constructors, but can be done for static methods.
9+
public static Tuple<T1, T2> Create<T1, T2>(T1 t1, T2 t2) {
10+
return new Tuple<T1, T2>(t1, t2);
11+
}
812
}
913

1014
internal class Tuple<T1, T2> {

Parse/Compat/Type.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
3+
namespace System {
4+
/// <summary>
5+
/// Unity does not have an API for GetTypeInfo(), instead they expose most of the methods
6+
/// on System.Reflection.TypeInfo on the type itself. This poses a problem for compatibility
7+
/// with the rest of the C# world, as we expect the result of GetTypeInfo() to be an actual TypeInfo,
8+
/// as well as be able to be converted back to a type using AsType().
9+
///
10+
/// This class simply implements some of the simple missing methods on Type to make it as API-compatible
11+
/// as possible to TypeInfo.
12+
/// </summary>
13+
internal static class TypeExtensions {
14+
public static Type AsType(this Type type) {
15+
return type;
16+
}
17+
}
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Parse.Internal {
8+
internal interface IObjectSubclassingController {
9+
String GetClassName(Type type);
10+
Type GetType(String className);
11+
12+
bool IsTypeValid(String className, Type type);
13+
14+
void RegisterSubclass(Type t);
15+
void UnregisterSubclass(Type t);
16+
17+
ParseObject Instantiate(String className);
18+
IDictionary<String, String> GetPropertyMappings(String className);
19+
}
20+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using System.Reflection;
8+
9+
#if UNITY
10+
using TypeInfo = System.Type;
11+
#endif
12+
13+
namespace Parse.Internal {
14+
internal class ObjectSubclassInfo {
15+
public ObjectSubclassInfo(Type type, ConstructorInfo constructor) {
16+
TypeInfo = type.GetTypeInfo();
17+
ClassName = GetClassName(TypeInfo);
18+
Constructor = constructor;
19+
PropertyMappings = type.GetProperties()
20+
.Select(prop => Tuple.Create(prop, prop.GetCustomAttribute<ParseFieldNameAttribute>(true)))
21+
.Where(t => t.Item2 != null)
22+
.Select(t => Tuple.Create(t.Item1, t.Item2.FieldName))
23+
.ToDictionary(t => t.Item1.Name, t => t.Item2);
24+
}
25+
26+
public TypeInfo TypeInfo { get; private set; }
27+
public String ClassName { get; private set; }
28+
public IDictionary<String, String> PropertyMappings { get; private set; }
29+
private ConstructorInfo Constructor { get; set; }
30+
31+
public ParseObject Instantiate() {
32+
return (ParseObject)Constructor.Invoke(null);
33+
}
34+
35+
internal static String GetClassName(TypeInfo type) {
36+
var attribute = type.GetCustomAttribute<ParseClassNameAttribute>();
37+
return attribute != null ? attribute.ClassName : null;
38+
}
39+
}
40+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Threading;
6+
7+
#if UNITY
8+
using TypeInfo = System.Type;
9+
#endif
10+
11+
namespace Parse.Internal {
12+
internal class ObjectSubclassingController : IObjectSubclassingController {
13+
private readonly ReaderWriterLockSlim mutex;
14+
private readonly IDictionary<String, ObjectSubclassInfo> registeredSubclasses;
15+
private IDictionary<String, Action> registerActions;
16+
17+
public ObjectSubclassingController(IDictionary<Type, Action> actions) {
18+
mutex = new ReaderWriterLockSlim();
19+
registeredSubclasses = new Dictionary<String, ObjectSubclassInfo>();
20+
registerActions = actions.ToDictionary(p => GetClassName(p.Key), p => p.Value);
21+
}
22+
23+
public String GetClassName(Type type) {
24+
return ObjectSubclassInfo.GetClassName(type.GetTypeInfo());
25+
}
26+
27+
public Type GetType(String className) {
28+
ObjectSubclassInfo info = null;
29+
mutex.EnterReadLock();
30+
registeredSubclasses.TryGetValue(className, out info);
31+
mutex.ExitReadLock();
32+
33+
return info != null
34+
? info.TypeInfo.AsType()
35+
: null;
36+
}
37+
38+
public bool IsTypeValid(String className, Type type) {
39+
ObjectSubclassInfo subclassInfo = null;
40+
41+
mutex.EnterReadLock();
42+
registeredSubclasses.TryGetValue(className, out subclassInfo);
43+
mutex.ExitReadLock();
44+
45+
return subclassInfo == null
46+
? type == typeof(ParseObject)
47+
: subclassInfo.TypeInfo == type.GetTypeInfo();
48+
}
49+
50+
public void RegisterSubclass(Type type) {
51+
TypeInfo typeInfo = type.GetTypeInfo();
52+
if (!typeInfo.IsSubclassOf(typeof(ParseObject))) {
53+
throw new ArgumentException("Cannot register a type that is not a subclass of ParseObject");
54+
}
55+
56+
String className = ObjectSubclassInfo.GetClassName(typeInfo);
57+
58+
try {
59+
// Perform this as a single independent transaction, so we can never get into an
60+
// intermediate state where we *theoretically* register the wrong class due to a
61+
// TOCTTOU bug.
62+
mutex.EnterWriteLock();
63+
64+
ObjectSubclassInfo previousInfo = null;
65+
if (registeredSubclasses.TryGetValue(className, out previousInfo)) {
66+
if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo)) {
67+
// Previous subclass is more specific or equal to the current type, do nothing.
68+
return;
69+
} else if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo)) {
70+
// Previous subclass is parent of new child, fallthrough and actually register
71+
// this class.
72+
/* Do nothing */
73+
} else {
74+
throw new ArgumentException(
75+
"Tried to register both " + previousInfo.TypeInfo.FullName + " and " + typeInfo.FullName +
76+
" as the ParseObject subclass of " + className + ". Cannot determine the right class " +
77+
"to use because neither inherits from the other."
78+
);
79+
}
80+
}
81+
82+
ConstructorInfo constructor = type.FindConstructor();
83+
if (constructor == null) {
84+
throw new ArgumentException("Cannot register a type that does not implement the default constructor!");
85+
}
86+
87+
registeredSubclasses[className] = new ObjectSubclassInfo(type, constructor);
88+
} finally {
89+
mutex.ExitWriteLock();
90+
}
91+
92+
Action toPerform = null;
93+
if (registerActions.TryGetValue(className, out toPerform)) {
94+
toPerform();
95+
}
96+
}
97+
98+
public void UnregisterSubclass(Type type) {
99+
mutex.EnterWriteLock();
100+
registeredSubclasses.Remove(GetClassName(type));
101+
mutex.ExitWriteLock();
102+
}
103+
104+
public ParseObject Instantiate(String className) {
105+
ObjectSubclassInfo info = null;
106+
107+
mutex.EnterReadLock();
108+
registeredSubclasses.TryGetValue(className, out info);
109+
mutex.ExitReadLock();
110+
111+
return info != null
112+
? info.Instantiate()
113+
: new ParseObject(className);
114+
}
115+
116+
public IDictionary<String, String> GetPropertyMappings(String className) {
117+
ObjectSubclassInfo info = null;
118+
mutex.EnterReadLock();
119+
registeredSubclasses.TryGetValue(className, out info);
120+
mutex.ExitReadLock();
121+
122+
return info != null
123+
? info.PropertyMappings
124+
: null;
125+
}
126+
}
127+
}

Parse/Internal/ParseCorePlugins.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public static ParseCorePlugins Instance {
2828
private IParseSessionController sessionController;
2929
private IParseUserController userController;
3030
private IParsePushController pushController;
31-
private IParsePushChannelsController pushChannelsController;
31+
private IParsePushChannelsController pushChannelsController;
32+
private IObjectSubclassingController subclassingController;
3233

3334
#endregion
3435

@@ -242,6 +243,25 @@ internal set {
242243
currentUserController = value;
243244
}
244245
}
246+
}
247+
248+
public IObjectSubclassingController SubclassingController {
249+
get {
250+
lock (mutex) {
251+
subclassingController = subclassingController ?? new ObjectSubclassingController(new Dictionary<Type, Action> {
252+
// Do these as explicit closures instead of method references,
253+
// as we should still lazy-load the controllers.
254+
{ typeof(ParseUser), () => CurrentUserController.ClearFromMemory() },
255+
{ typeof(ParseInstallation), () => CurrentInstallationController.ClearFromMemory() }
256+
});
257+
return subclassingController;
258+
}
259+
}
260+
internal set {
261+
lock (mutex) {
262+
subclassingController = value;
263+
}
264+
}
245265
}
246266
}
247267
}

Parse/Internal/ReflectionHelpers.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ internal static bool IsConstructedGenericType(this Type type) {
5050
internal static IEnumerable<ConstructorInfo> GetConstructors(this Type type) {
5151
#if UNITY
5252
return type.GetConstructors();
53-
#else
54-
return type.GetTypeInfo().DeclaredConstructors;
53+
#else
54+
return type.GetTypeInfo().DeclaredConstructors
55+
.Where(c => (c.Attributes & MethodAttributes.Static) == 0);
5556
#endif
5657
}
5758

@@ -78,7 +79,7 @@ from constructor in self.GetConstructors()
7879
let types = from p in parameters select p.ParameterType
7980
where types.SequenceEqual(parameterTypes)
8081
select constructor;
81-
return constructors.Single();
82+
return constructors.SingleOrDefault();
8283
}
8384

8485
internal static PropertyInfo GetProperty(this Type type, string name) {

Parse/Parse.Android.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
<Compile Include="Internal\Object\Controller\ParseObjectController.cs" />
7474
<Compile Include="Internal\Object\State\IObjectState.cs" />
7575
<Compile Include="Internal\Object\State\MutableObjectState.cs" />
76+
<Compile Include="Internal\Object\Subclassing\IObjectSubclassingController.cs" />
77+
<Compile Include="Internal\Object\Subclassing\ObjectSubclassInfo.cs" />
78+
<Compile Include="Internal\Object\Subclassing\ObjectSubclassingController.cs" />
7679
<Compile Include="Internal\ParseCorePlugins.cs" />
7780
<Compile Include="Internal\ParseDecoder.cs" />
7881
<Compile Include="Internal\ParseEncoder.cs" />

Parse/Parse.Unity.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
<Compile Include="Compat\Progress.cs" />
5757
<Compile Include="Compat\ThreadLocal.cs" />
5858
<Compile Include="Compat\Tuple.cs" />
59+
<Compile Include="Compat\Type.cs" />
5960
<Compile Include="Internal\Analytics\Controller\IParseAnalyticsController.cs" />
6061
<Compile Include="Internal\Analytics\Controller\ParseAnalyticsController.cs" />
6162
<Compile Include="Internal\Cloud\Controller\IParseCloudCodeController.cs" />
@@ -90,6 +91,9 @@
9091
<Compile Include="Internal\Object\Controller\ParseObjectController.cs" />
9192
<Compile Include="Internal\Object\State\IObjectState.cs" />
9293
<Compile Include="Internal\Object\State\MutableObjectState.cs" />
94+
<Compile Include="Internal\Object\Subclassing\IObjectSubclassingController.cs" />
95+
<Compile Include="Internal\Object\Subclassing\ObjectSubclassInfo.cs" />
96+
<Compile Include="Internal\Object\Subclassing\ObjectSubclassingController.cs" />
9397
<Compile Include="Internal\ParseAddOperation.cs" />
9498
<Compile Include="Internal\ParseAddUniqueOperation.cs" />
9599
<Compile Include="Internal\ParseCorePlugins.cs" />

Parse/Parse.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@
6666
<Compile Include="Internal\Object\Controller\ParseObjectController.cs" />
6767
<Compile Include="Internal\Object\State\IObjectState.cs" />
6868
<Compile Include="Internal\Object\State\MutableObjectState.cs" />
69+
<Compile Include="Internal\Object\Subclassing\IObjectSubclassingController.cs" />
70+
<Compile Include="Internal\Object\Subclassing\ObjectSubclassInfo.cs" />
71+
<Compile Include="Internal\Object\Subclassing\ObjectSubclassingController.cs" />
6972
<Compile Include="Internal\ParseAddOperation.cs" />
7073
<Compile Include="Internal\ParseAddUniqueOperation.cs" />
7174
<Compile Include="Internal\Analytics\Controller\IParseAnalyticsController.cs" />

Parse/Parse.iOS.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@
136136
<Compile Include="Internal\Object\Controller\ParseObjectController.cs" />
137137
<Compile Include="Internal\Object\State\IObjectState.cs" />
138138
<Compile Include="Internal\Object\State\MutableObjectState.cs" />
139+
<Compile Include="Internal\Object\Subclassing\IObjectSubclassingController.cs" />
140+
<Compile Include="Internal\Object\Subclassing\ObjectSubclassInfo.cs" />
141+
<Compile Include="Internal\Object\Subclassing\ObjectSubclassingController.cs" />
139142
<Compile Include="Internal\ParseAddOperation.cs" />
140143
<Compile Include="Internal\ParseAddUniqueOperation.cs" />
141144
<Compile Include="Internal\ParseCorePlugins.cs" />

0 commit comments

Comments
 (0)