GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
ObjectCopier.bad.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.Generic;
19using System.Linq;
20using System.Text;
21using System.Threading.Tasks;
22using System.Reflection;
23using System.Collections;
24using static GenerallyPositive.Enums;
25using System.Runtime.CompilerServices;
26
27namespace GenerallyPositive
28{
29 public class ReferenceEqualityComparer : IEqualityComparer<object>
30 {
31 public new bool Equals(object x, object y) => ReferenceEquals(x, y);
32 public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
33 }
34
38 internal static class ObjectCopier
39 {
40 public static T DeepCopyObject<T>(dynamic source, T destination, DeepCopy deepCopy, HashSet<object> visited = null)
41 {
42 if (source == null || destination == null)
43 {
44 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Source and/or destination must not be null. Returning whichever is not null or default(T).", null, GPALObjectType.None);
45 return null != source ? source : null != destination ? destination : default(T);
46 }
47
48 Type sourceType = source.GetType();
49
50 // Handle dictionaries
51 if (typeof(IDictionary).IsAssignableFrom(sourceType))
52 {
53 if (!typeof(IDictionary).IsAssignableFrom(destination.GetType()))
54 {
55 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Destination [{destination.GetType()}] is not a compatible [{sourceType}] dictionary type.", source, GPALObjectType.None);
56 return destination;
57 }
58 CopyDictionary((IDictionary)source, (IDictionary)destination, deepCopy);
59 }
60 // Handle enumerables (lists, arrays, etc.)
61 else if (typeof(IEnumerable).IsAssignableFrom(sourceType) && !IsImmutableType(sourceType))
62 {
63 if (!typeof(IList).IsAssignableFrom(destination.GetType()))
64 {
65 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Destination [{destination.GetType()}] is not a compatible list type.", source, GPALObjectType.None);
66 return destination;
67 }
68 CopyObjects((IEnumerable)source, (IList)destination, deepCopy);
69 }
70 // Handle single objects
71 else
72 {
73 if (DeepCopy.Fields == deepCopy || DeepCopy.FieldsAndProperties == deepCopy)
74 destination = CopyFields(source, destination, deepCopy, visited);
75 if (DeepCopy.Properties == deepCopy || DeepCopy.FieldsAndProperties == deepCopy)
76 destination = CopyProperties(source, destination, deepCopy, visited);
77 }
78
79 return destination;
80 }
81 static void CopyDictionary(IDictionary sourceDict, IDictionary destinationDict, DeepCopy deepCopy)
82 {
83 destinationDict.Clear(); // Clear the destination to avoid duplicate entries
84
85 foreach (var key in sourceDict.Keys)
86 {
87 // Sanitize dynamic key to prevent misinterpretation as culture identifier
88 object safeKey;
89 if (key is string strKey)
90 safeKey = strKey;
91 else if (key is int intKey)
92 safeKey = intKey;
93 else if (key is null)
94 safeKey = null;
95 else
96 {
97 // Log warning and use ToString as fallback to avoid invalid culture identifiers
98 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Unsupported dynamic key type [{key.GetType().Name}]. Using ToString as fallback.", key, GPALObjectType.None);
99 safeKey = key.ToString();
100 }
101
102 var sourceValue = sourceDict[key];
103 if (sourceValue == null)
104 {
105 destinationDict[safeKey] = null;
106 continue;
107 }
108
109 Type valueType = sourceValue.GetType();
110 object destinationValue;
111
112 // Skip copying if the type is internal or culture-related
113 if (IsSystemInternalType(valueType) || IsCultureRelatedType(valueType))
114 {
115 destinationValue = sourceValue; // Shallow copy to avoid issues
116 }
117 else
118 {
119 destinationValue = ConverterHelper.InstantiateOne(valueType);
120
121 // Recursively copy fields and/or properties only for non-immutable types
122 if (!IsImmutableType(valueType))
123 {
124 if (DeepCopy.Fields == deepCopy || DeepCopy.FieldsAndProperties == deepCopy)
125 destinationValue = CopyFields(sourceValue, destinationValue, deepCopy);
126 if (DeepCopy.Properties == deepCopy || DeepCopy.FieldsAndProperties == deepCopy)
127 destinationValue = CopyProperties(sourceValue, destinationValue, deepCopy);
128 }
129 else
130 {
131 destinationValue = sourceValue; // Shallow copy for immutable types
132 }
133 }
134
135 destinationDict[safeKey] = destinationValue;
136 }
137 }
138 static IList CopyObjects(IEnumerable sourceObjects, IList destinationObjects, DeepCopy deepCopy)
139 {
140 foreach (var source in sourceObjects)
141 {
142 var destination = ConverterHelper.InstantiateOne(source.GetType());
143 if (DeepCopy.Fields == deepCopy || DeepCopy.FieldsAndProperties == deepCopy)
144 destination = CopyFields(source, destination);
145 if (DeepCopy.Properties == deepCopy || DeepCopy.FieldsAndProperties == deepCopy)
146 destination = CopyProperties(source, destination);
147 destinationObjects.Add(destination);
148 }
149 return destinationObjects;
150 }
151 public static T CopyProperties<T>(object source, T destination, DeepCopy deepCopy = DeepCopy.FieldsAndProperties, HashSet<object> visited = null)
152 {
153 visited = visited ?? new HashSet<object>(new ReferenceEqualityComparer());
154 if (!visited.Add(source))
155 {
156 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Circular reference detected in CopyProperties @ [{source}].", source, GPALObjectType.None);
157 return destination;
158 }
159
160 Type sourceType = source.GetType();
161 Type destType = destination.GetType();
162 PropertyInfo[] properties = sourceType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
163
164 // Handle dictionary types
165 if (typeof(IDictionary).IsAssignableFrom(sourceType))
166 {
167 if (!typeof(IDictionary).IsAssignableFrom(destType))
168 {
169 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Destination [{destType}] is not a compatible [{sourceType}] dictionary type.", source, GPALObjectType.None);
170 return destination;
171 }
172 CopyDictionary((IDictionary)source, (IDictionary)destination, deepCopy);
173 }
174 // Handle specific types like IGPALGrid<string>
175 else if (source is IGPALGrid<string>)
176 {
177 destination = (T)sourceType.GetMethod("Clone")?.Invoke(source, null) ?? destination;
178 }
179 // Handle other types
180 else
181 {
182 foreach (PropertyInfo property in properties)
183 {
184 // Skip indexers, collection interface properties, and system-internal/culture-related properties
185 if (property.GetIndexParameters().Length > 0 || // Skip indexers
186 (property.DeclaringType != null && (typeof(ICollection).IsAssignableFrom(property.DeclaringType) ||
187 IsSystemInternalType(property.DeclaringType) ||
188 IsCultureRelatedType(property.DeclaringType))))
189 {
190 continue;
191 }
192
193 try
194 {
195 if (property.CanRead && property.CanWrite)
196 {
197 object sourceValue = property.GetValue(source);
198 if (sourceValue == null)
199 {
200 property.SetValue(destination, null);
201 continue;
202 }
203
204 Type propType = property.PropertyType;
205
206 // Handle enumerable properties (lists, arrays, sets, etc.)
207 if (typeof(IEnumerable).IsAssignableFrom(propType) && !IsImmutableType(propType))
208 {
209 object destEnumerable = ConverterHelper.InstantiateOne(propType);
210 if (destEnumerable == null)
211 {
212 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Failed to instantiate enumerable type [{propType.Name}].", source, GPALObjectType.None);
213 continue;
214 }
215
216 if (typeof(IDictionary).IsAssignableFrom(propType))
217 {
218 CopyDictionary((IDictionary)sourceValue, (IDictionary)destEnumerable, deepCopy);
219 }
220 else if (typeof(IList).IsAssignableFrom(propType))
221 {
222 CopyObjects((IEnumerable)sourceValue, (IList)destEnumerable, deepCopy);
223 }
224 else
225 {
226 // Handle other enumerables (e.g., HashSet<T>, Queue<T>)
227 CopyGenericEnumerable((IEnumerable)sourceValue, destEnumerable, propType, deepCopy, visited);
228 }
229
230 property.SetValue(destination, destEnumerable);
231 }
232 // Handle complex objects (non-value types, non-immutable, non-system)
233 else if (!propType.IsValueType && !IsImmutableType(propType) && !IsSystemInternalType(propType) && !IsCultureRelatedType(propType))
234 {
235 object destValue = ConverterHelper.InstantiateOne(propType);
236 var nestedVisited = new HashSet<object>(new ReferenceEqualityComparer());
237 destValue = DeepCopyObject(sourceValue, destValue, deepCopy, nestedVisited);
238 property.SetValue(destination, destValue);
239 }
240 // Handle value types, strings, immutable types, and system types
241 else
242 {
243 property.SetValue(destination, sourceValue); // Shallow copy
244 }
245 }
246 }
247 catch (Exception ex)
248 {
249 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to copy property [{property.Name}] of type [{sourceType.Name}].", source, GPALObjectType.Other, ex);
250 }
251 }
252 }
253 return destination;
254 }
255
256 public static T CopyFields<T>(object source, T destination, DeepCopy deepCopy = DeepCopy.FieldsAndProperties, HashSet<object> visited = null)
257 {
258 visited = visited ?? new HashSet<object>(new ReferenceEqualityComparer());
259 if (!visited.Add(source))
260 {
261 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Circular reference detected in CopyFields @ [{source}].", source, GPALObjectType.None);
262 return destination;
263 }
264
265 Type sourceType = source.GetType();
266 Type destType = destination.GetType();
267 FieldInfo[] fields = sourceType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
268
269 foreach (FieldInfo field in fields)
270 {
271 // Skip system-internal or culture-related fields
272 if (IsSystemInternalType(field.FieldType) || IsCultureRelatedType(field.FieldType))
273 {
274 continue;
275 }
276
277 try
278 {
279 object sourceValue = field.GetValue(source);
280 if (sourceValue == null)
281 {
282 field.SetValue(destination, null);
283 continue;
284 }
285
286 Type fieldType = field.FieldType;
287
288 // Handle enumerable fields (lists, arrays, dictionaries, etc.)
289 if (typeof(IEnumerable).IsAssignableFrom(fieldType) && !IsImmutableType(fieldType))
290 {
291 object destEnumerable = ConverterHelper.InstantiateOne(fieldType);
292 if (destEnumerable == null)
293 {
294 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Failed to instantiate enumerable type [{fieldType.Name}].", source, GPALObjectType.None);
295 continue;
296 }
297
298 if (typeof(IDictionary).IsAssignableFrom(fieldType))
299 {
300 CopyDictionary((IDictionary)sourceValue, (IDictionary)destEnumerable, deepCopy);
301 }
302 else if (typeof(IList).IsAssignableFrom(fieldType))
303 {
304 CopyObjects((IEnumerable)sourceValue, (IList)destEnumerable, deepCopy);
305 }
306 else
307 {
308 // Handle other enumerables (e.g., HashSet<T>, Queue<T>)
309 CopyGenericEnumerable((IEnumerable)sourceValue, destEnumerable, fieldType, deepCopy, visited);
310 }
311
312 field.SetValue(destination, destEnumerable);
313 }
314 // Handle complex objects (non-value types, non-immutable, non-system)
315 else if (!fieldType.IsValueType && !IsImmutableType(fieldType) && !IsSystemInternalType(fieldType) && !IsCultureRelatedType(fieldType))
316 {
317 object destValue = ConverterHelper.InstantiateOne(fieldType);
318 var nestedVisited = new HashSet<object>(new ReferenceEqualityComparer());
319 destValue = DeepCopyObject(sourceValue, destValue, deepCopy, nestedVisited);
320 field.SetValue(destination, destValue);
321 }
322 // Handle value types, strings, immutable types, and system types
323 else
324 {
325 field.SetValue(destination, sourceValue); // Shallow copy
326 }
327 }
328 catch (Exception ex)
329 {
330 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to copy field [{field.Name}] of type [{sourceType.Name}].", source, GPALObjectType.Other, ex);
331 }
332 }
333 return destination;
334 }
335 private static void CopyGenericEnumerable(IEnumerable sourceEnumerable, object destEnumerable, Type enumerableType, DeepCopy deepCopy, HashSet<object> visited)
336 {
337 MethodInfo addMethod = enumerableType.GetMethod("Add");
338 if (addMethod == null)
339 {
340 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"No Add method found for type [{enumerableType.Name}].", null, GPALObjectType.None);
341 return;
342 }
343
344 foreach (var item in sourceEnumerable)
345 {
346 if (item == null)
347 {
348 addMethod.Invoke(destEnumerable, new object[] { null });
349 continue;
350 }
351
352 Type itemType = item.GetType();
353 object destItem = null;
354
355 if (!itemType.IsValueType && !IsImmutableType(itemType) && !IsSystemInternalType(itemType) && !IsCultureRelatedType(itemType))
356 {
357 if (!visited.Add(item))
358 {
359 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Circular reference detected for item of type [{itemType.Name}].", item, GPALObjectType.None);
360 continue;
361 }
362
363 destItem = ConverterHelper.InstantiateOne(itemType);
364 var nestedVisited = new HashSet<object>(new ReferenceEqualityComparer());
365 destItem = DeepCopyObject(item, destItem, deepCopy, nestedVisited);
366 }
367 else
368 {
369 destItem = item; // Shallow copy for value types, strings, immutable types, and system types
370 }
371
372 addMethod.Invoke(destEnumerable, new object[] { destItem });
373 }
374 }
375 private static bool IsImmutableType(Type type)
376 {
377 return type == typeof(string) ||
378 type == typeof(DateTime) ||
379 type == typeof(TimeSpan) ||
380 type == typeof(decimal) ||
381 type == typeof(Guid);
382 }
383
384 private static bool IsSystemInternalType(Type type)
385 {
386 return type.Namespace != null &&
387 (type.Namespace.StartsWith("System.Reflection") ||
388 type.Namespace.StartsWith("System.Runtime") ||
389 type.Namespace.StartsWith("System.Threading") ||
390 type.Namespace.StartsWith("System.Security"));
391 }
392
393 private static bool IsCultureRelatedType(Type type)
394 {
395 return type == typeof(System.Globalization.CultureInfo) ||
396 type.Namespace == "System.Globalization";
397 }
398 public static int GetItemCount(dynamic propertyOrField, dynamic source)
399 {
400 int count = 0;
401 if (source is IEnumerable<object> objectEnumerable)
402 {
403 return objectEnumerable.Count();
404 }
405 else if (source is Array array)
406 {
407 return array.Length;
408 }
409 else if (source is ICollection collection)
410 {
411 return collection.Count;
412 }
413 else if (null != propertyOrField)
414 {
415 dynamic enumeration = propertyOrField.GetValue(source);
416 if (null != enumeration)
417 foreach (dynamic item in enumeration)
418 count++;
419 return count;
420 }
421 else
422 return 0;
423 }
424 static bool IsPropertyEnumerable(PropertyInfo property)
425 {
426 // Check if the property type implements the IEnumerable interface
427 return typeof(IEnumerable).IsAssignableFrom(property.PropertyType);
428 }
429 static bool IsFieldEnumerable(FieldInfo field)
430 {
431 return typeof(IEnumerable).IsAssignableFrom(field.FieldType);
432 }
433 }
434
435}
436
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
Definition GPAL.cs:49
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
Definition GPAL.cs:2406