Files
XRLib/Assets/Scripts/UVC/Data/DataArray.cs

406 lines
13 KiB
C#

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace UVC.Data
{
/// <summary>
/// DataObject 객체 컬렉션의 변경사항을 추적하는 데이터 배열 클래스
/// </summary>
public class DataArray : List<DataObject>, IDataObject
{
// 추가 된 항목 목록
protected List<DataObject> addedList = new List<DataObject>();
// 제거 된 항목 목록
protected List<DataObject> removedList = new List<DataObject>();
// 수정 된 항목 목록
protected List<DataObject> modifiedList = new List<DataObject>();
// 추가된 항목에 접근할 수 있는 읽기 전용 컬렉션
public ReadOnlyCollection<DataObject> AddedItems => addedList.AsReadOnly();
// 제거된 항목에 접근할 수 있는 읽기 전용 컬렉션
public ReadOnlyCollection<DataObject> RemovedItems => removedList.AsReadOnly();
// 제거된 항목에 접근할 수 있는 읽기 전용 컬렉션
public ReadOnlyCollection<DataObject> ModifiedList => modifiedList.AsReadOnly();
/// <summary>
/// 변경 추적 활성화 여부
/// </summary>
public bool IsChangeTracking { get; set; } = false;
/// <summary>
/// 기본 생성자
/// </summary>
public DataArray() : base()
{
}
/// <summary>
/// 초기 용량을 지정하는 생성자
/// </summary>
/// <param name="capacity">초기 용량</param>
public DataArray(int capacity) : base(capacity)
{
}
/// <summary>
/// 기존 컬렉션으로부터 생성하는 생성자
/// </summary>
/// <param name="collection">초기 항목을 포함하는 컬렉션</param>
public DataArray(IEnumerable<DataObject> 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);
}
}
}
/// <summary>
/// JArray로부터 DataArray를 생성하는 생성자
/// </summary>
/// <param name="jArray">JSON 배열</param>
public DataArray(JArray jArray) : base()
{
if (jArray != null)
{
foreach (var item in jArray)
{
Add(ConvertToDataObject(item));
}
}
}
/// <summary>
/// JToken을 DataObject로 변환합니다.
/// </summary>
private DataObject ConvertToDataObject(JToken token)
{
if (token.Type == JTokenType.Object)
{
return new DataObject((JObject)token);
}
else
{
// JObject가 아닌 경우, 새 DataObject를 만들고 값을 넣어줍니다
var dataObject = new DataObject();
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<int>();
case JTokenType.Float:
return token.ToObject<float>();
case JTokenType.Boolean:
return token.ToObject<bool>();
case JTokenType.Object:
return new DataObject((JObject)token);
case JTokenType.Array:
JArray array = (JArray)token;
return new DataArray(array);
default:
return token.ToString();
}
}
/// <summary>
/// 모든 아이템이 추가 된것으로 표시합니다.
/// 전체 데이터가 갱신되었을 때 사용합니다.
/// </summary>
public void InitData()
{
addedList.Clear();
addedList.AddRange(this);
}
/// <summary>
/// 다른 DataObject와 현재 객체를 비교하여 다른 부분만 설정합니다.
/// 변경된 키는 자동으로 추적됩니다.
/// </summary>
/// <param name="other">비교할 DataObject</param>
public void UpdateDifferent(IDataObject other)
{
if (other == null) return;
if (other is DataArray otherArray)
{
addedList.Clear();
removedList.Clear();
modifiedList.Clear();
// Id 기준으로 객체들을 비교하기 위한 사전 생성
var thisDict = this.ToDictionary(item => item.Id, item => item);
var otherDict = otherArray.ToDictionary(item => item.Id, item => item);
// 제거된 항목 확인 (현재 배열에는 있지만 other에는 없는 항목)
foreach (var id in thisDict.Keys.Where(id => !otherDict.ContainsKey(id)))
{
removedList.Add(thisDict[id]);
}
// 추가된 항목 확인 (other에는 있지만 현재 배열에는 없는 항목)
foreach (var id in otherDict.Keys.Where(id => !thisDict.ContainsKey(id)))
{
addedList.Add(otherDict[id]);
}
// 수정된 항목 확인 (양쪽 모두에 있지만 내용이 다른 항목)
foreach (var id in thisDict.Keys.Where(id => otherDict.ContainsKey(id)))
{
var thisItem = thisDict[id];
var otherItem = otherDict[id];
if (!thisItem.ToString().Equals(otherItem.ToString()))
{
modifiedList.Add(thisItem);
thisItem.UpdateDifferent(otherItem);
}
}
// 실제 컬렉션 업데이트
// 현재 배열에서 제거된 항목들을 제거
for (int i = this.Count - 1; i >= 0; i--)
{
if (removedList.Contains(this[i]))
{
this.RemoveAt(i);
}
}
// 추가된 항목들을 현재 배열에 추가
foreach (var item in addedList)
{
this.Add(item);
}
}
}
/// <summary>
/// 업데이트 된 객체를 반환합니다.
/// </summary>
/// <returns></returns>
public IDataObject GetUpdatedObject()
{
return this;
}
/// <summary>
/// 업데이트 된 속성의 수.
/// </summary>
/// <returns>업데이트된 속성의 총 개수입니다. 업데이트된 속성이 없으면 0을 반환합니다.</returns>
public int UpdatedCount { get => addedList.Count + modifiedList.Count + removedList.Count; }
/// <summary>
/// 항목을 추가합니다.
/// </summary>
/// <param name="item">추가할 항목</param>
public new void Add(DataObject item)
{
base.Add(item);
// 변경 사항을 추적하고 이벤트 발생
if (IsChangeTracking)
{
int index = Count - 1;
addedList.Add(item);
}
}
/// <summary>
/// 항목을 제거합니다.
/// </summary>
/// <param name="item">제거할 항목</param>
/// <returns>제거 성공 여부</returns>
public new bool Remove(DataObject item)
{
int index = IndexOf(item);
if (index >= 0)
{
RemoveAt(index);
return true;
}
return false;
}
/// <summary>
/// 지정된 인덱스의 항목을 제거합니다.
/// </summary>
/// <param name="index">제거할 항목의 인덱스</param>
public new void RemoveAt(int index)
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
DataObject removedItem = this[index];
base.RemoveAt(index);
if (IsChangeTracking)
{
removedList.Add(removedItem);
}
}
/// <summary>
/// 컬렉션의 모든 항목을 제거합니다.
/// </summary>
public new void Clear()
{
if (Count > 0)
{
var oldItems = new List<DataObject>(this);
base.Clear();
if (IsChangeTracking)
{
// 모든 항목을 제거 목록에 추가
removedList.AddRange(oldItems);
}
}
}
/// <summary>
/// 지정된 인덱스에 항목을 삽입합니다.
/// </summary>
/// <param name="index">삽입할 위치</param>
/// <param name="item">삽입할 항목</param>
public new void Insert(int index, DataObject item)
{
base.Insert(index, item);
if (IsChangeTracking)
{
addedList.Add(item);
}
}
/// <summary>
/// 지정된 인덱스의 항목을 교체합니다.
/// </summary>
/// <param name="index">교체할 위치</param>
/// <param name="item">새 항목</param>
public new DataObject this[int index]
{
get { return base[index]; }
set
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
DataObject oldItem = base[index];
base[index] = value;
if (IsChangeTracking)
{
addedList.Add(value);
removedList.Add(oldItem);
}
}
}
/// <summary>
/// 범위의 항목을 추가합니다.
/// </summary>
/// <param name="collection">추가할 항목의 컬렉션</param>
public new void AddRange(IEnumerable<DataObject> collection)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
int startIndex = Count;
base.AddRange(collection);
if (IsChangeTracking)
{
var items = collection.ToList();
for (int i = 0; i < items.Count; i++)
{
addedList.Add(items[i]);
}
}
}
/// <summary>
/// 변경된 인덱스 목록을 초기화합니다.
/// </summary>
public void ResetChangedIndices()
{
addedList.Clear();
removedList.Clear();
modifiedList.Clear();
}
/// <summary>
/// 변경 내역을 저장하지 않고 항목을 추가합니다.
/// </summary>
/// <param name="item">추가할 항목</param>
public void AddWithoutTracking(DataObject item)
{
bool oldTracking = IsChangeTracking;
IsChangeTracking = false;
Add(item);
IsChangeTracking = oldTracking;
}
/// <summary>
/// DataArray를 JArray로 변환합니다.
/// </summary>
public JArray ToJArray()
{
JArray array = new JArray();
foreach (var item in this)
{
array.Add(item);
}
return array;
}
/// <summary>
/// DataArray를 JArray로 암시적 변환합니다.
/// </summary>
public static implicit operator JArray(DataArray dataArray)
{
return dataArray.ToJArray();
}
/// <summary>
/// JArray를 DataArray로 암시적 변환합니다.
/// </summary>
public static implicit operator DataArray(JArray jArray)
{
return new DataArray(jArray);
}
}
}