[reflection] Coop handles icalls in System.Reflection and System.RuntimeTypeHandle...
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / Base / Core / EditingContext.cs
1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------
4
5 namespace System.Activities.Presentation 
6 {
7     using System.Activities.Presentation.Internal.Properties;
8     using System;
9     using System.Runtime;
10     using System.Collections.Generic;
11     using System.ComponentModel;
12     using System.Diagnostics;
13     using System.Globalization;
14     using System.Text;
15     using System.Activities.Presentation;
16
17     // <summary>
18     // The EditingContext class contains contextual state about a designer.  This includes permanent
19     // state such as list of services running in the designer.
20     // It also includes transient state consisting of context items.  Examples of transient
21     // context item state include the set of currently selected objects as well as the editing tool
22     // being used to manipulate objects on the design surface.
23     //
24     // The editing context is designed to be a concrete class for ease of use.  It does have a protected
25     // API that can be used to replace its implementation.
26     // </summary>
27     public class EditingContext : IDisposable 
28     {
29
30         private ContextItemManager _contextItems;
31         private ServiceManager _services;
32
33         // <summary>
34         // Creates a new editing context.
35         // </summary>
36         public EditingContext() 
37         {
38         }
39
40        
41         // <summary>
42         // The Disposing event gets fired just before the context gets disposed.
43         // </summary>
44         public event EventHandler Disposing;
45
46         // <summary>
47         // Returns the local collection of context items offered by this editing context.
48         // </summary>
49         // <value></value>
50         public ContextItemManager Items 
51         {
52             get {
53                 if (_contextItems == null) 
54                 {
55                     _contextItems = CreateContextItemManager();
56                     if (_contextItems == null) 
57                     {
58                         throw FxTrace.Exception.AsError(new InvalidOperationException(
59                             string.Format(CultureInfo.CurrentCulture, Resources.Error_NullImplementation, "CreateContextItemManager")));
60                     }
61                 }
62
63                 return _contextItems;
64             }
65         }
66
67         // <summary>
68         // Returns the service manager for this editing context.
69         // </summary>
70         // <value></value>
71         public ServiceManager Services 
72         {
73             get {
74                 if (_services == null) 
75                 {
76                     _services = CreateServiceManager();
77                     if (_services == null) 
78                     {
79                         throw FxTrace.Exception.AsError(new InvalidOperationException(
80                             string.Format(CultureInfo.CurrentCulture, Resources.Error_NullImplementation, "CreateServiceManager")));
81                     }
82                 }
83
84                 return _services;
85             }
86         }
87
88         // <summary>
89         // Creates an instance of the context item manager to be returned from
90         // the ContextItems property.  The default implementation creates a
91         // ContextItemManager that supports delayed activation of design editor
92         // managers through the declaration of a SubscribeContext attribute on
93         // the design editor manager.
94         // </summary>
95         // <returns>Returns an implementation of the ContextItemManager class.</returns>
96         protected virtual ContextItemManager CreateContextItemManager() 
97         {
98             return new DefaultContextItemManager(this);
99         }
100
101         // <summary>
102         // Creates an instance of the service manager to be returned from the
103         // Services property. The default implementation creates a ServiceManager
104         // that supports delayed activation of design editor managers through the
105         // declaration of a SubscribeService attribute on the design editor manager.
106         // </summary>
107         // <returns>Returns an implemetation of the ServiceManager class.</returns>
108         protected virtual ServiceManager CreateServiceManager() 
109         {
110             return new DefaultServiceManager();
111         }
112
113         // <summary>
114         // Disposes this editing context.
115         // </summary>
116         public void Dispose() 
117         {
118             Dispose(true);
119             GC.SuppressFinalize(this);
120         }
121
122         // <summary>
123         // Disposes this editing context.
124         // <param name="disposing">True if this object is being disposed, or false if it is finalizing.</param>
125         // </summary>
126         protected virtual void Dispose(bool disposing) 
127         {
128             if (disposing) 
129             {
130                 // Let any interested parties know the context is being disposed
131                 if (Disposing != null)
132                 {
133                     Disposing(this, EventArgs.Empty);
134                 }
135
136                 IDisposable d = _services as IDisposable;
137                 if (d != null) 
138                 {
139                     d.Dispose();
140                 }
141
142                 d = _contextItems as IDisposable;
143                 if (d != null) 
144                 {
145                     d.Dispose();
146                 }
147             }
148         }
149
150         // <summary>
151         // This is the default context item manager for our editing context.
152         // </summary>
153         private sealed class DefaultContextItemManager : ContextItemManager 
154         {
155             private EditingContext _context;
156             private DefaultContextLayer _currentLayer;
157             private Dictionary<Type, SubscribeContextCallback> _subscriptions;
158
159             internal DefaultContextItemManager(EditingContext context) 
160             {
161                 _context = context;
162                 _currentLayer = new DefaultContextLayer(null);
163             }
164
165             // <summary>
166             // This changes a context item to the given value.  It is illegal to pass
167             // null here.  If you want to set a context item to its empty value create
168             // an instance of the item using a default constructor.
169             // </summary>
170             // <param name="value"></param>
171             public override void SetValue(ContextItem value) 
172             {
173                 if (value == null) 
174                 {
175                     throw FxTrace.Exception.ArgumentNull("value");
176                 }
177
178                 // The rule for change is that we store the new value,
179                 // raise a change on the item, and then raise a change
180                 // to everyone else.  If changing the item fails, we recover
181                 // the previous item.
182                 ContextItem existing, existingRawValue;
183                 existing = existingRawValue = GetValueNull(value.ItemType);
184
185                 if (existing == null) 
186                 {
187                     existing = GetValue(value.ItemType);
188                 }
189
190                 bool success = false;
191
192                 try 
193                 {
194                     _currentLayer.Items[value.ItemType] = value;
195                     NotifyItemChanged(_context, value, existing);
196                     success = true;
197                 }
198                 finally 
199                 {
200                     if (success) 
201                     {
202                         OnItemChanged(value);
203                     }
204                     else 
205                     {
206                         // The item threw during its transition to 
207                         // becoming active.  Put the old one back.
208                         // We must put the old one back by re-activating
209                         // it.  This could throw a second time, so we
210                         // cover this case by removing the value first.
211                         // Should it throw again, we won't recurse because
212                         // the existing raw value would be null.
213
214                         _currentLayer.Items.Remove(value.ItemType);
215                         if (existingRawValue != null) 
216                         {
217                             SetValue(existingRawValue);
218                         }
219                     }
220                 }
221             }
222
223             // <summary>
224             // Returns true if the item manager contains an item of the given type.
225             // This only looks in the current layer.
226             // </summary>
227             // <param name="itemType"></param>
228             // <returns></returns>
229             public override bool Contains(Type itemType) 
230             {
231                 if (itemType == null) 
232                 {
233                     throw FxTrace.Exception.ArgumentNull("itemType");
234                 }
235                 if (!typeof(ContextItem).IsAssignableFrom(itemType)) 
236                 {
237                     throw FxTrace.Exception.AsError(new ArgumentException(
238                         string.Format(CultureInfo.CurrentCulture,
239                         Resources.Error_ArgIncorrectType,
240                         "itemType", typeof(ContextItem).FullName)));
241                 }
242
243                 return _currentLayer.Items.ContainsKey(itemType);
244             }
245
246             // <summary>
247             // Returns an instance of the requested item type.  If there is no context
248             // item with the given type, an empty item will be created.
249             // </summary>
250             // <param name="itemType"></param>
251             // <returns></returns>
252             public override ContextItem GetValue(Type itemType) 
253             {
254
255                 ContextItem item = GetValueNull(itemType);
256
257                 if (item == null) 
258                 {
259
260                     // Check the default item table and add a new
261                     // instance there if we need to
262                     if (!_currentLayer.DefaultItems.TryGetValue(itemType, out item)) 
263                     {
264                         item = (ContextItem)Activator.CreateInstance(itemType);
265
266                         // Verify that the resulting item has the correct item type
267                         // If it doesn't, it means that the user provided a derived
268                         // item type
269                         if (item.ItemType != itemType) 
270                         {
271                             throw FxTrace.Exception.AsError(new ArgumentException(string.Format(
272                                 CultureInfo.CurrentCulture,
273                                 Resources.Error_DerivedContextItem,
274                                 itemType.FullName,
275                                 item.ItemType.FullName)));
276                         }
277
278                         // Now push the item in the context so we have
279                         // a consistent reference
280                         _currentLayer.DefaultItems.Add(item.ItemType, item);
281                     }
282                 }
283
284                 return item;
285             }
286
287             // <summary>
288             // Similar to GetValue, but returns NULL if the item isn't found instead of
289             // creating an empty item.
290             // </summary>
291             // <param name="itemType"></param>
292             // <returns></returns>
293             private ContextItem GetValueNull(Type itemType) 
294             {
295
296                 if (itemType == null) 
297                 {
298                     throw FxTrace.Exception.ArgumentNull("itemType");
299                 }
300                 if (!typeof(ContextItem).IsAssignableFrom(itemType)) 
301                 {
302                     throw FxTrace.Exception.AsError(new ArgumentException(
303                         string.Format(CultureInfo.CurrentCulture,
304                         Resources.Error_ArgIncorrectType,
305                         "itemType", typeof(ContextItem).FullName)));
306                 }
307
308                 ContextItem item = null;
309                 DefaultContextLayer layer = _currentLayer;
310                 while (layer != null && !layer.Items.TryGetValue(itemType, out item)) 
311                 {
312                     layer = layer.ParentLayer;
313                 }
314
315                 return item;
316             }
317
318             // <summary>
319             // Enumerates the context items in the editing context.  This enumeration
320             // includes prior layers unless the enumerator hits an isolated layer.
321             // Enumeration is typically not useful in most scenarios but it is provided so
322             // that developers can search in the context and learn what is placed in it.
323             // </summary>
324             // <returns></returns>
325             public override IEnumerator<ContextItem> GetEnumerator() 
326             {
327                 return _currentLayer.Items.Values.GetEnumerator();
328             }
329
330             // <summary>
331             // Called when an item changes value.  This happens in one of two ways:
332             // either the user has called Change, or the user has removed a layer.
333             // </summary>
334             // <param name="item"></param>
335             private void OnItemChanged(ContextItem item) 
336             {
337                 SubscribeContextCallback callback;
338
339                 Fx.Assert(item != null, "You cannot pass a null item here.");
340
341                 if (_subscriptions != null && _subscriptions.TryGetValue(item.ItemType, out callback)) 
342                 {
343                     callback(item);
344                 }
345             }
346
347             // <summary>
348             // Adds an event callback that will be invoked with a context item of the given item type changes.
349             // </summary>
350             // <param name="contextItemType"></param>
351             // <param name="callback"></param>
352             public override void Subscribe(Type contextItemType, SubscribeContextCallback callback) 
353             {
354                 if (contextItemType == null) 
355                 {
356                     throw FxTrace.Exception.ArgumentNull("contextItemType");
357                 }
358                 if (callback == null) 
359                 {
360                     throw FxTrace.Exception.ArgumentNull("callback");
361                 }
362                 if (!typeof(ContextItem).IsAssignableFrom(contextItemType)) 
363                 {
364                     throw FxTrace.Exception.AsError(new ArgumentException(
365                         string.Format(CultureInfo.CurrentCulture,
366                         Resources.Error_ArgIncorrectType,
367                         "contextItemType", typeof(ContextItem).FullName)));
368                 }
369
370                 if (_subscriptions == null) 
371                 {
372                     _subscriptions = new Dictionary<Type, SubscribeContextCallback>();
373                 }
374
375                 SubscribeContextCallback existing = null;
376
377                 _subscriptions.TryGetValue(contextItemType, out existing);
378
379                 existing = (SubscribeContextCallback)Delegate.Combine(existing, callback);
380                 _subscriptions[contextItemType] = existing;
381
382                 // If the context is already present, invoke the callback.
383                 ContextItem item = GetValueNull(contextItemType);
384
385                 if (item != null) 
386                 {
387                     callback(item);
388                 }
389             }
390
391             // <summary>
392             //     Removes a subscription.
393             // </summary>
394             public override void Unsubscribe(Type contextItemType, SubscribeContextCallback callback) 
395             {
396
397                 if (contextItemType == null) 
398                 {
399                     throw FxTrace.Exception.ArgumentNull("contextItemType");
400                 }
401                 if (callback == null) 
402                 {
403                     throw FxTrace.Exception.ArgumentNull("callback");
404                 }
405                 if (!typeof(ContextItem).IsAssignableFrom(contextItemType)) 
406                 {
407                     throw FxTrace.Exception.AsError(new ArgumentException(
408                         string.Format(CultureInfo.CurrentCulture,
409                         Resources.Error_ArgIncorrectType,
410                         "contextItemType", typeof(ContextItem).FullName)));
411                 }
412                 if (_subscriptions != null) 
413                 {
414                     SubscribeContextCallback existing;
415                     if (_subscriptions.TryGetValue(contextItemType, out existing)) 
416                     {
417                         existing = (SubscribeContextCallback)RemoveCallback(existing, callback);
418                         if (existing == null) 
419                         {
420                             _subscriptions.Remove(contextItemType);
421                         }
422                         else 
423                         {
424                             _subscriptions[contextItemType] = existing;
425                         }
426                     }
427                 }
428             }
429
430             // <summary>
431             // This context layer contains our context items.
432             // </summary>
433             private class DefaultContextLayer 
434             {
435                 private DefaultContextLayer _parentLayer;
436                 private Dictionary<Type, ContextItem> _items;
437                 private Dictionary<Type, ContextItem> _defaultItems;
438
439                 internal DefaultContextLayer(DefaultContextLayer parentLayer) 
440                 {
441                     _parentLayer = parentLayer; // can be null
442                 }
443
444                 internal Dictionary<Type, ContextItem> DefaultItems 
445                 {
446                     get {
447                         if (_defaultItems == null) 
448                         {
449                             _defaultItems = new Dictionary<Type, ContextItem>();
450                         }
451                         return _defaultItems;
452                     }
453                 }
454
455                 internal Dictionary<Type, ContextItem> Items 
456                 {
457                     get {
458                         if (_items == null) 
459                         {
460                             _items = new Dictionary<Type, ContextItem>();
461                         }
462                         return _items;
463                     }
464                 }
465
466                 internal DefaultContextLayer ParentLayer 
467                 {
468                     get { return _parentLayer; }
469                 }
470             }
471         }
472
473         // <summary>
474         // This is the default service manager for our editing context.
475         // </summary>
476         private sealed class DefaultServiceManager : ServiceManager, IDisposable 
477         {
478             private static readonly object _recursionSentinel = new object();
479
480             private Dictionary<Type, object> _services;
481             private Dictionary<Type, SubscribeServiceCallback> _subscriptions;
482
483             internal DefaultServiceManager() 
484             {
485             }
486
487             // <summary>
488             // Returns true if the service manager contains a service of the given type.
489             // </summary>
490             // <param name="serviceType"></param>
491             // <returns></returns>
492             public override bool Contains(Type serviceType) 
493             {
494                 if (serviceType == null) 
495                 {
496                     throw FxTrace.Exception.ArgumentNull("serviceType");
497                 }
498                 return (_services != null && _services.ContainsKey(serviceType));
499             }
500
501             // <summary>
502             // Retrieves the requested service.  This method returns null if the service could not be located.
503             // </summary>
504             // <param name="serviceType"></param>
505             // <returns></returns>
506             public override object GetService(Type serviceType)
507             {
508                 object result = this.GetPublishedService(serviceType);
509                 if (result == null)
510                 {
511                     if (this.Contains(typeof(IServiceProvider)))
512                     {
513                         result = this.GetRequiredService<IServiceProvider>().GetService(serviceType);
514                         if (result != null)
515                         {
516                             this.Publish(serviceType, result);
517                         }
518                     }
519                 }
520                 return result;
521             }
522
523             object GetPublishedService(Type serviceType)
524             {
525                 object service = null;
526
527                 if (serviceType == null) 
528                 {
529                     throw FxTrace.Exception.ArgumentNull("serviceType");
530                 }
531
532                 if (_services != null && _services.TryGetValue(serviceType, out service)) 
533                 {
534
535                     // If this service is our recursion sentinel, it means that someone is recursing
536                     // while resolving a service callback.  Throw to break out of the recursion
537                     // cycle.
538                     if (service == _recursionSentinel) 
539                     {
540                         throw FxTrace.Exception.AsError(new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.Error_RecursionResolvingService, serviceType.FullName)));
541                     }
542
543                     // See if this service is a callback.  If it is, invoke it and store
544                     // the resulting service back in the dictionary.
545                     PublishServiceCallback callback = service as PublishServiceCallback;
546                     if (callback != null) 
547                     {
548
549                         // Store a recursion sentinel in the dictionary so we can easily
550                         // tell if someone is recursing
551                         _services[serviceType] = _recursionSentinel;
552                         try 
553                         {
554                             service = callback(serviceType);
555                             if (service == null) 
556                             {
557                                 throw FxTrace.Exception.AsError(new InvalidOperationException(
558                                     string.Format(CultureInfo.CurrentCulture,
559                                     Resources.Error_NullService,
560                                     callback.Method.DeclaringType.FullName,
561                                     serviceType.FullName)));
562                             }
563
564                             if (!serviceType.IsInstanceOfType(service)) 
565                             {
566                                 throw FxTrace.Exception.AsError(new InvalidOperationException(
567                                     string.Format(CultureInfo.CurrentCulture,
568                                     Resources.Error_IncorrectServiceType,
569                                     callback.Method.DeclaringType.FullName,
570                                     serviceType.FullName,
571                                     service.GetType().FullName)));
572                             }
573                         }
574                         finally 
575                         {
576                             // Note, this puts the callback back in place if it threw.
577                             _services[serviceType] = service;
578                         }
579                     }
580                 }
581
582                 // If the service is not found locally, do not walk up the parent chain.  
583                 // This was a major source of unreliability with the component model
584                 // design.  For a service to be accessible from the editing context, it
585                 // must be added.
586
587                 return service;
588             }
589
590             // <summary>
591             // Retrieves an enumerator that can be used to enumerate all of the services that this
592             // service manager publishes.
593             // </summary>
594             // <returns></returns>
595             public override IEnumerator<Type> GetEnumerator() 
596             {
597                 if (_services == null) 
598                 {
599                     _services = new Dictionary<Type, object>();
600                 }
601
602                 return _services.Keys.GetEnumerator();
603             }
604
605             // <summary>
606             // Calls back on the provided callback when someone has published the requested service.
607             // If the service was already available, this method invokes the callback immediately.
608             //
609             // A generic version of this method is provided for convience, and calls the non-generic
610             // method with appropriate casts.
611             // </summary>
612             // <param name="serviceType"></param>
613             // <param name="callback"></param>
614             public override void Subscribe(Type serviceType, SubscribeServiceCallback callback) 
615             {
616                 if (serviceType == null) 
617                 {
618                     throw FxTrace.Exception.ArgumentNull("serviceType");
619                 }
620                 if (callback == null) 
621                 {
622                     throw FxTrace.Exception.ArgumentNull("callback");
623                 }
624
625                 object service = GetService(serviceType);
626                 if (service != null) 
627                 {
628
629                     // If the service is already available, callback immediately
630                     callback(serviceType, service);
631                 }
632                 else 
633                 {
634
635                     // Otherwise, store this for later
636                     if (_subscriptions == null) 
637                     {
638                         _subscriptions = new Dictionary<Type, SubscribeServiceCallback>();
639                     }
640                     SubscribeServiceCallback existing = null;
641                     _subscriptions.TryGetValue(serviceType, out existing);
642                     existing = (SubscribeServiceCallback)Delegate.Combine(existing, callback);
643                     _subscriptions[serviceType] = existing;
644                 }
645             }
646
647             // <summary>
648             // Calls back on the provided callback when someone has published the requested service.
649             // If the service was already available, this method invokes the callback immediately.
650             //
651             // A generic version of this method is provided for convience, and calls the non-generic
652             // method with appropriate casts.
653             // </summary>
654             // <param name="serviceType"></param>
655             // <param name="callback"></param>
656             public override void Publish(Type serviceType, PublishServiceCallback callback) 
657             {
658                 if (serviceType == null) 
659                 {
660                     throw FxTrace.Exception.ArgumentNull("serviceType");
661                 }
662                 if (callback == null) 
663                 {
664                     throw FxTrace.Exception.ArgumentNull("callback");
665                 }
666
667                 Publish(serviceType, (object)callback);
668             }
669
670             // <summary>
671             //     If you already have an instance to a service, you can publish it here.
672             // </summary>
673             // <param name="serviceType"></param>
674             // <param name="serviceInstance"></param>
675             public override void Publish(Type serviceType, object serviceInstance) 
676             {
677                 if (serviceType == null) 
678                 {
679                     throw FxTrace.Exception.ArgumentNull("serviceType");
680                 }
681                 if (serviceInstance == null) 
682                 {
683                     throw FxTrace.Exception.ArgumentNull("serviceInstance");
684                 }
685
686                 if (!(serviceInstance is PublishServiceCallback) && !serviceType.IsInstanceOfType(serviceInstance)) 
687                 {
688                     throw FxTrace.Exception.AsError(new ArgumentException(
689                         string.Format(CultureInfo.CurrentCulture,
690                         Resources.Error_IncorrectServiceType,
691                         typeof(ServiceManager).Name,
692                         serviceType.FullName,
693                         serviceInstance.GetType().FullName)));
694                 }
695
696                 if (_services == null) 
697                 {
698                     _services = new Dictionary<Type, object>();
699                 }
700
701                 try 
702                 {
703                     _services.Add(serviceType, serviceInstance);
704                 }
705                 catch (ArgumentException e) 
706                 {
707                     throw FxTrace.Exception.AsError(new ArgumentException(string.Format(
708                         CultureInfo.CurrentCulture,
709                         Resources.Error_DuplicateService, serviceType.FullName), e));
710                 }
711
712                 // Now see if there were any subscriptions that required this service
713                 SubscribeServiceCallback subscribeCallback;
714                 if (_subscriptions != null && _subscriptions.TryGetValue(serviceType, out subscribeCallback)) 
715                 {
716                     subscribeCallback(serviceType, GetService(serviceType));
717                     _subscriptions.Remove(serviceType);
718                 }
719             }
720
721             // <summary>
722             //     Removes a subscription.
723             // </summary>
724             public override void Unsubscribe(Type serviceType, SubscribeServiceCallback callback) 
725             {
726
727                 if (serviceType == null) 
728                 {
729                     throw FxTrace.Exception.ArgumentNull("serviceType");
730                 }
731                 if (callback == null) 
732                 {
733                     throw FxTrace.Exception.ArgumentNull("callback");
734                 }
735
736                 if (_subscriptions != null) 
737                 {
738                     SubscribeServiceCallback existing;
739                     if (_subscriptions.TryGetValue(serviceType, out existing)) 
740                     {
741                         existing = (SubscribeServiceCallback)RemoveCallback(existing, callback);
742                         if (existing == null) 
743                         {
744                             _subscriptions.Remove(serviceType);
745                         }
746                         else 
747                         {
748                             _subscriptions[serviceType] = existing;
749                         }
750                     }
751                 }
752             }
753
754             // <summary>
755             // We implement IDisposable so that the editing context can destroy us when it
756             // shuts down.
757             // </summary>
758             void IDisposable.Dispose() 
759             {
760                 if (_services != null) 
761                 {
762                     Dictionary<Type, object> services = _services;
763
764                     try 
765                     {
766                         foreach (object value in services.Values) 
767                         {
768                             IDisposable d = value as IDisposable;
769                             if (d != null) 
770                             {
771                                 d.Dispose();
772                             }
773                         }
774                     }
775                     finally 
776                     {
777                         _services = null;
778                     }
779                 }
780             }
781         }
782     }
783 }