#nullable enable using System; using System.Collections.Generic; namespace UVC.UIToolkit { /// /// UIToolkit 메뉴 시스템의 데이터 모델입니다. /// 메뉴 아이템 컬렉션을 관리하고 검색 기능을 제공합니다. /// /// /// 이 클래스는 메뉴 아이템을 효율적으로 관리하기 위해 Dictionary 캐싱을 사용합니다. /// 검색 시간 복잡도: O(1) (Dictionary 사용) /// /// /// /// // 모델 생성 /// var model = new UTKTopMenuModel(); /// /// // 메뉴 아이템 추가 /// var fileMenu = new UTKMenuItemData("file", "menu_file"); /// model.AddMenuItem(fileMenu); /// /// // 메뉴 아이템 검색 /// var found = model.FindMenuItem("file"); /// /// // 메뉴 아이템 제거 /// model.RemoveMenuItem("file"); /// /// // 모든 메뉴 정리 /// model.ClearMenuItems(); /// /// // 사용 후 정리 /// model.Dispose(); /// /// public class UTKTopMenuModel : IDisposable { #region Fields /// 빠른 검색을 위한 메뉴 아이템 인덱스 (ItemId -> MenuItemData) private readonly Dictionary _menuItemIndex; #endregion #region Properties /// 최상위 메뉴 아이템 리스트 public List MenuItems { get; private set; } #endregion #region Constructor /// /// UTKTopMenuModel의 새 인스턴스를 초기화합니다. /// public UTKTopMenuModel() { MenuItems = new List(); _menuItemIndex = new Dictionary(StringComparer.Ordinal); } #endregion #region Methods /// /// 메뉴 아이템을 추가합니다. /// /// 추가할 메뉴 아이템 /// item이 null인 경우 /// 이미 정리된 객체에 접근하는 경우 public void AddMenuItem(UTKMenuItemData item) { if (_disposed) throw new ObjectDisposedException(nameof(UTKTopMenuModel), "이미 정리된 모델에 메뉴 아이템을 추가할 수 없습니다."); if (item == null) throw new ArgumentNullException(nameof(item), "추가할 메뉴 아이템이 null입니다."); MenuItems.Add(item); // 인덱스에 추가 (재귀적으로 하위 메뉴도 인덱싱) AddToIndex(item); } /// /// 메뉴 아이템을 제거합니다. /// /// 제거할 메뉴 아이템의 ID /// 제거 성공 시 true, 그렇지 않으면 false /// 이미 정리된 객체에 접근하는 경우 /// /// 시간 복잡도: O(n) (n = MenuItems.Count) /// 하위 메뉴도 함께 제거되며, 제거된 아이템은 Dispose됩니다. /// public bool RemoveMenuItem(string itemId) { if (_disposed) throw new ObjectDisposedException(nameof(UTKTopMenuModel), "이미 정리된 모델에서 메뉴 아이템을 제거할 수 없습니다."); if (string.IsNullOrEmpty(itemId)) return false; // 최상위 메뉴에서 제거 시도 for (int i = 0; i < MenuItems.Count; i++) { if (string.Equals(MenuItems[i].ItemId, itemId, StringComparison.Ordinal)) { var item = MenuItems[i]; MenuItems.RemoveAt(i); // 인덱스에서 제거 (재귀적으로 하위 메뉴도 제거) RemoveFromIndex(item); // 메모리 정리 item.Dispose(); return true; } } // 최상위에서 찾지 못했으면 재귀적으로 하위 메뉴에서 검색 return RemoveMenuItemRecursive(MenuItems, itemId); } /// /// 재귀적으로 메뉴 아이템을 검색합니다. /// /// 검색할 메뉴 아이템의 ID /// 찾은 메뉴 아이템, 없으면 null /// 이미 정리된 객체에 접근하는 경우 /// /// 시간 복잡도: O(1) (Dictionary 캐싱 사용) /// public UTKMenuItemData? FindMenuItem(string itemId) { if (_disposed) throw new ObjectDisposedException(nameof(UTKTopMenuModel), "이미 정리된 모델에서 메뉴 아이템을 검색할 수 없습니다."); if (string.IsNullOrEmpty(itemId)) return null; // Dictionary 캐싱으로 O(1) 검색 return _menuItemIndex.TryGetValue(itemId, out var item) ? item : null; } /// /// 모든 메뉴 아이템을 초기화합니다. /// /// 이미 정리된 객체에 접근하는 경우 /// /// 모든 메뉴 아이템을 재귀적으로 Dispose합니다. /// public void ClearMenuItems() { if (_disposed) throw new ObjectDisposedException(nameof(UTKTopMenuModel), "이미 정리된 모델의 메뉴 아이템을 초기화할 수 없습니다."); // 모든 메뉴 아이템 정리 foreach (var item in MenuItems) { item?.Dispose(); } MenuItems.Clear(); _menuItemIndex.Clear(); } #endregion #region Private Methods /// /// 메뉴 아이템과 그 하위 메뉴들을 인덱스에 추가합니다. /// /// 인덱싱할 메뉴 아이템 private void AddToIndex(UTKMenuItemData item) { if (item == null) return; // 중복 체크 (성능 최적화: ContainsKey 대신 TryAdd 사용) _menuItemIndex[item.ItemId] = item; // 하위 메뉴 재귀적 인덱싱 if (item.SubMenuItems != null) { foreach (var subItem in item.SubMenuItems) { AddToIndex(subItem); } } } /// /// 메뉴 아이템과 그 하위 메뉴들을 인덱스에서 제거합니다. /// /// 제거할 메뉴 아이템 private void RemoveFromIndex(UTKMenuItemData item) { if (item == null) return; _menuItemIndex.Remove(item.ItemId); // 하위 메뉴 재귀적 제거 if (item.SubMenuItems != null) { foreach (var subItem in item.SubMenuItems) { RemoveFromIndex(subItem); } } } /// /// 재귀적으로 메뉴 아이템을 제거합니다. /// /// 검색할 메뉴 아이템 리스트 /// 제거할 메뉴 아이템 ID /// 제거 성공 시 true, 그렇지 않으면 false private bool RemoveMenuItemRecursive(List items, string itemId) { if (items == null || string.IsNullOrEmpty(itemId)) return false; foreach (var item in items) { if (item.SubMenuItems != null && item.SubMenuItems.Count > 0) { // 하위 메뉴에서 제거 시도 for (int i = 0; i < item.SubMenuItems.Count; i++) { if (string.Equals(item.SubMenuItems[i].ItemId, itemId, StringComparison.Ordinal)) { var subItem = item.SubMenuItems[i]; item.SubMenuItems.RemoveAt(i); // 인덱스에서 제거 RemoveFromIndex(subItem); // 메모리 정리 subItem.Dispose(); return true; } } // 더 깊은 하위 메뉴에서 재귀 검색 if (RemoveMenuItemRecursive(item.SubMenuItems, itemId)) return true; } } return false; } #endregion #region IDisposable private bool _disposed; /// /// 모든 메뉴 아이템을 정리합니다. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// 리소스를 정리합니다. /// /// 관리되는 리소스를 정리할지 여부 protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // 모든 메뉴 아이템 재귀적으로 정리 if (MenuItems != null) { foreach (var item in MenuItems) { item?.Dispose(); } MenuItems.Clear(); } // 인덱스 정리 _menuItemIndex?.Clear(); } _disposed = true; } /// /// 소멸자 /// ~UTKTopMenuModel() { Dispose(false); } #endregion } }