GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
GPALGrid.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections;
19using System.Collections.Generic;
20using System.Linq;
21using System.Text;
22using System.Threading.Tasks;
23using static GenerallyPositive.Enums;
24
25namespace GenerallyPositive
26{
54 internal class GPALGrid<T> : IGPALGrid<T>
55 {
56 private List<List<T>> data;
57 // rows arrive from whatever thread ran the workflow that made them, and GPAL.Workflow runs several
58 // of those at once, so adding a row is one step rather than a read of Count and a write after it
59 private readonly object rowLock = new object();
60
66 internal bool UseDedupe { get; private set; } = false;
67
73 public IGPALGrid<T> WithDedupeData(bool useDedupe = true)
74 {
75 UseDedupe = useDedupe;
76 return this;
77 }
78
82 internal GPALGrid()
83 {
84 data = new List<List<T>>();
85 }
86
109 public IAllowGridActions<T> AddRow(List<T> row)
110 {
111 if (row == null)
112 {
113 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Row is null in AddRow(List<T>). Skipping addition.", this, GPALObjectType.Other);
114 return this;
115 }
116
117 lock (rowLock)
118 data.Add(row);
119
120 return this;
121 }
135 internal void Add(List<T> row)
136 {
137 if (row == null)
138 {
139 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Row is null in AddRow(List<T>). Skipping addition.", this, GPALObjectType.Other);
140 }
141
142 data.Add(row);
143 }
144
145 public IAllowGridActions<T> Add(IGPALGrid<T> grid)
146 {
147 foreach (List<T> row in grid)
148 data.Add(row);
149
150 return this;
151 }
180 public IAllowGridActions<T> AddRow(object anonymousObject)
181 {
182 if (anonymousObject == null)
183 {
184 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Anonymous object is null in AddRow(object). Skipping addition.", this, GPALObjectType.Other);
185 return this;
186 }
187
188 // Convert anonymous object properties to a list of values
189 var values = anonymousObject.GetType()
190 .GetProperties()
191 .Select(prop => prop.GetValue(anonymousObject, null))
192 .ToList();
193
194 // Validate column count if the grid already has rows
195 lock (rowLock)
196 {
197 int expectedColumns = Columns;
198 if (data.Count > 0 && values.Count != expectedColumns)
199 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Anonymous object has [{values.Count}] values, but expected [{expectedColumns}]. Adding row as-is.", this, GPALObjectType.Other);
200 }
201
202 // Convert each value to type T
203 var row = new List<T>();
204 foreach (var value in values)
205 {
206 try
207 {
208 if (value == null)
209 {
210 row.Add(default(T));
211 continue;
212 }
213 row.Add((T)Convert.ChangeType(value, typeof(T)));
214 }
215 catch (Exception ex)
216 {
217 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"Cannot convert value [{value}] to type [{typeof(T).Name}]. Using default value", this, GPALObjectType.Other, ex);
218 row.Add(default(T));
219 }
220 }
221
222 lock (rowLock)
223 data.Add(row);
224
225 return this;
226 }
227
255 public IAllowGridActions<T> AddRow(params T[] values)
256 {
257 if (values == null)
258 {
259 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Values array is null in AddRow(params T[]). Skipping addition.", this, GPALObjectType.Other);
260 return this;
261 }
262
263 // Validate column count if the grid already has rows, and add while still holding the gate
264 lock (rowLock)
265 {
266 int expectedColumns = Columns;
267 if (data.Count > 0 && values.Length != expectedColumns)
268 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Params array has [{values.Length}] values, but expected [{expectedColumns}]. Adding row as-is.", this, GPALObjectType.Other);
269
270 data.Add(new List<T>(values));
271 }
272 return this;
273 }
274
298 public IAllowGridActions<T> DeleteRow(int rowIdx)
299 {
300 if (rowIdx >= 0 && rowIdx < data.Count)
301 {
302 data.RemoveAt(rowIdx);
303 }
304 else
305 {
306 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Invalid row index [{rowIdx}] in DeleteRow(int). Row count is [{data.Count}]. Skipping deletion.", this, GPALObjectType.Other);
307 }
308 return this;
309 }
310
334 public IAllowGridActions<T> DeleteRow(RowPosition rowPosition = RowPosition.Last)
335 {
336 int rowIdx;
337
338 if (data.Count == 0)
339 {
340 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Grid is empty in DeleteRow(RowPosition). Skipping deletion.", this, GPALObjectType.Other);
341 return this;
342 }
343
344 if (rowPosition == RowPosition.First)
345 {
346 rowIdx = 0;
347 }
348 else // position == RowPosition.Last
349 {
350 rowIdx = data.Count - 1;
351 }
352
353 data.RemoveAt(rowIdx);
354 return this;
355 }
356
383 public IAllowGridActions<T> AddColumn(List<T> column)
384 {
385 if (column == null)
386 {
387 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Column is null in AddColumn. Skipping addition.", this, GPALObjectType.Other);
388 return this;
389 }
390
391 for (int i = 0; i < column.Count; i++)
392 {
393 if (data.Count <= i)
394 {
395 data.Add(new List<T>());
396 }
397 data[i].Add(column[i]);
398 }
399 return this;
400 }
401
405 public int Rows => data.Count;
406
411 public int Columns
412 {
413 get
414 {
415 int count = 0;
416
417 foreach (var row in data)
418 count = Math.Max(count, row.Count);
419
420 return count;
421 }
422 }
423
448 public IAllowGridActions<T> AddCell(T value, int row, int column)
449 {
450 if (row >= 0 && row < Rows && column >= 0 && column < Columns)
451 {
452 data[row][column] = value;
453 }
454 else
455 {
456 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Invalid cell coordinates ([{row}], [{column}]) in AddCell. Grid size is [{Rows}]x[{Columns}]. Skipping operation.", this, GPALObjectType.Other);
457 }
458 return this;
459 }
460
478 public T GetCell(int row, int column)
479 {
480 if (row >= 0 && row < Rows && column >= 0 && column < Columns)
481 {
482 return data[row][column];
483 }
484
485 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Invalid cell coordinates ([{row}], [{column}]) in GetCell. Grid size is [{Rows}]x[{Columns}]. Returning default value.", this, GPALObjectType.Other);
486 return default(T);
487 }
488
503 public IAllowGridActions<T> Clear()
504 {
505 data = new List<List<T>>();
506 return this;
507 }
508
530 public List<T> this[int index]
531 {
532 get
533 {
534 if (index >= data.Count)
535 {
536 for (int idx = index; idx >= data.Count; idx--)
537 data.Add(new List<T>());
538 }
539
540 return data[index];
541 }
542 set
543 {
544 if (index >= data.Count)
545 {
546 for (int idx = index; idx >= data.Count; idx--)
547 data.Add(new List<T>());
548 }
549
550 if (value == null)
551 {
552 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Value is null in indexer set at index [{index}]. Setting to empty list.", this, GPALObjectType.Other);
553 data[index] = new List<T>();
554 return;
555 }
556
557 data[index] = value;
558 }
559 }
560
582 public T this[int row, int column]
583 {
584 get { return GetCell(row, column); }
585 set { AddCell(value, row, column); }
586 }
587
592 public IGPALGrid<T> ToGPALObject()
593 {
594 return this;
595 }
596
613 public bool Contains(T value)
614 {
615 if (value == null)
616 {
617 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Value is null in Contains. Returning false.", this, GPALObjectType.Other);
618 return false;
619 }
620
621 string valueString = value.ToString();
622 foreach (List<T> list in data)
623 foreach (T item in list)
624 if (item != null && item.ToString().Contains(valueString))
625 return true;
626 return false;
627 }
628
634 public int Count() => data.Count();
635
640 public IEnumerator<List<T>> GetEnumerator()
641 {
642 return data.GetEnumerator();
643 }
644
649 IEnumerator IEnumerable.GetEnumerator()
650 {
651 return GetEnumerator();
652 }
653
673 public object Clone()
674 {
675 GPALGrid<T> clone = new GPALGrid<T>();
676
677 foreach (List<T> row in data)
678 {
679 List<T> clonedRow = new List<T>();
680 foreach (T item in row)
681 {
682 if (item is ICloneable cloneable)
683 {
684 clonedRow.Add((T)cloneable.Clone());
685 }
686 else
687 {
688 clonedRow.Add(item);
689 }
690 }
691 clone.AddRow(clonedRow);
692 }
693
694 return clone;
695 }
696
703 public IAllowGridActions<T> InputFrom(GPALFile input)
704 {
705 FileHelper.TokenizeFile(input); // CAVEAT: NOTE: could tokenize multiple files? if wildcard? but we only grab top list
706 data = (List<List<T>>)((IGPALFileInternal)input).TokenList[0].Clone();
707 return this;
708 }
709
715 public IAllowGridActions<T> SaveTo(GPALFile output)
716 {
717 GPAL.Converter.WithInput(this).SaveTo(output);
718 return this;
719 }
720
727 public IAllowGridActions<T> InputFrom(IGPALDatabase input)
728 {
729 throw new NotImplementedException();
730 }
731
738 public IAllowGridActions<T> SaveTo(IGPALDatabase output)
739 {
740 throw new NotImplementedException();
741 }
742
747 public List<T> ToList()
748 {
749 List<T> retVal = new List<T>();
750
751 foreach (List<T> row in data)
752 foreach (T col in row)
753 retVal.Add(col);
754
755 return retVal;
756 }
757 }
758}