Skip to content

Commit 9d51b34

Browse files
Merge pull request #61 from ParsePlatform/richardross.subclassing.decouple
Decoupled ParseObject subclassing
2 parents c606130 + 95cfdb4 commit 9d51b34

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)