using LeanCloud.Storage.Internal;
using LeanCloud.Utilities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Collections;
namespace LeanCloud
{
///
/// The AVObject is a local representation of data that can be saved and
/// retrieved from the LeanCloud cloud.
///
///
/// The basic workflow for creating new data is to construct a new AVObject,
/// use the indexer to fill it with data, and then use SaveAsync() to persist to the
/// database.
///
///
/// The basic workflow for accessing existing data is to use a AVQuery
/// to specify which existing data to retrieve.
///
///
public class AVObject : IEnumerable>, INotifyPropertyChanged, INotifyPropertyUpdated, INotifyCollectionPropertyUpdated, IAVObject
{
private static readonly string AutoClassName = "_Automatic";
#if UNITY
private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain");
#else
private static readonly bool isCompiledByIL2CPP = false;
#endif
internal readonly object mutex = new object();
private readonly LinkedList> operationSetQueue =
new LinkedList>();
private readonly IDictionary estimatedData = new Dictionary();
private static readonly ThreadLocal isCreatingPointer = new ThreadLocal(() => false);
private bool hasBeenFetched;
private bool dirty;
internal TaskQueue taskQueue = new TaskQueue();
private IObjectState state;
internal void MutateState(Action func)
{
lock (mutex)
{
state = state.MutatedClone(func);
// Refresh the estimated data.
RebuildEstimatedData();
}
}
public IObjectState State
{
get
{
return state;
}
}
internal static IAVObjectController ObjectController
{
get
{
return AVPlugins.Instance.ObjectController;
}
}
internal static IObjectSubclassingController SubclassingController
{
get
{
return AVPlugins.Instance.SubclassingController;
}
}
public static string GetSubClassName()
{
return SubclassingController.GetClassName(typeof(TAVObject));
}
#region AVObject Creation
///
/// Constructor for use in AVObject subclasses. Subclasses must specify a AVClassName attribute.
///
protected AVObject()
: this(AutoClassName)
{
}
///
/// Constructs a new AVObject with no data in it. A AVObject constructed in this way will
/// not have an ObjectId and will not persist to the database until
/// is called.
///
///
/// Class names must be alphanumerical plus underscore, and start with a letter. It is recommended
/// to name classes in CamelCaseLikeThis.
///
/// The className for this AVObject.
public AVObject(string className)
{
// We use a ThreadLocal rather than passing a parameter so that createWithoutData can do the
// right thing with subclasses. It's ugly and terrible, but it does provide the development
// experience we generally want, so... yeah. Sorry to whomever has to deal with this in the
// future. I pinky-swear we won't make a habit of this -- you believe me, don't you?
var isPointer = isCreatingPointer.Value;
isCreatingPointer.Value = false;
if (className == null)
{
throw new ArgumentException("You must specify a LeanCloud class name when creating a new AVObject.");
}
if (AutoClassName.Equals(className))
{
className = SubclassingController.GetClassName(GetType());
}
// If this is supposed to be created by a factory but wasn't, throw an exception
if (!SubclassingController.IsTypeValid(className, GetType()))
{
throw new ArgumentException(
"You must create this type of AVObject using AVObject.Create() or the proper subclass.");
}
state = new MutableObjectState
{
ClassName = className
};
OnPropertyChanged("ClassName");
operationSetQueue.AddLast(new Dictionary());
if (!isPointer)
{
hasBeenFetched = true;
IsDirty = true;
SetDefaultValues();
}
else
{
IsDirty = false;
hasBeenFetched = false;
}
}
///
/// Creates a new AVObject based upon a class name. If the class name is a special type (e.g.
/// for ), then the appropriate type of AVObject is returned.
///
/// The class of object to create.
/// A new AVObject for the given class name.
public static AVObject Create(string className)
{
return SubclassingController.Instantiate(className);
}
///
/// Creates a reference to an existing AVObject for use in creating associations between
/// AVObjects. Calling on this object will return
/// false until has been called.
/// No network request will be made.
///
/// The object's class.
/// The object id for the referenced object.
/// A AVObject without data.
public static AVObject CreateWithoutData(string className, string objectId)
{
isCreatingPointer.Value = true;
try
{
var result = SubclassingController.Instantiate(className);
result.ObjectId = objectId;
result.IsDirty = false;
if (result.IsDirty)
{
throw new InvalidOperationException(
"A AVObject subclass default constructor must not make changes to the object that cause it to be dirty.");
}
return result;
}
finally
{
isCreatingPointer.Value = false;
}
}
///
/// Creates a new AVObject based upon a given subclass type.
///
/// A new AVObject for the given class name.
public static T Create() where T : AVObject
{
return (T)SubclassingController.Instantiate(SubclassingController.GetClassName(typeof(T)));
}
///
/// Creates a reference to an existing AVObject for use in creating associations between
/// AVObjects. Calling on this object will return
/// false until has been called.
/// No network request will be made.
///
/// The object id for the referenced object.
/// A AVObject without data.
public static T CreateWithoutData(string objectId) where T : AVObject
{
return (T)CreateWithoutData(SubclassingController.GetClassName(typeof(T)), objectId);
}
///
/// restore a AVObject of subclass instance from IObjectState.
///
/// IObjectState after encode from Dictionary.
/// The name of the subclass.
public static T FromState(IObjectState state, string defaultClassName) where T : AVObject
{
string className = state.ClassName ?? defaultClassName;
T obj = (T)CreateWithoutData(className, state.ObjectId);
obj.HandleFetchResult(state);
return obj;
}
#endregion
public static IDictionary GetPropertyMappings(string className)
{
return SubclassingController.GetPropertyMappings(className);
}
private static string GetFieldForPropertyName(string className, string propertyName)
{
String fieldName = null;
SubclassingController.GetPropertyMappings(className).TryGetValue(propertyName, out fieldName);
return fieldName;
}
///
/// Sets the value of a property based upon its associated AVFieldName attribute.
///
/// The new value.
/// The name of the property.
/// The type for the property.
protected virtual void SetProperty(T value,
#if !UNITY
[CallerMemberName] string propertyName = null
#else
string propertyName
#endif
)
{
this[GetFieldForPropertyName(ClassName, propertyName)] = value;
}
///
/// Gets a relation for a property based upon its associated AVFieldName attribute.
///
/// The AVRelation for the property.
/// The name of the property.
/// The AVObject subclass type of the AVRelation.
protected AVRelation GetRelationProperty(
#if !UNITY
[CallerMemberName] string propertyName = null
#else
string propertyName
#endif
) where T : AVObject
{
return GetRelation(GetFieldForPropertyName(ClassName, propertyName));
}
///
/// Gets the value of a property based upon its associated AVFieldName attribute.
///
/// The value of the property.
/// The name of the property.
/// The return type of the property.
protected virtual T GetProperty(
#if !UNITY
[CallerMemberName] string propertyName = null
#else
string propertyName
#endif
)
{
return GetProperty(default(T), propertyName);
}
///
/// Gets the value of a property based upon its associated AVFieldName attribute.
///
/// The value of the property.
/// The value to return if the property is not present on the AVObject.
/// The name of the property.
/// The return type of the property.
protected virtual T GetProperty(T defaultValue,
#if !UNITY
[CallerMemberName] string propertyName = null
#else
string propertyName
#endif
)
{
T result;
if (TryGetValue(GetFieldForPropertyName(ClassName, propertyName), out result))
{
return result;
}
return defaultValue;
}
///
/// Allows subclasses to set values for non-pointer construction.
///
internal virtual void SetDefaultValues()
{
}
///
/// Registers a custom subclass type with the LeanCloud SDK, enabling strong-typing of those AVObjects whenever
/// they appear. Subclasses must specify the AVClassName attribute, have a default constructor, and properties
/// backed by AVObject fields should have AVFieldName attributes supplied.
///
/// The AVObject subclass type to register.
public static void RegisterSubclass() where T : AVObject, new()
{
SubclassingController.RegisterSubclass(typeof(T));
}
internal static void UnregisterSubclass() where T : AVObject, new()
{
SubclassingController.UnregisterSubclass(typeof(T));
}
///
/// Clears any changes to this object made since the last call to .
///
public void Revert()
{
lock (mutex)
{
bool wasDirty = CurrentOperations.Count > 0;
if (wasDirty)
{
CurrentOperations.Clear();
RebuildEstimatedData();
OnPropertyChanged("IsDirty");
}
}
}
internal virtual void HandleFetchResult(IObjectState serverState)
{
lock (mutex)
{
MergeFromServer(serverState);
}
}
internal void HandleFailedSave(
IDictionary operationsBeforeSave)
{
lock (mutex)
{
var opNode = operationSetQueue.Find(operationsBeforeSave);
var nextOperations = opNode.Next.Value;
bool wasDirty = nextOperations.Count > 0;
operationSetQueue.Remove(opNode);
// Merge the data from the failed save into the next save.
foreach (var pair in operationsBeforeSave)
{
var operation1 = pair.Value;
IAVFieldOperation operation2 = null;
nextOperations.TryGetValue(pair.Key, out operation2);
if (operation2 != null)
{
operation2 = operation2.MergeWithPrevious(operation1);
}
else
{
operation2 = operation1;
}
nextOperations[pair.Key] = operation2;
}
if (!wasDirty && nextOperations == CurrentOperations && operationsBeforeSave.Count > 0)
{
OnPropertyChanged("IsDirty");
}
}
}
internal virtual void HandleSave(IObjectState serverState)
{
lock (mutex)
{
var operationsBeforeSave = operationSetQueue.First.Value;
operationSetQueue.RemoveFirst();
// Merge the data from the save and the data from the server into serverData.
//MutateState(mutableClone =>
//{
// mutableClone.Apply(operationsBeforeSave);
//});
state = state.MutatedClone((objectState) => objectState.Apply(operationsBeforeSave));
MergeFromServer(serverState);
}
}
public virtual void MergeFromServer(IObjectState serverState)
{
// Make a new serverData with fetched values.
var newServerData = serverState.ToDictionary(t => t.Key, t => t.Value);
lock (mutex)
{
// Trigger handler based on serverState
if (serverState.ObjectId != null)
{
// If the objectId is being merged in, consider this object to be fetched.
hasBeenFetched = true;
OnPropertyChanged("IsDataAvailable");
}
if (serverState.UpdatedAt != null)
{
OnPropertyChanged("UpdatedAt");
}
if (serverState.CreatedAt != null)
{
OnPropertyChanged("CreatedAt");
}
// We cache the fetched object because subsequent Save operation might flush
// the fetched objects into Pointers.
IDictionary fetchedObject = CollectFetchedObjects();
foreach (var pair in serverState)
{
var value = pair.Value;
if (value is AVObject)
{
// Resolve fetched object.
var avObject = value as AVObject;
if (fetchedObject.ContainsKey(avObject.ObjectId))
{
value = fetchedObject[avObject.ObjectId];
}
}
newServerData[pair.Key] = value;
}
IsDirty = false;
serverState = serverState.MutatedClone(mutableClone =>
{
mutableClone.ServerData = newServerData;
});
MutateState(mutableClone =>
{
mutableClone.Apply(serverState);
});
}
}
internal void MergeFromObject(AVObject other)
{
lock (mutex)
{
// If they point to the same instance, we don't need to merge
if (this == other)
{
return;
}
}
// Clear out any changes on this object.
if (operationSetQueue.Count != 1)
{
throw new InvalidOperationException("Attempt to MergeFromObject during save.");
}
operationSetQueue.Clear();
foreach (var operationSet in other.operationSetQueue)
{
operationSetQueue.AddLast(operationSet.ToDictionary(entry => entry.Key,
entry => entry.Value));
}
lock (mutex)
{
state = other.State;
}
RebuildEstimatedData();
}
private bool HasDirtyChildren
{
get
{
lock (mutex)
{
return FindUnsavedChildren().FirstOrDefault() != null;
}
}
}
///
/// Flattens dictionaries and lists into a single enumerable of all contained objects
/// that can then be queried over.
///
/// The root of the traversal
/// Whether to traverse into AVObjects' children
/// Whether to include the root in the result
///
internal static IEnumerable