]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/common/xtistrm.cpp
wxMessageBox off the main thread lost result code.
[wxWidgets.git] / src / common / xtistrm.cpp
... / ...
CommitLineData
1/////////////////////////////////////////////////////////////////////////////
2// Name: src/common/xtistrm.cpp
3// Purpose: streaming runtime metadata information
4// Author: Stefan Csomor
5// Modified by:
6// Created: 27/07/03
7// Copyright: (c) 2003 Stefan Csomor
8// Licence: wxWindows licence
9/////////////////////////////////////////////////////////////////////////////
10
11// For compilers that support precompilation, includes "wx.h".
12#include "wx/wxprec.h"
13
14#ifdef __BORLANDC__
15 #pragma hdrstop
16#endif
17
18#if wxUSE_EXTENDED_RTTI
19
20#include "wx/xtistrm.h"
21
22#ifndef WX_PRECOMP
23 #include "wx/object.h"
24 #include "wx/hash.h"
25 #include "wx/event.h"
26#endif
27
28#include "wx/tokenzr.h"
29#include "wx/txtstrm.h"
30
31// STL headers:
32
33#include "wx/beforestd.h"
34#include <map>
35#include <vector>
36#include <string>
37#include "wx/afterstd.h"
38using namespace std;
39
40
41// ----------------------------------------------------------------------------
42// wxObjectWriter
43// ----------------------------------------------------------------------------
44
45struct wxObjectWriter::wxObjectWriterInternal
46{
47 map< const wxObject*, int > m_writtenObjects;
48 int m_nextId;
49};
50
51wxObjectWriter::wxObjectWriter()
52{
53 m_data = new wxObjectWriterInternal;
54 m_data->m_nextId = 0;
55}
56
57wxObjectWriter::~wxObjectWriter()
58{
59 delete m_data;
60}
61
62struct wxObjectWriter::wxObjectWriterInternalPropertiesData
63{
64 char nothing;
65};
66
67void wxObjectWriter::ClearObjectContext()
68{
69 delete m_data;
70 m_data = new wxObjectWriterInternal();
71 m_data->m_nextId = 0;
72}
73
74void wxObjectWriter::WriteObject(const wxObject *object, const wxClassInfo *classInfo,
75 wxObjectWriterCallback *writercallback, const wxString &name,
76 const wxStringToAnyHashMap &metadata )
77{
78 DoBeginWriteTopLevelEntry( name );
79 WriteObject( object, classInfo, writercallback, false, metadata);
80 DoEndWriteTopLevelEntry( name );
81}
82
83void wxObjectWriter::WriteObject(const wxObject *object, const wxClassInfo *classInfo,
84 wxObjectWriterCallback *writercallback, bool isEmbedded,
85 const wxStringToAnyHashMap &metadata )
86{
87 if ( !classInfo->BeforeWriteObject( object, this, writercallback, metadata) )
88 return;
89
90 if ( writercallback->BeforeWriteObject( this, object, classInfo, metadata) )
91 {
92 if ( object == NULL )
93 DoWriteNullObject();
94 else if ( IsObjectKnown( object ) )
95 DoWriteRepeatedObject( GetObjectID(object) );
96 else
97 {
98 int oid = m_data->m_nextId++;
99 if ( !isEmbedded )
100 m_data->m_writtenObjects[object] = oid;
101
102 // in case this object is a wxDynamicObject we also have to insert is superclass
103 // instance with the same id, so that object relations are streamed out correctly
104 const wxDynamicObject* dynobj = wx_dynamic_cast(const wxDynamicObject*, object);
105 if ( !isEmbedded && dynobj )
106 m_data->m_writtenObjects[dynobj->GetSuperClassInstance()] = oid;
107
108 DoBeginWriteObject( object, classInfo, oid, metadata );
109 wxObjectWriterInternalPropertiesData data;
110 WriteAllProperties( object, classInfo, writercallback, &data );
111 DoEndWriteObject( object, classInfo, oid );
112 }
113 writercallback->AfterWriteObject( this,object, classInfo );
114 }
115}
116
117void wxObjectWriter::FindConnectEntry(const wxEvtHandler * evSource,
118 const wxEventSourceTypeInfo* dti,
119 const wxObject* &sink,
120 const wxHandlerInfo *&handler)
121{
122 wxList *dynamicEvents = evSource->GetDynamicEventTable();
123
124 if ( dynamicEvents )
125 {
126 for ( wxList::const_iterator node = dynamicEvents->begin(); node != dynamicEvents->end(); ++node )
127 {
128 wxDynamicEventTableEntry *entry = (wxDynamicEventTableEntry*)(*node);
129
130 // find the match
131 if ( entry->m_fn &&
132 (dti->GetEventType() == entry->m_eventType) &&
133 (entry->m_id == -1 ) &&
134 (entry->m_fn->GetEvtHandler() != NULL ) )
135 {
136 sink = entry->m_fn->GetEvtHandler();
137 const wxClassInfo* sinkClassInfo = sink->GetClassInfo();
138 const wxHandlerInfo* sinkHandler = sinkClassInfo->GetFirstHandler();
139 while ( sinkHandler )
140 {
141 if ( sinkHandler->GetEventFunction() == entry->m_fn->GetEvtMethod() )
142 {
143 handler = sinkHandler;
144 break;
145 }
146 sinkHandler = sinkHandler->GetNext();
147 }
148 break;
149 }
150 }
151 }
152}
153void wxObjectWriter::WriteAllProperties( const wxObject * obj, const wxClassInfo* ci,
154 wxObjectWriterCallback *writercallback,
155 wxObjectWriterInternalPropertiesData * data )
156{
157 wxPropertyInfoMap map;
158 ci->GetProperties( map );
159 for ( int i = 0; i < ci->GetCreateParamCount(); ++i )
160 {
161 wxString name = ci->GetCreateParamName(i);
162 wxPropertyInfoMap::const_iterator iter = map.find(name);
163 const wxPropertyInfo* prop = iter == map.end() ? NULL : iter->second;
164 if ( prop )
165 {
166 WriteOneProperty( obj, prop->GetDeclaringClass(), prop, writercallback, data );
167 }
168 else
169 {
170 wxLogError( _("Create Parameter %s not found in declared RTTI Parameters"), name.c_str() );
171 }
172 map.erase( name );
173 }
174 { // Extra block for broken compilers
175 for( wxPropertyInfoMap::iterator iter = map.begin(); iter != map.end(); ++iter )
176 {
177 const wxPropertyInfo* prop = iter->second;
178 if ( prop->GetFlags() & wxPROP_OBJECT_GRAPH )
179 {
180 WriteOneProperty( obj, prop->GetDeclaringClass(), prop, writercallback, data );
181 }
182 }
183 }
184 { // Extra block for broken compilers
185 for( wxPropertyInfoMap::iterator iter = map.begin(); iter != map.end(); ++iter )
186 {
187 const wxPropertyInfo* prop = iter->second;
188 if ( !(prop->GetFlags() & wxPROP_OBJECT_GRAPH) )
189 {
190 WriteOneProperty( obj, prop->GetDeclaringClass(), prop, writercallback, data );
191 }
192 }
193 }
194}
195
196class WXDLLIMPEXP_BASE wxObjectPropertyWriter: public wxObjectWriterFunctor
197{
198public:
199 wxObjectPropertyWriter(const wxClassTypeInfo* cti,
200 wxObjectWriterCallback *writercallback,
201 wxObjectWriter* writer,
202 wxStringToAnyHashMap &props) :
203 m_cti(cti),m_persister(writercallback),m_writer(writer),m_props(props)
204 {}
205
206 virtual void operator()(const wxObject *vobj)
207 {
208 m_writer->WriteObject( vobj, (vobj ? vobj->GetClassInfo() : m_cti->GetClassInfo() ),
209 m_persister, m_cti->GetKind()== wxT_OBJECT, m_props );
210 }
211private:
212 const wxClassTypeInfo* m_cti;
213 wxObjectWriterCallback *m_persister;
214 wxObjectWriter* m_writer;
215 wxStringToAnyHashMap& m_props;
216};
217
218void wxObjectWriter::WriteOneProperty( const wxObject *obj, const wxClassInfo* ci,
219 const wxPropertyInfo* pi, wxObjectWriterCallback *writercallback,
220 wxObjectWriterInternalPropertiesData *WXUNUSED(data) )
221{
222 if ( pi->GetFlags() & wxPROP_DONT_STREAM )
223 return;
224
225 // make sure that we are picking the correct object for accessing the property
226 const wxDynamicObject* dynobj = wx_dynamic_cast(const wxDynamicObject*, obj );
227 if ( dynobj && (wx_dynamic_cast(const wxDynamicClassInfo*, ci) == NULL) )
228 obj = dynobj->GetSuperClassInstance();
229
230 if ( pi->GetTypeInfo()->GetKind() == wxT_COLLECTION )
231 {
232 wxAnyList data;
233 pi->GetAccessor()->GetPropertyCollection(obj, data);
234 const wxTypeInfo * elementType =
235 wx_dynamic_cast( const wxCollectionTypeInfo*, pi->GetTypeInfo() )->GetElementType();
236 if ( !data.empty() )
237 {
238 DoBeginWriteProperty( pi );
239 for ( wxAnyList::const_iterator iter = data.begin(); iter != data.end(); ++iter )
240 {
241 DoBeginWriteElement();
242 const wxAny* valptr = *iter;
243 if ( writercallback->BeforeWriteProperty( this, obj, pi, *valptr ) )
244 {
245 const wxClassTypeInfo* cti =
246 wx_dynamic_cast( const wxClassTypeInfo*, elementType );
247 if ( cti )
248 {
249 wxStringToAnyHashMap md;
250 wxObjectPropertyWriter pw(cti,writercallback,this, md);
251
252 const wxClassInfo* pci = cti->GetClassInfo();
253 pci->CallOnAny( *valptr, &pw);
254 }
255 else
256 {
257 DoWriteSimpleType( *valptr );
258 }
259 }
260 DoEndWriteElement();
261 }
262 DoEndWriteProperty( pi );
263 }
264 }
265 else
266 {
267 const wxEventSourceTypeInfo* dti =
268 wx_dynamic_cast( const wxEventSourceTypeInfo* , pi->GetTypeInfo() );
269 if ( dti )
270 {
271 const wxObject* sink = NULL;
272 const wxHandlerInfo *handler = NULL;
273
274 const wxEvtHandler * evSource = wx_dynamic_cast(const wxEvtHandler *, obj);
275 if ( evSource )
276 {
277 FindConnectEntry( evSource, dti, sink, handler );
278 if ( writercallback->BeforeWriteDelegate( this, obj, ci, pi, sink, handler ) )
279 {
280 if ( sink != NULL && handler != NULL )
281 {
282 DoBeginWriteProperty( pi );
283 if ( IsObjectKnown( sink ) )
284 {
285 DoWriteDelegate( obj, ci, pi, sink, GetObjectID( sink ),
286 sink->GetClassInfo(), handler );
287 DoEndWriteProperty( pi );
288 }
289 else
290 {
291 wxLogError( wxT("Streaming delegates for not already ")
292 wxT("streamed objects not yet supported") );
293 }
294 }
295 }
296 }
297 else
298 {
299 wxLogError(_("Illegal Object Class (Non-wxEvtHandler) as Event Source") );
300 }
301 }
302 else
303 {
304 wxAny value;
305 pi->GetAccessor()->GetProperty(obj, value);
306
307 // avoid streaming out void objects
308 // TODO Verify
309 if( value.IsNull() )
310 return;
311
312 if ( pi->GetFlags() & wxPROP_ENUM_STORE_LONG )
313 {
314 const wxEnumTypeInfo *eti =
315 wx_dynamic_cast(const wxEnumTypeInfo*, pi->GetTypeInfo() );
316 if ( eti )
317 {
318 eti->ConvertFromLong( wxANY_AS(value, long ), value );
319 }
320 else
321 {
322 wxLogError( _("Type must have enum - long conversion") );
323 }
324 }
325
326 // avoid streaming out default values
327 if ( pi->GetTypeInfo()->HasStringConverters() &&
328 !pi->GetDefaultValue().IsNull() ) // TODO Verify
329 {
330 if ( wxAnyGetAsString(value) == wxAnyGetAsString(pi->GetDefaultValue()) )
331 return;
332 }
333
334 // avoid streaming out null objects
335 const wxClassTypeInfo* cti =
336 wx_dynamic_cast( const wxClassTypeInfo* , pi->GetTypeInfo() );
337
338 if ( cti && cti->GetKind() == wxT_OBJECT_PTR && wxAnyGetAsObjectPtr(value) == NULL )
339 return;
340
341 if ( writercallback->BeforeWriteProperty( this, obj, pi, value ) )
342 {
343 DoBeginWriteProperty( pi );
344 if ( cti )
345 {
346 if ( cti->HasStringConverters() )
347 {
348 wxString stringValue;
349 cti->ConvertToString( value, stringValue );
350 wxAny convertedValue(stringValue);
351 DoWriteSimpleType( convertedValue );
352 }
353 else
354 {
355 wxStringToAnyHashMap md;
356 wxObjectPropertyWriter pw(cti,writercallback,this, md);
357
358 const wxClassInfo* pci = cti->GetClassInfo();
359 pci->CallOnAny(value, &pw);
360 }
361 }
362 else
363 {
364 DoWriteSimpleType( value );
365 }
366 DoEndWriteProperty( pi );
367 }
368 }
369 }
370}
371
372int wxObjectWriter::GetObjectID(const wxObject *obj)
373{
374 if ( !IsObjectKnown( obj ) )
375 return wxInvalidObjectID;
376
377 return m_data->m_writtenObjects[obj];
378}
379
380bool wxObjectWriter::IsObjectKnown( const wxObject *obj )
381{
382 return m_data->m_writtenObjects.find( obj ) != m_data->m_writtenObjects.end();
383}
384
385
386// ----------------------------------------------------------------------------
387// wxObjectReader
388// ----------------------------------------------------------------------------
389
390struct wxObjectReader::wxObjectReaderInternal
391{
392 map<int,wxClassInfo*> m_classInfos;
393};
394
395wxObjectReader::wxObjectReader()
396{
397 m_data = new wxObjectReaderInternal;
398}
399
400wxObjectReader::~wxObjectReader()
401{
402 delete m_data;
403}
404
405wxClassInfo* wxObjectReader::GetObjectClassInfo(int objectID)
406{
407 if ( objectID == wxNullObjectID || objectID == wxInvalidObjectID )
408 {
409 wxLogError( _("Invalid or Null Object ID passed to GetObjectClassInfo" ) );
410 return NULL;
411 }
412 if ( m_data->m_classInfos.find(objectID) == m_data->m_classInfos.end() )
413 {
414 wxLogError( _("Unknown Object passed to GetObjectClassInfo" ) );
415 return NULL;
416 }
417 return m_data->m_classInfos[objectID];
418}
419
420void wxObjectReader::SetObjectClassInfo(int objectID, wxClassInfo *classInfo )
421{
422 if ( objectID == wxNullObjectID || objectID == wxInvalidObjectID )
423 {
424 wxLogError( _("Invalid or Null Object ID passed to GetObjectClassInfo" ) );
425 return;
426 }
427 if ( m_data->m_classInfos.find(objectID) != m_data->m_classInfos.end() )
428 {
429 wxLogError( _("Already Registered Object passed to SetObjectClassInfo" ) );
430 return;
431 }
432 m_data->m_classInfos[objectID] = classInfo;
433}
434
435bool wxObjectReader::HasObjectClassInfo( int objectID )
436{
437 if ( objectID == wxNullObjectID || objectID == wxInvalidObjectID )
438 {
439 wxLogError( _("Invalid or Null Object ID passed to HasObjectClassInfo" ) );
440 return false;
441 }
442 return m_data->m_classInfos.find(objectID) != m_data->m_classInfos.end();
443}
444
445
446// ----------------------------------------------------------------------------
447// reading xml in
448// ----------------------------------------------------------------------------
449
450/*
451Reading components has not to be extended for components
452as properties are always sought by typeinfo over all levels
453and create params are always toplevel class only
454*/
455
456
457// ----------------------------------------------------------------------------
458// wxObjectRuntimeReaderCallback - depersisting to memory
459// ----------------------------------------------------------------------------
460
461struct wxObjectRuntimeReaderCallback::wxObjectRuntimeReaderCallbackInternal
462{
463 map<int,wxObject *> m_objects;
464
465 void SetObject(int objectID, wxObject *obj )
466 {
467 if ( m_objects.find(objectID) != m_objects.end() )
468 {
469 wxLogError( _("Passing a already registered object to SetObject") );
470 return ;
471 }
472 m_objects[objectID] = obj;
473 }
474 wxObject* GetObject( int objectID )
475 {
476 if ( objectID == wxNullObjectID )
477 return NULL;
478 if ( m_objects.find(objectID) == m_objects.end() )
479 {
480 wxLogError( _("Passing an unknown object to GetObject") );
481 return NULL;
482 }
483
484 return m_objects[objectID];
485 }
486};
487
488wxObjectRuntimeReaderCallback::wxObjectRuntimeReaderCallback()
489{
490 m_data = new wxObjectRuntimeReaderCallbackInternal();
491}
492
493wxObjectRuntimeReaderCallback::~wxObjectRuntimeReaderCallback()
494{
495 delete m_data;
496}
497
498void wxObjectRuntimeReaderCallback::AllocateObject(int objectID, wxClassInfo *classInfo,
499 wxStringToAnyHashMap &WXUNUSED(metadata))
500{
501 wxObject *O;
502 O = classInfo->CreateObject();
503 m_data->SetObject(objectID, O);
504}
505
506void wxObjectRuntimeReaderCallback::CreateObject(int objectID,
507 const wxClassInfo *classInfo,
508 int paramCount,
509 wxAny *params,
510 int *objectIdValues,
511 const wxClassInfo **objectClassInfos,
512 wxStringToAnyHashMap &WXUNUSED(metadata))
513{
514 wxObject *o;
515 o = m_data->GetObject(objectID);
516 for ( int i = 0; i < paramCount; ++i )
517 {
518 if ( objectIdValues[i] != wxInvalidObjectID )
519 {
520 wxObject *o;
521 o = m_data->GetObject(objectIdValues[i]);
522 // if this is a dynamic object and we are asked for another class
523 // than wxDynamicObject we cast it down manually.
524 wxDynamicObject *dyno = wx_dynamic_cast( wxDynamicObject *, o);
525 if ( dyno!=NULL && (objectClassInfos[i] != dyno->GetClassInfo()) )
526 {
527 o = dyno->GetSuperClassInstance();
528 }
529 params[i] = objectClassInfos[i]->ObjectPtrToAny(o);
530 }
531 }
532 classInfo->Create(o, paramCount, params);
533}
534
535void wxObjectRuntimeReaderCallback::ConstructObject(int objectID,
536 const wxClassInfo *classInfo,
537 int paramCount,
538 wxAny *params,
539 int *objectIdValues,
540 const wxClassInfo **objectClassInfos,
541 wxStringToAnyHashMap &WXUNUSED(metadata))
542{
543 wxObject *o;
544 for ( int i = 0; i < paramCount; ++i )
545 {
546 if ( objectIdValues[i] != wxInvalidObjectID )
547 {
548 wxObject *o;
549 o = m_data->GetObject(objectIdValues[i]);
550 // if this is a dynamic object and we are asked for another class
551 // than wxDynamicObject we cast it down manually.
552 wxDynamicObject *dyno = wx_dynamic_cast( wxDynamicObject *, o);
553 if ( dyno!=NULL && (objectClassInfos[i] != dyno->GetClassInfo()) )
554 {
555 o = dyno->GetSuperClassInstance();
556 }
557 params[i] = objectClassInfos[i]->ObjectPtrToAny(o);
558 }
559 }
560 o = classInfo->ConstructObject(paramCount, params);
561 m_data->SetObject(objectID, o);
562}
563
564
565void wxObjectRuntimeReaderCallback::DestroyObject(int objectID, wxClassInfo *WXUNUSED(classInfo))
566{
567 wxObject *o;
568 o = m_data->GetObject(objectID);
569 delete o;
570}
571
572void wxObjectRuntimeReaderCallback::SetProperty(int objectID,
573 const wxClassInfo *classInfo,
574 const wxPropertyInfo* propertyInfo,
575 const wxAny &value)
576{
577 wxObject *o;
578 o = m_data->GetObject(objectID);
579 classInfo->SetProperty( o, propertyInfo->GetName().c_str(), value );
580}
581
582void wxObjectRuntimeReaderCallback::SetPropertyAsObject(int objectID,
583 const wxClassInfo *classInfo,
584 const wxPropertyInfo* propertyInfo,
585 int valueObjectId)
586{
587 wxObject *o, *valo;
588 o = m_data->GetObject(objectID);
589 valo = m_data->GetObject(valueObjectId);
590 const wxClassInfo* valClassInfo =
591 (wx_dynamic_cast(const wxClassTypeInfo*,propertyInfo->GetTypeInfo()))->GetClassInfo();
592
593 // if this is a dynamic object and we are asked for another class
594 // than wxDynamicObject we cast it down manually.
595 wxDynamicObject *dynvalo = wx_dynamic_cast( wxDynamicObject *, valo);
596 if ( dynvalo!=NULL && (valClassInfo != dynvalo->GetClassInfo()) )
597 {
598 valo = dynvalo->GetSuperClassInstance();
599 }
600
601 classInfo->SetProperty( o, propertyInfo->GetName().c_str(),
602 valClassInfo->ObjectPtrToAny(valo) );
603}
604
605void wxObjectRuntimeReaderCallback::SetConnect(int eventSourceObjectID,
606 const wxClassInfo *WXUNUSED(eventSourceClassInfo),
607 const wxPropertyInfo *delegateInfo,
608 const wxClassInfo *WXUNUSED(eventSinkClassInfo),
609 const wxHandlerInfo* handlerInfo,
610 int eventSinkObjectID )
611{
612 wxEvtHandler *ehsource =
613 wx_dynamic_cast( wxEvtHandler* , m_data->GetObject( eventSourceObjectID ) );
614 wxEvtHandler *ehsink =
615 wx_dynamic_cast( wxEvtHandler *,m_data->GetObject(eventSinkObjectID) );
616
617 if ( ehsource && ehsink )
618 {
619 const wxEventSourceTypeInfo *delegateTypeInfo =
620 wx_dynamic_cast(const wxEventSourceTypeInfo*,delegateInfo->GetTypeInfo());
621 if( delegateTypeInfo && delegateTypeInfo->GetLastEventType() == -1 )
622 {
623 ehsource->Connect( -1, delegateTypeInfo->GetEventType(),
624 handlerInfo->GetEventFunction(), NULL /*user data*/,
625 ehsink );
626 }
627 else
628 {
629 for ( wxEventType iter = delegateTypeInfo->GetEventType();
630 iter <= delegateTypeInfo->GetLastEventType(); ++iter )
631 {
632 ehsource->Connect( -1, iter,
633 handlerInfo->GetEventFunction(), NULL /*user data*/,
634 ehsink );
635 }
636 }
637 }
638}
639
640wxObject *wxObjectRuntimeReaderCallback::GetObject(int objectID)
641{
642 return m_data->GetObject( objectID );
643}
644
645void wxObjectRuntimeReaderCallback::AddToPropertyCollection( int objectID,
646 const wxClassInfo *classInfo,
647 const wxPropertyInfo* propertyInfo,
648 const wxAny &value)
649{
650 wxObject *o;
651 o = m_data->GetObject(objectID);
652 classInfo->AddToPropertyCollection( o, propertyInfo->GetName().c_str(), value );
653}
654
655void wxObjectRuntimeReaderCallback::AddToPropertyCollectionAsObject(int objectID,
656 const wxClassInfo *classInfo,
657 const wxPropertyInfo* propertyInfo,
658 int valueObjectId)
659{
660 wxObject *o, *valo;
661 o = m_data->GetObject(objectID);
662 valo = m_data->GetObject(valueObjectId);
663 const wxCollectionTypeInfo * collectionTypeInfo =
664 wx_dynamic_cast( const wxCollectionTypeInfo *, propertyInfo->GetTypeInfo() );
665 const wxClassInfo* valClassInfo =
666 (wx_dynamic_cast(const wxClassTypeInfo*,collectionTypeInfo->GetElementType()))->GetClassInfo();
667
668 // if this is a dynamic object and we are asked for another class
669 // than wxDynamicObject we cast it down manually.
670 wxDynamicObject *dynvalo = wx_dynamic_cast( wxDynamicObject *, valo);
671 if ( dynvalo!=NULL && (valClassInfo != dynvalo->GetClassInfo()) )
672 {
673 valo = dynvalo->GetSuperClassInstance();
674 }
675
676 classInfo->AddToPropertyCollection( o, propertyInfo->GetName().c_str(),
677 valClassInfo->ObjectPtrToAny(valo) );
678}
679
680#endif // wxUSE_EXTENDED_RTTI