using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LeanCloud.Storage.Internal { public class AVRelationOperation : IAVFieldOperation { private readonly IList adds; private readonly IList removes; private readonly string targetClassName; private AVRelationOperation(IEnumerable adds, IEnumerable removes, string targetClassName) { this.targetClassName = targetClassName; this.adds = new ReadOnlyCollection(adds.ToList()); this.removes = new ReadOnlyCollection(removes.ToList()); } public AVRelationOperation(IEnumerable adds, IEnumerable removes) { adds = adds ?? new AVObject[0]; removes = removes ?? new AVObject[0]; this.targetClassName = adds.Concat(removes).Select(o => o.ClassName).FirstOrDefault(); this.adds = new ReadOnlyCollection(IdsFromObjects(adds).ToList()); this.removes = new ReadOnlyCollection(IdsFromObjects(removes).ToList()); } public object Encode() { var adds = this.adds .Select(id => PointerOrLocalIdEncoder.Instance.Encode( AVObject.CreateWithoutData(targetClassName, id))) .ToList(); var removes = this.removes .Select(id => PointerOrLocalIdEncoder.Instance.Encode( AVObject.CreateWithoutData(targetClassName, id))) .ToList(); var addDict = adds.Count == 0 ? null : new Dictionary { {"__op", "AddRelation"}, {"objects", adds} }; var removeDict = removes.Count == 0 ? null : new Dictionary { {"__op", "RemoveRelation"}, {"objects", removes} }; if (addDict != null && removeDict != null) { return new Dictionary { {"__op", "Batch"}, {"ops", new[] {addDict, removeDict}} }; } return addDict ?? removeDict; } public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) { if (previous == null) { return this; } if (previous is AVDeleteOperation) { throw new InvalidOperationException("You can't modify a relation after deleting it."); } var other = previous as AVRelationOperation; if (other != null) { if (other.TargetClassName != TargetClassName) { throw new InvalidOperationException( string.Format("Related object must be of class {0}, but {1} was passed in.", other.TargetClassName, TargetClassName)); } var newAdd = adds.Union(other.adds.Except(removes)).ToList(); var newRemove = removes.Union(other.removes.Except(adds)).ToList(); return new AVRelationOperation(newAdd, newRemove, TargetClassName); } throw new InvalidOperationException("Operation is invalid after previous operation."); } public object Apply(object oldValue, string key) { if (adds.Count == 0 && removes.Count == 0) { return null; } if (oldValue == null) { return AVRelationBase.CreateRelation(null, key, targetClassName); } if (oldValue is AVRelationBase) { var oldRelation = (AVRelationBase)oldValue; var oldClassName = oldRelation.TargetClassName; if (oldClassName != null && oldClassName != targetClassName) { throw new InvalidOperationException("Related object must be a " + oldClassName + ", but a " + targetClassName + " was passed in."); } oldRelation.TargetClassName = targetClassName; return oldRelation; } throw new InvalidOperationException("Operation is invalid after previous operation."); } public string TargetClassName { get { return targetClassName; } } private IEnumerable IdsFromObjects(IEnumerable objects) { foreach (var obj in objects) { if (obj.ObjectId == null) { throw new ArgumentException( "You can't add an unsaved AVObject to a relation."); } if (obj.ClassName != targetClassName) { throw new ArgumentException(string.Format( "Tried to create a AVRelation with 2 different types: {0} and {1}", targetClassName, obj.ClassName)); } } return objects.Select(o => o.ObjectId).Distinct(); } } }