using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace UVC.Data.Core { /// /// DataObject 객체 컬렉션의 변경사항을 추적하는 데이터 배열 클래스 /// public class DataArray : List, IDataObject { private bool isInPool = false; /// /// 이 객체가 객체 풀에 있는지 여부를 나타냅니다. /// 중복 반환을 방지하기 위해 DataArrayPool에서 내부적으로 사용됩니다. /// internal bool IsInPool { get => isInPool; set { isInPool = value; foreach (var item in this) { item.IsInPool = value; // 내부 DataObject도 풀에 있다고 표시합니다. } } } private bool createdFromPool = false; /// /// 객체가 풀에서 생성되었는지 여부를 나타냅니다. /// internal bool CreatedFromPool { get => createdFromPool; set { createdFromPool = value; foreach (var item in this) { item.CreatedFromPool = value; // 내부 DataObject도 풀에서 생성되었음을 표시합니다. } } } // 추가 된 항목 목록 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); } } } public DataArray(System.IO.Stream jsonStream) { if (jsonStream == null) throw new ArgumentNullException(nameof(jsonStream)); // 스트림 처리 최적화를 위해 청크 단위로 읽을 수 있지만, // 현재는 Newtonsoft.Json의 기본 역직렬화 사용 using (var reader = new Newtonsoft.Json.JsonTextReader(new System.IO.StreamReader(jsonStream))) { // 청크 읽기 설정 - 메모리 사용량 최적화 reader.SupportMultipleContent = true; var serializer = new Newtonsoft.Json.JsonSerializer(); var sourceObject = serializer.Deserialize(reader); // 수정된 코드: 생성자를 호출하는 대신 JArray 메서드를 사용 if (sourceObject != null) FromJArray(sourceObject); } } /// /// JArray로부터 DataArray를 생성하는 생성자 /// /// JSON 배열 public DataArray(JArray jArray) : base() { FromJArray(jArray); } /// /// 배열의 JSON 문자열 표현을 사용하여 현재 인스턴스를 채웁니다. /// /// 이 메서드는 제공된 JSON 문자열을 구문 분석하고 각 요소를 데이터 객체로 변환한 다음 /// 현재 에 추가합니다. 이 null이거나 /// 비어 있는 경우, 메서드는 수정 없이 현재 인스턴스를 반환합니다. /// 구문 분석하여 데이터 객체로 변환할 JSON 문자열입니다. 유효한 JSON 배열을 나타내야 합니다. /// JSON 문자열에서 파싱된 데이터 객체로 채워진 현재 인스턴스입니다. /// 이 유효한 JSON 배열 형식이 아닌 경우 발생합니다. public DataArray FromJsonString(string jsonString) { 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); } } return this; } 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(); 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].Copy(fromPool: false)); } // 수정된 항목 확인 (양쪽 모두에 있지만 내용이 다른 항목) 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) { // 제거될 객체들을 먼저 풀에 반환합니다. foreach (var item in removedList) { if (item.IsInPool) item.ReturnToPool(); } // 그 다음 리스트에서 제거합니다. 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(bool fromPool = true) { // 풀에서 새 DataArray 인스턴스를 가져옵니다. var clone = fromPool ? DataArrayPool.Get() : new DataArray(); clone.FromCapacity(this.Count); // 배열의 모든 DataObject를 순회하며 각각을 복제합니다. foreach (var item in this) { // DataObject의 Clone 메서드를 호출하여 깊은 복사를 수행하고, // base.Add를 사용해 추적 로직 없이 직접 추가합니다. if (item.GetUpdatedObject(fromPool) 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() { // 내부 리스트와 변경 추적 리스트를 모두 비웁니다. ReturnToDataObjectPool(); base.Clear(); ClearTrackedChanges(); } /// /// 객체를 풀에 반환하기 전에 초기 상태로 리셋합니다. /// public void Reset() { Clear(); } public void ReturnToPool() { if (CreatedFromPool) { Reset(); } else { // 포함된 DataObject들을 먼저 풀에 반환합니다. DataArrayPool.Return(this); } } /// /// 이 DataArray에 포함된 모든 DataObject를 풀에 반환하고 리스트를 비웁니다. /// DataArray의 사용이 끝났을 때 호출해야 합니다. /// private void ReturnToDataObjectPool() { foreach (var item in this) { item.ReturnToPool(); } } /// /// 변경된 인덱스 목록을 초기화합니다. /// public void ClearTrackedChanges() { addedList.Clear(); removedList.Clear(); modifiedList.Clear(); } /// /// 동일한 상태와 값을 가진 현재 데이터 객체의 새 인스턴스를 생성합니다. /// /// 복제된 객체는 원본 객체와 독립적이므로, 한 객체를 변경해도 다른 객체에는 영향을 미치지 않습니다. /// /// 객체 풀에서 복제할지 여부를 지정합니다. 기본값은 true입니다. /// 현재 객체의 복사본인 새 인스턴스를 반환합니다. public IDataObject Clone(bool fromPool = true) { return Copy(fromPool); } /// /// 현재 인스턴스의 요소 및 관련 상태를 포함한 깊은 복사본을 생성합니다. /// /// /// 이 메서드는 현재 배열에 있는 모든 요소의 깊은 복사본을 포함하는 새로운 인스턴스를 반환합니다. /// 복사된 인스턴스는 내부 목록에서 추적하는 추가, 제거 또는 수정된 요소를 포함하여 원본 인스턴스의 상태도 복제합니다. /// /// 객체 풀에서 복제할지 여부를 지정합니다. 기본값은 true입니다. /// 현재 인스턴스의 깊은 복사본인 새로운 인스턴스를 반환합니다. public DataArray Copy(bool fromPool = true) { // 풀에서 새 DataArray 인스턴스를 가져옵니다. DataArray clone; if (fromPool) clone = DataArrayPool.Get(); else clone = new DataArray(); // 배열의 모든 DataObject를 순회하며 각각을 복제합니다. foreach (var item in this) { // DataObject의 Clone 메서드를 호출하여 깊은 복사를 수행하고, // base.Add를 사용해 추적 로직 없이 직접 추가합니다. DataObject clonedItem = item.Copy(fromPool); 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; } } }