Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Parse/Compat/Tuple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
using System.Text;

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

internal class Tuple<T1, T2> {
Expand Down
18 changes: 18 additions & 0 deletions Parse/Compat/Type.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace System {
/// <summary>
/// Unity does not have an API for GetTypeInfo(), instead they expose most of the methods
/// on System.Reflection.TypeInfo on the type itself. This poses a problem for compatibility
/// with the rest of the C# world, as we expect the result of GetTypeInfo() to be an actual TypeInfo,
/// as well as be able to be converted back to a type using AsType().
///
/// This class simply implements some of the simple missing methods on Type to make it as API-compatible
/// as possible to TypeInfo.
/// </summary>
internal static class TypeExtensions {
public static Type AsType(this Type type) {
return type;
}
}
}
20 changes: 20 additions & 0 deletions Parse/Internal/Object/Subclassing/IObjectSubclassingController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Parse.Internal {
internal interface IObjectSubclassingController {
String GetClassName(Type type);
Type GetType(String className);

bool IsTypeValid(String className, Type type);

void RegisterSubclass(Type t);
void UnregisterSubclass(Type t);

ParseObject Instantiate(String className);
IDictionary<String, String> GetPropertyMappings(String className);
}
}
40 changes: 40 additions & 0 deletions Parse/Internal/Object/Subclassing/ObjectSubclassInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Reflection;

#if UNITY
using TypeInfo = System.Type;
#endif

namespace Parse.Internal {
internal class ObjectSubclassInfo {
public ObjectSubclassInfo(Type type, ConstructorInfo constructor) {
TypeInfo = type.GetTypeInfo();
ClassName = GetClassName(TypeInfo);
Constructor = constructor;
PropertyMappings = type.GetProperties()
.Select(prop => Tuple.Create(prop, prop.GetCustomAttribute<ParseFieldNameAttribute>(true)))
.Where(t => t.Item2 != null)
.Select(t => Tuple.Create(t.Item1, t.Item2.FieldName))
.ToDictionary(t => t.Item1.Name, t => t.Item2);
}

public TypeInfo TypeInfo { get; private set; }
public String ClassName { get; private set; }
public IDictionary<String, String> PropertyMappings { get; private set; }
private ConstructorInfo Constructor { get; set; }

public ParseObject Instantiate() {
return (ParseObject)Constructor.Invoke(null);
}

internal static String GetClassName(TypeInfo type) {
var attribute = type.GetCustomAttribute<ParseClassNameAttribute>();
return attribute != null ? attribute.ClassName : null;
}
}
}
127 changes: 127 additions & 0 deletions Parse/Internal/Object/Subclassing/ObjectSubclassingController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;

#if UNITY
using TypeInfo = System.Type;
#endif

namespace Parse.Internal {
internal class ObjectSubclassingController : IObjectSubclassingController {
private readonly ReaderWriterLockSlim mutex;
private readonly IDictionary<String, ObjectSubclassInfo> registeredSubclasses;
private IDictionary<String, Action> registerActions;

public ObjectSubclassingController(IDictionary<Type, Action> actions) {
mutex = new ReaderWriterLockSlim();
registeredSubclasses = new Dictionary<String, ObjectSubclassInfo>();
registerActions = actions.ToDictionary(p => GetClassName(p.Key), p => p.Value);
}

public String GetClassName(Type type) {
return ObjectSubclassInfo.GetClassName(type.GetTypeInfo());
}

public Type GetType(String className) {
ObjectSubclassInfo info = null;
mutex.EnterReadLock();
registeredSubclasses.TryGetValue(className, out info);
mutex.ExitReadLock();

return info != null
? info.TypeInfo.AsType()
: null;
}

public bool IsTypeValid(String className, Type type) {
ObjectSubclassInfo subclassInfo = null;

mutex.EnterReadLock();
registeredSubclasses.TryGetValue(className, out subclassInfo);
mutex.ExitReadLock();

return subclassInfo == null
? type == typeof(ParseObject)
: subclassInfo.TypeInfo == type.GetTypeInfo();
}

public void RegisterSubclass(Type type) {
TypeInfo typeInfo = type.GetTypeInfo();
if (!typeInfo.IsSubclassOf(typeof(ParseObject))) {
throw new ArgumentException("Cannot register a type that is not a subclass of ParseObject");
}

String className = ObjectSubclassInfo.GetClassName(typeInfo);

try {
// Perform this as a single independent transaction, so we can never get into an
// intermediate state where we *theoretically* register the wrong class due to a
// TOCTTOU bug.
mutex.EnterWriteLock();

ObjectSubclassInfo previousInfo = null;
if (registeredSubclasses.TryGetValue(className, out previousInfo)) {
if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo)) {
// Previous subclass is more specific or equal to the current type, do nothing.
return;
} else if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo)) {
// Previous subclass is parent of new child, fallthrough and actually register
// this class.
/* Do nothing */
} else {
throw new ArgumentException(
"Tried to register both " + previousInfo.TypeInfo.FullName + " and " + typeInfo.FullName +
" as the ParseObject subclass of " + className + ". Cannot determine the right class " +
"to use because neither inherits from the other."
);
}
}

ConstructorInfo constructor = type.FindConstructor();
if (constructor == null) {
throw new ArgumentException("Cannot register a type that does not implement the default constructor!");
}

registeredSubclasses[className] = new ObjectSubclassInfo(type, constructor);
} finally {
mutex.ExitWriteLock();
}

Action toPerform = null;
if (registerActions.TryGetValue(className, out toPerform)) {
toPerform();
}
}

public void UnregisterSubclass(Type type) {
mutex.EnterWriteLock();
registeredSubclasses.Remove(GetClassName(type));
mutex.ExitWriteLock();
}

public ParseObject Instantiate(String className) {
ObjectSubclassInfo info = null;

mutex.EnterReadLock();
registeredSubclasses.TryGetValue(className, out info);
mutex.ExitReadLock();

return info != null
? info.Instantiate()
: new ParseObject(className);
}

public IDictionary<String, String> GetPropertyMappings(String className) {
ObjectSubclassInfo info = null;
mutex.EnterReadLock();
registeredSubclasses.TryGetValue(className, out info);
mutex.ExitReadLock();

return info != null
? info.PropertyMappings
: null;
}
}
}
22 changes: 21 additions & 1 deletion Parse/Internal/ParseCorePlugins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public static ParseCorePlugins Instance {
private IParseSessionController sessionController;
private IParseUserController userController;
private IParsePushController pushController;
private IParsePushChannelsController pushChannelsController;
private IParsePushChannelsController pushChannelsController;
private IObjectSubclassingController subclassingController;

#endregion

Expand Down Expand Up @@ -242,6 +243,25 @@ internal set {
currentUserController = value;
}
}
}

public IObjectSubclassingController SubclassingController {
get {
lock (mutex) {
subclassingController = subclassingController ?? new ObjectSubclassingController(new Dictionary<Type, Action> {
// Do these as explicit closures instead of method references,
// as we should still lazy-load the controllers.
{ typeof(ParseUser), () => CurrentUserController.ClearFromMemory() },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah. I see it. This is good.

{ typeof(ParseInstallation), () => CurrentInstallationController.ClearFromMemory() }
});
return subclassingController;
}
}
internal set {
lock (mutex) {
subclassingController = value;
}
}
}
}
}
7 changes: 4 additions & 3 deletions Parse/Internal/ReflectionHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ internal static bool IsConstructedGenericType(this Type type) {
internal static IEnumerable<ConstructorInfo> GetConstructors(this Type type) {
#if UNITY
return type.GetConstructors();
#else
return type.GetTypeInfo().DeclaredConstructors;
#else
return type.GetTypeInfo().DeclaredConstructors
.Where(c => (c.Attributes & MethodAttributes.Static) == 0);
#endif
}

Expand All @@ -78,7 +79,7 @@ from constructor in self.GetConstructors()
let types = from p in parameters select p.ParameterType
where types.SequenceEqual(parameterTypes)
select constructor;
return constructors.Single();
return constructors.SingleOrDefault();
}

internal static PropertyInfo GetProperty(this Type type, string name) {
Expand Down
3 changes: 3 additions & 0 deletions Parse/Parse.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
<Compile Include="Internal\Object\Controller\ParseObjectController.cs" />
<Compile Include="Internal\Object\State\IObjectState.cs" />
<Compile Include="Internal\Object\State\MutableObjectState.cs" />
<Compile Include="Internal\Object\Subclassing\IObjectSubclassingController.cs" />
<Compile Include="Internal\Object\Subclassing\ObjectSubclassInfo.cs" />
<Compile Include="Internal\Object\Subclassing\ObjectSubclassingController.cs" />
<Compile Include="Internal\ParseCorePlugins.cs" />
<Compile Include="Internal\ParseDecoder.cs" />
<Compile Include="Internal\ParseEncoder.cs" />
Expand Down
4 changes: 4 additions & 0 deletions Parse/Parse.Unity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
<Compile Include="Compat\Progress.cs" />
<Compile Include="Compat\ThreadLocal.cs" />
<Compile Include="Compat\Tuple.cs" />
<Compile Include="Compat\Type.cs" />
<Compile Include="Internal\Analytics\Controller\IParseAnalyticsController.cs" />
<Compile Include="Internal\Analytics\Controller\ParseAnalyticsController.cs" />
<Compile Include="Internal\Cloud\Controller\IParseCloudCodeController.cs" />
Expand Down Expand Up @@ -90,6 +91,9 @@
<Compile Include="Internal\Object\Controller\ParseObjectController.cs" />
<Compile Include="Internal\Object\State\IObjectState.cs" />
<Compile Include="Internal\Object\State\MutableObjectState.cs" />
<Compile Include="Internal\Object\Subclassing\IObjectSubclassingController.cs" />
<Compile Include="Internal\Object\Subclassing\ObjectSubclassInfo.cs" />
<Compile Include="Internal\Object\Subclassing\ObjectSubclassingController.cs" />
<Compile Include="Internal\ParseAddOperation.cs" />
<Compile Include="Internal\ParseAddUniqueOperation.cs" />
<Compile Include="Internal\ParseCorePlugins.cs" />
Expand Down
3 changes: 3 additions & 0 deletions Parse/Parse.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
<Compile Include="Internal\Object\Controller\ParseObjectController.cs" />
<Compile Include="Internal\Object\State\IObjectState.cs" />
<Compile Include="Internal\Object\State\MutableObjectState.cs" />
<Compile Include="Internal\Object\Subclassing\IObjectSubclassingController.cs" />
<Compile Include="Internal\Object\Subclassing\ObjectSubclassInfo.cs" />
<Compile Include="Internal\Object\Subclassing\ObjectSubclassingController.cs" />
<Compile Include="Internal\ParseAddOperation.cs" />
<Compile Include="Internal\ParseAddUniqueOperation.cs" />
<Compile Include="Internal\Analytics\Controller\IParseAnalyticsController.cs" />
Expand Down
3 changes: 3 additions & 0 deletions Parse/Parse.iOS.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@
<Compile Include="Internal\Object\Controller\ParseObjectController.cs" />
<Compile Include="Internal\Object\State\IObjectState.cs" />
<Compile Include="Internal\Object\State\MutableObjectState.cs" />
<Compile Include="Internal\Object\Subclassing\IObjectSubclassingController.cs" />
<Compile Include="Internal\Object\Subclassing\ObjectSubclassInfo.cs" />
<Compile Include="Internal\Object\Subclassing\ObjectSubclassingController.cs" />
<Compile Include="Internal\ParseAddOperation.cs" />
<Compile Include="Internal\ParseAddUniqueOperation.cs" />
<Compile Include="Internal\ParseCorePlugins.cs" />
Expand Down
Loading