[NUI] Check if event handler of the DisposedQueued object is valid
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Common / BaseHandle.cs
1 /*
2  * Copyright(c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 using System;
18 using System.ComponentModel;
19 using System.Runtime.CompilerServices;
20 using Tizen.NUI.Binding;
21
22 namespace Tizen.NUI
23 {
24     /// <summary>
25     /// BaseHandle is a handle to an internal Dali resource.
26     /// </summary>
27     /// <since_tizen> 3 </since_tizen>
28     public class BaseHandle : Element, global::System.IDisposable
29     {
30         /// <summary>
31         /// swigCMemOwn
32         /// </summary>
33         /// <since_tizen> 3 </since_tizen>
34         [Obsolete("Deprecated in API9, Will be removed in API11, Please use SwigCMemOwn")]
35         [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "<Pending>")]
36         protected bool swigCMemOwn;
37
38         /// <summary>
39         /// A flag to check if it is already disposed.
40         /// </summary>
41         /// <since_tizen> 3 </since_tizen>
42         [Obsolete("Deprecated in API9, Will be removed in API11, Please use Disposed")]
43         [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "<Pending>")]
44         protected bool disposed = false;
45
46         private global::System.Runtime.InteropServices.HandleRef swigCPtr;
47         private global::System.Runtime.InteropServices.HandleRef swigCPtrCopy;
48         private bool registerMe;
49
50         //A Flag to check who called Dispose(). (By User or DisposeQueue)
51         private bool isDisposeQueued = false;
52
53 #if NUI_DEBUG_ON
54         private static int debuggingCount = 0;
55 #endif
56
57         /// <summary>
58         /// Create an instance of BaseHandle.
59         /// </summary>
60         /// <since_tizen> 3 </since_tizen>
61         public BaseHandle() : this(Interop.BaseHandle.NewBaseHandle())
62         {
63             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
64         }
65
66         /// <summary>
67         /// Create an instance of BaseHandle.
68         /// </summary>
69         /// <param name="handle">The BaseHandle instance.</param>
70         /// <since_tizen> 3 </since_tizen>
71         public BaseHandle(BaseHandle handle) : this(Interop.BaseHandle.NewBaseHandle(BaseHandle.getCPtr(handle)))
72         {
73             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
74         }
75
76         internal BaseHandle(global::System.IntPtr cPtr, bool cMemoryOwn)
77         {
78             //to catch derived classes dali native exceptions
79             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
80
81             NUILog.Debug($"[Dispose] BaseHandle.contructor with cMemeryOwn:{cMemoryOwn} START");
82
83             registerMe = swigCMemOwn = cMemoryOwn;
84             swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
85             // using copy constructor to create another native handle so Registry.Unregister works fine.
86             swigCPtrCopy = new global::System.Runtime.InteropServices.HandleRef(this, Interop.BaseHandle.NewBaseHandle(swigCPtr));
87             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
88
89             if (registerMe)
90             {
91                 // Register this instance of BaseHandle in the registry.
92                 Registry.Register(this);
93             }
94
95 #if NUI_DEBUG_ON
96             NUILog.Debug($"[Dispose] type:{GetType()} copyNativeHandle:{swigCPtrCopy.Handle.ToString("X8")}");
97
98             debuggingCount++;
99             if (this is BaseComponents.View view)
100             {
101                 NUILog.Debug($"[Dispose] ID:{view.ID} Name:{view.Name} debuggingCount:{debuggingCount}");
102             }
103             NUILog.Debug($"[Dispose] BaseHandle.contructor with cMemeryOwn END");
104             NUILog.Debug($"=============================");
105 #endif
106         }
107
108         internal BaseHandle(global::System.IntPtr cPtr)
109         {
110             NUILog.Debug($"[Dispose] BaseHandle.contructor START");
111
112             registerMe = swigCMemOwn = true;
113
114             swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
115
116             // using copy constructor to create another native handle so Registry.Unregister works fine.
117             swigCPtrCopy = new global::System.Runtime.InteropServices.HandleRef(this, Interop.BaseHandle.NewBaseHandle(SwigCPtr));
118             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
119
120             if (registerMe)
121             {
122                 // Register this instance of BaseHandle in the registry.
123                 Registry.Register(this);
124             }
125
126 #if NUI_DEBUG_ON
127             NUILog.Debug($"[Dispose] type:{GetType()} copyNativeHandle:{swigCPtrCopy.Handle.ToString("X8")}");
128
129             debuggingCount++;
130             if (this is BaseComponents.View view)
131             {
132                 NUILog.Debug($"[Dispose] ID:{view.ID} Name:{view.Name} debuggingCount:{debuggingCount}");
133             }
134             NUILog.Debug($"[Dispose] BaseHandle.contructor END");
135             NUILog.Debug($"=============================");
136 #endif
137
138         }
139
140         /// <summary>
141         /// Dispose.
142         /// </summary>
143         /// <since_tizen> 3 </since_tizen>
144         // following this guide: https://docs.microsoft.com/ko-kr/dotnet/fundamentals/code-analysis/quality-rules/ca1063?view=vs-2019 (CA1063)
145         ~BaseHandle() => Dispose(false);
146
147         /// <summary>
148         /// Event when a property is set.
149         /// </summary>
150         /// <since_tizen> 5 </since_tizen>
151         /// <seealso cref="BindableObject.PropertyChanged"/>
152         [Obsolete("Deprecated in API9, Will be removed in API11, Please use BindableObject.PropertyChanged instead!")]
153         public event PropertyChangedEventHandler PropertySet;
154
155         internal global::System.Runtime.InteropServices.HandleRef GetBaseHandleCPtrHandleRef
156         {
157             get
158             {
159                 return swigCPtrCopy;
160             }
161         }
162
163         /// <summary>
164         /// Returns the bool value true to indicate that an operand is true and returns false otherwise.
165         /// </summary>
166         /// <since_tizen> 3 </since_tizen>
167         public static bool operator true(BaseHandle handle)
168         {
169             // if the C# object is null, return false
170             if (BaseHandle.ReferenceEquals(handle, null))
171             {
172                 return false;
173             }
174             // returns true if the handle has a body, false otherwise
175             return handle.HasBody();
176         }
177
178         /// <summary>
179         /// Returns the bool false  to indicate that an operand is false and returns true otherwise.
180         /// </summary>
181         /// <since_tizen> 3 </since_tizen>
182         public static bool operator false(BaseHandle handle)
183         {
184             // if the C# object is null, return true
185             if (BaseHandle.ReferenceEquals(handle, null))
186             {
187                 return true;
188             }
189             return !handle.HasBody();
190         }
191
192         /// <summary>
193         /// Explicit conversion from Handle to bool.
194         /// </summary>
195         /// <since_tizen> 3 </since_tizen>
196         public static explicit operator bool(BaseHandle handle)
197         {
198             // if the C# object is null, return false
199             if (BaseHandle.ReferenceEquals(handle, null))
200             {
201                 return false;
202             }
203             // returns true if the handle has a body, false otherwise
204             return handle.HasBody();
205         }
206
207         /// <summary>
208         /// Equality operator
209         /// </summary>
210         /// <since_tizen> 3 </since_tizen>
211         public static bool operator ==(BaseHandle x, BaseHandle y)
212         {
213             // if the C# objects are the same return true
214             if (BaseHandle.ReferenceEquals(x, y))
215             {
216                 return true;
217             }
218             if (!BaseHandle.ReferenceEquals(x, null) && !BaseHandle.ReferenceEquals(y, null))
219             {
220                 // drop into native code to see if both handles point to the same body
221                 return x.IsEqual(y);
222             }
223
224             if (BaseHandle.ReferenceEquals(x, null) && !BaseHandle.ReferenceEquals(y, null))
225             {
226                 if (y.HasBody()) return false;
227                 else return true;
228             }
229             if (!BaseHandle.ReferenceEquals(x, null) && BaseHandle.ReferenceEquals(y, null))
230             {
231                 if (x.HasBody()) return false;
232                 else return true;
233             }
234
235             return false;
236         }
237
238         /// <summary>
239         /// Inequality operator. Returns Null if either operand is Null
240         /// </summary>
241         /// <since_tizen> 3 </since_tizen>
242         public static bool operator !=(BaseHandle x, BaseHandle y)
243         {
244             return !(x == y);
245         }
246
247         /// <summary>
248         /// Logical AND operator.<br />
249         /// It's possible when doing a  operator this function (opBitwiseAnd) is never called due to short circuiting.<br />
250         /// </summary>
251         /// <since_tizen> 3 </since_tizen>
252         public static BaseHandle operator &(BaseHandle x, BaseHandle y)
253         {
254             if (x == y)
255             {
256                 return x;
257             }
258             return null;
259         }
260
261         /// <summary>
262         /// Logical OR operator for ||.<br />
263         /// It's possible when doing a || this function (opBitwiseOr) is never called due to short circuiting.<br />
264         /// </summary>
265         /// <since_tizen> 3 </since_tizen>
266         public static BaseHandle operator |(BaseHandle x, BaseHandle y)
267         {
268             if (!BaseHandle.ReferenceEquals(x, null) || !BaseHandle.ReferenceEquals(y, null))
269             {
270                 if (x != null && x.HasBody())
271                 {
272                     return x;
273                 }
274                 if (y != null && y.HasBody())
275                 {
276                     return y;
277                 }
278                 return null;
279             }
280             return null;
281         }
282
283         /// <summary>
284         /// Logical ! operator
285         /// </summary>
286         /// <since_tizen> 3 </since_tizen>
287         public static bool operator !(BaseHandle x)
288         {
289             // if the C# object is null, return true
290             if (BaseHandle.ReferenceEquals(x, null))
291             {
292                 return true;
293             }
294             if (x.HasBody())
295             {
296                 return false;
297             }
298             return true;
299         }
300
301         /// <summary>
302         /// Equals
303         /// </summary>
304         /// <param name="o">The object should be compared.</param>
305         /// <returns>True if equal.</returns>
306         /// <since_tizen> 5 </since_tizen>
307         public override bool Equals(object o)
308         {
309             return base.Equals(o);
310         }
311
312         /// <summary>
313         /// Gets the hash code of this baseHandle.
314         /// </summary>
315         /// <returns>The hash code.</returns>
316         /// <since_tizen> 5 </since_tizen>
317         public override int GetHashCode()
318         {
319             return base.GetHashCode();
320         }
321
322         /// <summary>
323         /// Dispose.
324         /// </summary>
325         /// <since_tizen> 3 </since_tizen>
326         public void Dispose()
327         {
328             if (isDisposeQueued)
329             {
330                 Dispose(DisposeTypes.Implicit);
331             }
332             else
333             {
334                 Dispose(true);
335             }
336             GC.SuppressFinalize(this);
337         }
338
339         /// <summary>
340         /// Hidden API (Inhouse API).
341         /// Dispose. 
342         /// </summary>
343         /// <remarks>
344         /// Following the guide of https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose.
345         /// This will replace "protected virtual void Dispose(DisposeTypes type)" which is exactly same in functionality.
346         /// </remarks>
347         /// <param name="disposing">true in order to free managed objects</param>
348         // Protected implementation of Dispose pattern.
349         [EditorBrowsable(EditorBrowsableState.Never)]
350         protected virtual void Dispose(bool disposing)
351         {
352             if (disposed)
353             {
354                 return;
355             }
356
357             if (disposing)
358             {
359                 // TODO: dispose managed state (managed objects).
360                 // Explicit call. user calls Dispose()
361
362                 //Throw exception if Dispose() is called in separate thread.
363                 if (!Window.IsInstalled())
364                 {
365                     var process = global::System.Diagnostics.Process.GetCurrentProcess().Id;
366                     var thread = global::System.Threading.Thread.CurrentThread.ManagedThreadId;
367                     var me = this.GetType().FullName;
368
369                     throw new global::System.InvalidOperationException("This API called from separate thread. This API must be called from MainThread. \n" +
370                         $" process:{process} thread:{thread}, disposing:{disposing}, isDisposed:{this.disposed}, isDisposeQueued:{this.isDisposeQueued}, me:{me}\n");
371                 }
372
373                 if (isDisposeQueued)
374                 {
375                     Tizen.Log.Fatal("NUI", $"should not be here! (dead code) this will be removed!");
376                     throw new global::System.Exception($"[NUI] should not be here! (dead code) this will be removed!");
377                     Dispose(DisposeTypes.Implicit);
378                 }
379                 else
380                 {
381                     Dispose(DisposeTypes.Explicit);
382                 }
383             }
384             else
385             {
386                 // Implicit call. user doesn't call Dispose(), so this object is added into DisposeQueue to be disposed automatically.
387                 if (!isDisposeQueued)
388                 {
389                     isDisposeQueued = true;
390                     DisposeQueue.Instance.Add(this);
391                 }
392             }
393
394             // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
395             // TODO: set large fields to null.
396         }
397
398
399         /// <summary>
400         /// Performs an action on this object with the given action name and attributes.
401         /// </summary>
402         /// <param name="actionName">The command for the action.</param>
403         /// <param name="attributes">The list of attributes for the action.</param>
404         /// <returns>The action is performed by the object or not.</returns>
405         /// <since_tizen> 3 </since_tizen>
406         public bool DoAction(string actionName, PropertyMap attributes)
407         {
408             bool ret = Interop.BaseHandle.DoAction(swigCPtrCopy, actionName, PropertyMap.getCPtr(attributes));
409             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
410             return ret;
411         }
412
413         /// <summary>
414         /// Returns the type name for the Handle.<br />
415         /// Will return an empty string if the typename does not exist. This will happen for types that
416         /// have not registered with type-registry.
417         /// </summary>
418         /// <returns>The type name. Empty string if the typename does not exist.</returns>
419         /// <since_tizen> 3 </since_tizen>
420         public string GetTypeName()
421         {
422             string ret = Interop.BaseHandle.GetTypeName(swigCPtrCopy);
423             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
424             return ret;
425         }
426
427         /// <summary>
428         /// Returns the type info for the Handle.<br />
429         /// </summary>
430         /// <param name="info">The type information.</param>
431         /// <returns>True If get the type info.</returns>
432         /// <since_tizen> 3 </since_tizen>
433         public bool GetTypeInfo(Tizen.NUI.TypeInfo info)
434         {
435             bool ret = Interop.BaseHandle.GetTypeInfo(swigCPtrCopy, Tizen.NUI.TypeInfo.getCPtr(info));
436             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
437             return ret;
438         }
439
440         /// <summary>
441         /// Resets the handle.
442         /// </summary>
443         /// <since_tizen> 3 </since_tizen>
444         public void Reset()
445         {
446             Interop.BaseHandle.Reset(swigCPtrCopy);
447             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
448         }
449
450         /// <summary>
451         /// To check the BaseHandle instance is equal or not.
452         /// </summary>
453         /// <param name="rhs">The baseHandle instance.</param>
454         /// <returns>True If equal.</returns>
455         /// <since_tizen> 3 </since_tizen>
456         public bool EqualTo(BaseHandle rhs)
457         {
458             bool ret = Interop.BaseHandle.EqualTo(swigCPtrCopy, BaseHandle.getCPtr(rhs));
459             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
460             return ret;
461         }
462
463         /// <summary>
464         /// To check the BaseHandle instance is equal or not.
465         /// </summary>
466         /// <param name="rhs">The baseHandle instance.</param>
467         /// <returns>True If not equal.</returns>
468         /// <since_tizen> 3 </since_tizen>
469         public bool NotEqualTo(BaseHandle rhs)
470         {
471             bool ret = Interop.BaseHandle.NotEqualTo(swigCPtrCopy, BaseHandle.getCPtr(rhs));
472             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
473             return ret;
474         }
475
476         /// <summary>
477         /// To check the BaseHandle instance has body or not.
478         /// </summary>
479         /// <returns>True If the baseHandle instance has body.</returns>
480         /// <since_tizen> 3 </since_tizen>
481         public bool HasBody()
482         {
483             if (swigCPtrCopy.Handle == IntPtr.Zero)
484             {
485                 return false;
486             }
487
488             if (disposed == true)
489             {
490                 return false;
491             }
492             bool ret = Interop.BaseHandle.HasBody(swigCPtrCopy);
493             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
494             return ret;
495         }
496
497         /// <summary>
498         /// To check the BaseHandle instance is equal or not.
499         /// </summary>
500         /// <param name="rhs">The baseHandle instance.</param>
501         /// <returns>True If equal.</returns>
502         /// <since_tizen> 3 </since_tizen>
503         public bool IsEqual(BaseHandle rhs)
504         {
505             if (disposed == true)
506             {
507                 return false;
508             }
509
510             bool ret = Interop.BaseHandle.IsEqual(swigCPtrCopy, BaseHandle.getCPtr(rhs));
511             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
512             return ret;
513         }
514
515         internal static global::System.Runtime.InteropServices.HandleRef getCPtr(BaseHandle obj)
516         {
517             return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtrCopy;
518         }
519
520         internal void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
521         {
522             PropertySet?.Invoke(this, new PropertyChangedEventArgs(propertyName));
523         }
524
525         /// <summary>
526         /// Dispose.
527         /// </summary>
528         /// <since_tizen> 3 </since_tizen>
529         protected virtual void Dispose(DisposeTypes type)
530         {
531             if (disposed)
532             {
533                 return;
534             }
535
536             NUILog.Debug($"[Dispose] BaseHandle.Dispose({type}) START");
537
538             if (type == DisposeTypes.Explicit)
539             {
540                 //Called by User
541                 //Release your own managed resources here.
542                 //You should release all of your own disposable objects here.
543
544             }
545
546             //Release your own unmanaged resources here.
547             //You should not access any managed member here except static instance.
548             //because the execution order of Finalizes is non-deterministic.
549
550             //Unreference this instance from Registry.
551             if (registerMe)
552             {
553                 Registry.Unregister(this);
554             }
555
556 #if NUI_DEBUG_ON
557             debuggingCount--;
558             NUILog.Debug($"[Dispose] swigCMemOwn:{swigCMemOwn} debuggingCount:{debuggingCount} type:{GetType()} copyNativeHandle:{swigCPtrCopy.Handle.ToString("X8")}");
559 #endif
560
561             if (SwigCPtr.Handle != IntPtr.Zero)
562             {
563                 if (swigCMemOwn)
564                 {
565                     swigCMemOwn = false;
566                     ReleaseSwigCPtr(SwigCPtr);
567                 }
568                 swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
569             }
570             if (swigCPtrCopy.Handle != global::System.IntPtr.Zero)
571             {
572                 swigCMemOwn = false;
573                 Interop.BaseHandle.DeleteBaseHandle(swigCPtrCopy);
574                 swigCPtrCopy = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
575             }
576
577             disposed = true;
578
579             if (null != Application.Current)
580             {
581                 Application.Current.XamlResourceChanged -= OnResourcesChanged;
582             }
583             
584             NUILog.Debug($"[Dispose] BaseHandle.Dispose({type}) END");
585             NUILog.Debug($"=============================");
586         }
587
588         /// <summary>
589         /// Release swigCPtr.
590         /// </summary>
591         /// <since_tizen> 6 </since_tizen>
592         /// This will not be public opened.
593         [EditorBrowsable(EditorBrowsableState.Never)]
594         protected virtual void ReleaseSwigCPtr(System.Runtime.InteropServices.HandleRef swigCPtr)
595         {
596         }
597
598         /// <summary>
599         /// Contains event arguments for the FocusChangeRequested event.
600         /// </summary>
601         [Obsolete("Deprecated in API9; Will be removed in API11.")]
602         public class FocusRequestArgs : EventArgs
603         {
604
605             /// <summary>
606             /// Gets or sets a value that indicates the starting focus state of the element for which a focus change is requested.
607             /// </summary>
608             public bool Focus { get; set; }
609
610             /// <summary>
611             /// Gets or sets a value that indicates the ending focus state of the element for which a focus change is requested.
612             /// </summary>
613             public bool Result { get; set; }
614         }
615
616         internal global::System.Runtime.InteropServices.HandleRef SwigCPtr
617         {
618             get
619             {
620                 if (swigCPtr.Handle == IntPtr.Zero)
621                 {
622                     var process = global::System.Diagnostics.Process.GetCurrentProcess().Id;
623                     var thread = global::System.Threading.Thread.CurrentThread.ManagedThreadId;
624                     var me = this.GetType().FullName;
625
626                     throw new ObjectDisposedException(nameof(SwigCPtr), $"Error! NUI's native dali object is already disposed. " +
627                         $"OR the native dali object handle of NUI becomes null! \n" +
628                         $" process:{process} thread:{thread}, isDisposed:{this.disposed}, isDisposeQueued:{this.isDisposeQueued}, me:{me}\n");
629                 }
630                 return swigCPtr;
631             }
632         }
633
634         internal bool IsNativeHandleInvalid()
635         {
636             return swigCPtr.Handle == IntPtr.Zero;
637         }
638
639         /// <summary>
640         /// swigCMemOwn
641         /// </summary>
642         [EditorBrowsable(EditorBrowsableState.Never)]
643         protected internal bool SwigCMemOwn => swigCMemOwn;
644
645         /// <summary>
646         /// A flag to check if it is already disposed.
647         /// </summary>
648         [EditorBrowsable(EditorBrowsableState.Never)]
649         protected internal bool Disposed => disposed;
650
651         /// <summary>
652         /// A flag to check if it is disposed by DisposeQueue.
653         /// </summary>
654         [EditorBrowsable(EditorBrowsableState.Never)]
655         protected internal bool IsDisposeQueued => isDisposeQueued;
656
657     }
658 }