172 lines
5.8 KiB
C#
172 lines
5.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using LeanCloud.Storage.Internal;
|
|
|
|
namespace LeanCloud.Storage.Internal
|
|
{
|
|
internal class ObjectSubclassingController : IObjectSubclassingController
|
|
{
|
|
// Class names starting with _ are documented to be reserved. Use this one
|
|
// here to allow us to 'inherit' certain properties.
|
|
private static readonly string avObjectClassName = "_AVObject";
|
|
|
|
private readonly ReaderWriterLockSlim mutex;
|
|
private readonly IDictionary<String, ObjectSubclassInfo> registeredSubclasses;
|
|
private Dictionary<String, Action> registerActions;
|
|
|
|
public ObjectSubclassingController()
|
|
{
|
|
mutex = new ReaderWriterLockSlim();
|
|
registeredSubclasses = new Dictionary<String, ObjectSubclassInfo>();
|
|
registerActions = new Dictionary<string, Action>();
|
|
|
|
// Register the AVObject subclass, so we get access to the ACL,
|
|
// objectId, and other AVFieldName properties.
|
|
RegisterSubclass(typeof(AVObject));
|
|
}
|
|
|
|
public String GetClassName(Type type)
|
|
{
|
|
return type == typeof(AVObject)
|
|
? avObjectClassName
|
|
: 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(AVObject)
|
|
: subclassInfo.TypeInfo == type.GetTypeInfo();
|
|
}
|
|
|
|
public void RegisterSubclass(Type type)
|
|
{
|
|
TypeInfo typeInfo = type.GetTypeInfo();
|
|
if (!typeof(AVObject).GetTypeInfo().IsAssignableFrom(typeInfo))
|
|
{
|
|
throw new ArgumentException("Cannot register a type that is not a subclass of AVObject");
|
|
}
|
|
|
|
String className = GetClassName(type);
|
|
|
|
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 AVObject 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;
|
|
|
|
mutex.EnterReadLock();
|
|
registerActions.TryGetValue(className, out toPerform);
|
|
mutex.ExitReadLock();
|
|
|
|
if (toPerform != null)
|
|
{
|
|
toPerform();
|
|
}
|
|
}
|
|
|
|
public void UnregisterSubclass(Type type)
|
|
{
|
|
mutex.EnterWriteLock();
|
|
registeredSubclasses.Remove(GetClassName(type));
|
|
mutex.ExitWriteLock();
|
|
}
|
|
|
|
public void AddRegisterHook(Type t, Action action)
|
|
{
|
|
mutex.EnterWriteLock();
|
|
registerActions.Add(GetClassName(t), action);
|
|
mutex.ExitWriteLock();
|
|
}
|
|
|
|
public AVObject Instantiate(String className)
|
|
{
|
|
ObjectSubclassInfo info = null;
|
|
|
|
mutex.EnterReadLock();
|
|
registeredSubclasses.TryGetValue(className, out info);
|
|
mutex.ExitReadLock();
|
|
|
|
return info != null
|
|
? info.Instantiate()
|
|
: new AVObject(className);
|
|
}
|
|
|
|
public IDictionary<String, String> GetPropertyMappings(String className)
|
|
{
|
|
ObjectSubclassInfo info = null;
|
|
mutex.EnterReadLock();
|
|
registeredSubclasses.TryGetValue(className, out info);
|
|
if (info == null)
|
|
{
|
|
registeredSubclasses.TryGetValue(avObjectClassName, out info);
|
|
}
|
|
mutex.ExitReadLock();
|
|
|
|
return info.PropertyMappings;
|
|
}
|
|
|
|
}
|
|
}
|