using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace UVC.Data { /// /// DataObject 객체 컬렉션의 변경사항을 추적하는 데이터 배열 클래스 /// public class DataArray : List, IDataObject { /// /// 이 객체가 객체 풀에 있는지 여부를 나타냅니다. /// 중복 반환을 방지하기 위해 DataArrayPool에서 내부적으로 사용됩니다. /// internal bool IsInPool { get; set; } = false; // 추가 된 항목 목록 protected List addedList = new List(); // 제거 된 항목 목록 protected List removedList = new List(); // 수정 된 항목 목록 protected List modifiedList = new List(); // 추가된 항목에 접근할 수 있는 읽기 전용 컬렉션 public ReadOnlyCollection AddedItems => addedList.AsReadOnly(); // 제거된 항목에 접근할 수 있는 읽기 전용 컬렉션 public ReadOnlyCollection RemovedItems => removedList.AsReadOnly(); // 제거된 항목에 접근할 수 있는 읽기 전용 컬렉션 public ReadOnlyCollection ModifiedList => modifiedList.AsReadOnly(); /// /// 기본 생성자 /// public DataArray() : base() { } /// /// 초기 용량을 지정하는 생성자 /// /// 초기 용량 public DataArray(int capacity) : base(capacity) { } /// /// 기존 컬렉션으로부터 생성하는 생성자 /// /// 초기 항목을 포함하는 컬렉션 public DataArray(IEnumerable collection) : base(collection) { } public DataArray(string jsonString) : base() { if (!string.IsNullOrEmpty(jsonString)) { try { JArray jArray = JArray.Parse(jsonString); foreach (var item in jArray) { Add(ConvertToDataObject(item)); } } catch (Exception ex) { throw new ArgumentException("Invalid JSON string format.", nameof(jsonString), ex); } } } /// /// JArray로부터 DataArray를 생성하는 생성자 /// /// JSON 배열 public DataArray(JArray jArray) : base() { FromJArray(jArray); } public DataArray FromCapacity(int capacity) { Capacity = capacity; return this; } public DataArray FromCollection(IEnumerable collection) { if (collection == null) return this; foreach (var item in collection) { // base.Add를 사용하여 추적 로직을 우회하고 직접 추가합니다. base.Add(item); } return this; } /// /// JArray로부터 DataArray의 내용을 채웁니다. /// /// JSON 배열 public DataArray FromJArray(JArray jArray) { if (jArray == null) return this; foreach (var item in jArray) { // base.Add를 사용하여 추적 로직을 우회하고 직접 추가합니다. base.Add(ConvertToDataObject(item)); } return this; } /// /// JToken을 DataObject로 변환합니다. /// private DataObject ConvertToDataObject(JToken token) { if (token.Type == JTokenType.Object) { // 풀에서 객체를 가져와 JObject로 초기화합니다. var dataObject = DataObjectPool.Get(); dataObject.FromJObject((JObject)token); return dataObject; } else { // JObject가 아닌 경우, 풀에서 새 DataObject를 가져와 값을 넣어줍니다. var dataObject = DataObjectPool.Get(); dataObject.Add("value", ConvertJTokenToObject(token)); return dataObject; } } private object ConvertJTokenToObject(JToken token) { switch (token.Type) { case JTokenType.String: return token.ToString(); case JTokenType.Integer: return token.ToObject(); case JTokenType.Float: return token.ToObject(); case JTokenType.Boolean: return token.ToObject(); case JTokenType.Object: var dataObject = DataObjectPool.Get(); dataObject.FromJObject((JObject)token); return dataObject; case JTokenType.Array: JArray array = (JArray)token; var dataArray = DataArrayPool.Get(); dataArray.FromJArray(array); return dataArray; default: return token.ToString(); } } /// /// 모든 아이템이 추가 된것으로 표시합니다. /// 전체 데이터가 갱신되었을 때 사용합니다. /// public void MarkAllAsUpdated() { addedList.Clear(); addedList.AddRange(this); } /// /// 다른 DataObject와 현재 객체를 비교하여 다른 부분만 설정합니다. /// 변경된 키는 자동으로 추적됩니다. /// /// 비교할 DataObject public void UpdateDifferent(IDataObject other) { if (other == null) return; if (!(other is DataArray otherArray)) return; // 기존 변경 추적 목록을 초기화합니다. ClearTrackedChanges(); // 성능 향상을 위해 ID를 키로 사용하는 사전을 생성합니다. var thisDict = this.ToDictionary(item => item.Id); var otherDict = otherArray.ToDictionary(item => item.Id); var thisIds = new HashSet(thisDict.Keys); var otherIds = new HashSet(otherDict.Keys); // 제거된 항목 확인 (현재 배열에는 있지만 다른 배열에는 없는 항목) foreach (var id in thisIds.Where(id => !otherIds.Contains(id))) { removedList.Add(thisDict[id]); } // 추가된 항목 확인 (다른 배열에는 있지만 현재 배열에는 없는 항목) foreach (var id in otherIds.Where(id => !thisIds.Contains(id))) { addedList.Add(otherDict[id]); } // 수정된 항목 확인 (양쪽 모두에 있지만 내용이 다른 항목) foreach (var id in thisIds.Where(id => otherIds.Contains(id))) { var thisItem = thisDict[id]; var otherItem = otherDict[id]; // [성능 개선] ToString() 비교는 매우 비효율적입니다. // DataObject.UpdateDifferent를 직접 호출하여 변경 사항을 적용하고, // UpdatedCount를 통해 실제 변경 여부를 확인합니다. thisItem.UpdateDifferent(otherItem); if (thisItem.UpdatedCount > 0) { modifiedList.Add(thisItem); } } // [성능 개선] RemoveAll과 HashSet을 사용하여 제거 작업의 효율성을 높입니다. if (removedList.Count > 0) { var removedItemIds = new HashSet(removedList.Select(i => i.Id)); this.RemoveAll(item => removedItemIds.Contains(item.Id)); } // [성능 개선] base.AddRange를 사용하여 추적 로직을 우회하고 효율적으로 추가합니다. if (addedList.Count > 0) { base.AddRange(addedList); } } /// /// 업데이트 된 객체를 반환합니다. /// /// public IDataObject GetUpdatedObject() { // 풀에서 새 DataArray 인스턴스를 가져옵니다. var clone = DataArrayPool.Get(); clone.FromCapacity(this.Count); // 배열의 모든 DataObject를 순회하며 각각을 복제합니다. foreach (var item in this) { // DataObject의 Clone 메서드를 호출하여 깊은 복사를 수행하고, // base.Add를 사용해 추적 로직 없이 직접 추가합니다. if (item.GetUpdatedObject() is DataObject updatedObject) { clone.Add(updatedObject); if (addedList.Contains(item)) { clone.addedList.Add(updatedObject); } else if (removedList.Contains(item)) { clone.removedList.Add(updatedObject); } else if (modifiedList.Contains(item)) { clone.modifiedList.Add(updatedObject); } } } return clone; } /// /// 업데이트 된 속성의 수. /// /// 업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다. public int UpdatedCount { get => addedList.Count + modifiedList.Count + removedList.Count; } /// /// 컬렉션의 모든 항목을 제거합니다. /// public new void Clear() { if (Count > 0) { ReturnToDataObjectPool(); base.Clear(); } } /// /// 객체를 풀에 반환하기 전에 초기 상태로 리셋합니다. /// public void Reset() { // 포함된 DataObject들을 먼저 풀에 반환합니다. ReturnToDataObjectPool(); // 내부 리스트와 변경 추적 리스트를 모두 비웁니다. base.Clear(); ClearTrackedChanges(); } public void ReturnToPool() { DataArrayPool.Return(this); } /// /// 이 DataArray에 포함된 모든 DataObject를 풀에 반환하고 리스트를 비웁니다. /// DataArray의 사용이 끝났을 때 호출해야 합니다. /// private void ReturnToDataObjectPool() { foreach (var item in this) { DataObjectPool.Return(item); } } /// /// 변경된 인덱스 목록을 초기화합니다. /// public void ClearTrackedChanges() { addedList.Clear(); removedList.Clear(); modifiedList.Clear(); } /// /// 동일한 상태와 값을 가진 현재 데이터 객체의 새 인스턴스를 생성합니다. /// /// 복제된 객체는 원본 객체와 독립적이므로, 한 객체를 변경해도 다른 객체에는 영향을 미치지 않습니다. /// /// 현재 객체의 복사본인 새 인스턴스를 반환합니다. public IDataObject Clone() { // 풀에서 새 DataArray 인스턴스를 가져옵니다. var clone = DataArrayPool.Get(); clone.FromCapacity(this.Count); // 배열의 모든 DataObject를 순회하며 각각을 복제합니다. foreach (var item in this) { // DataObject의 Clone 메서드를 호출하여 깊은 복사를 수행하고, // base.Add를 사용해 추적 로직 없이 직접 추가합니다. if (item.Clone() is DataObject clonedItem) { clone.Add(clonedItem); if (addedList.Contains(item)) { clone.addedList.Add(clonedItem); } else if (removedList.Contains(item)) { clone.removedList.Add(clonedItem); } else if (modifiedList.Contains(item)) { clone.modifiedList.Add(clonedItem); } } } return clone; } /// /// DataArray를 JArray로 변환합니다. /// public JArray ToJArray() { JArray array = new JArray(); foreach (var item in this) { array.Add(item); } return array; } /// /// DataArray를 JArray로 암시적 변환합니다. /// public static implicit operator JArray(DataArray dataArray) { return dataArray.ToJArray(); } /// /// JArray를 DataArray로 암시적 변환합니다. /// public static implicit operator DataArray(JArray jArray) { var dataArray = DataArrayPool.Get(); dataArray.FromJArray(jArray); return dataArray; } } }