view Implab/Components/ServiceLocator.cs @ 196:40d7fed4a09e

fixed promise chaining behavior, the error handler doesn't handle result or cancellation handlers exceptions these exceptions are propagated to the next handlers.
author cin
date Mon, 29 Aug 2016 23:15:51 +0300
parents 240aa6994018
children bdfdba6b645b
line wrap: on
line source

using System;
using System.Collections.Generic;

namespace Implab.Components {
    /// <summary>
    /// Коллекция сервисов, позволяет регистрировать и получать сервисы.
    /// </summary>
    public class ServiceLocator: Disposable, IServiceLocator, IServiceProvider {
        // запись о сервисе
        struct ServiceEntry : IDisposable {
            public object service; // сервис
            public bool shared; // признак того, что сервис НЕ нужно освобождать
            public Func<object> activator; // активатор сервиса при первом обращении
            public Action<object> cleanup; // функция для очистки сервиса
            public List<Type> associated; // ссылки на текущую запись
            public Type origin; // ссылка на оригинальную запись о сервисе

            #region IDisposable implementation

            public void Dispose() {
                if (shared)
                    return;
                if (cleanup != null) {
                    if (service != null)
                        cleanup(service);
                } else
                    Safe.Dispose(service);
            }

            #endregion
        }

        // словарь существующих сервисов
        readonly Dictionary<Type, ServiceEntry> m_services = new Dictionary<Type,ServiceEntry>();

        /// <summary>
        /// Получает объект предоставляющий сервис <typeparamref name="T"/>.
        /// </summary>
        /// <typeparam name="T">Тип запрашиваемого сервиса</typeparam>
        /// <returns>Объект, реализующий сервис</returns>
        /// <exception cref="KeyNotFoundException">Сервис не зарегистрирован</exception>
        public T GetService<T>() {
            object result;
            if (TryGetService(typeof(T), out result))
                return (T)result;
            throw new ApplicationException (String.Format ("{0} doesn't provide {1} service", this, typeof(T)));
        }


        /// <summary>
        /// Пытается получить указанный сервис, в случае, если компонента не предоставляет требуемый сервис
        /// не возникает исключений.
        /// </summary>
        /// <typeparam name="T">Тип требуемого сервиса.</typeparam>
        /// <param name="service">Объект реализующий сервис, или <c>default(T)</c> если такового нет.</param>
        /// <returns><c>true</c> - сервис найден, <c>false</c> - сервис не зарегистрирован.</returns>
        public bool TryGetService<T>(out T service) {
            object result;
            if (TryGetService(typeof(T), out result)) {
                service = (T)result;
                return true;
            }
            service = default(T);
            return false;
        }

        /// <summary>
        /// Получает объект предоставляющий сервис <paramref name="serviceType"/>
        /// </summary>
        /// <param name="serviceType">Тип запрашиваемого сервиса</param>
        /// <returns>Объект, реализующий сервис</returns>
        /// <exception cref="KeyNotFoundException">Сервис не зарегистрирован</exception>
		public object GetService(Type serviceType) {
            object result;
            if (TryGetService(serviceType, out result))
                return result;
			throw new ApplicationException (String.Format ("{0} doesn't provide {1} service", this, serviceType));
		}

		/// <summary>
		/// Пытается получить требуемый сервис или совместимый с ним.
		/// </summary>
		/// <returns><c>true</c>, если сервис был найден, <c>false</c> в противном случае..</returns>
		/// <param name="serviceType">Тип запрашиваемого сервиса.</param>
		/// <param name="service">Искомый сервис.</param>
		public virtual bool TryGetService(Type serviceType, out object service) {
            Safe.ArgumentNotNull(serviceType, "serviceType");
            AssertNotDisposed();

            ServiceEntry se;
            if (!m_services.TryGetValue(serviceType, out se)) {
                // ищем ближайщий объект, реализующий нужный сервис
                Type pt = null;
                foreach (var t in m_services.Keys)
                    if (serviceType.IsAssignableFrom(t) && (pt == null || t.IsAssignableFrom(pt)))
                        pt = t;

				if (pt == null) {
					// нет нужного сервиса
					service = null;
					return false;
				}

                var pe = m_services[pt];

                // найденная запись может ссылаться на оригинальную запись с сервисом
                if(pe.origin != null) {
                    pt = pe.origin;
                    pe = m_services[pt];
                }
                    
                // добавляем список с обратными ссылками
                if (pe.associated == null)
                    pe.associated = new List<Type>();

                pe.associated.Add(serviceType);

                // обновляем родительскую запись
                m_services[pt] = pe;

                // создаем запись со ссылкой
                se = new ServiceEntry {
                    service = pe.service,
                    origin = pt,
                    shared = true // предотвращаем множественные попытки освобождения
                };

                m_services[serviceType] = se;
            }

            // запись содержит в себе информацию о сервисе
			if (se.service != null) {
				service = se.service;
				return true;
			}

            // текущая запись является ссылкой
            if (se.origin != null) {
                se.service = GetService(se.origin);
                m_services[serviceType] = se;
				service = se.service;
				return true;
            }

            // текущая запись не является ссылкой и не имеет информации о сервисе
            // она должна сожержать информацию об активации
            if (se.activator != null) {
                se.service = se.activator();

                m_services[serviceType] = se;

				service = se.service;
				return true;
            }

			service = null;
			return false;
        }

        /// <summary>
        /// Регистрирует фабрику для активации сервиса по первому требованию.
        /// </summary>
        /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam>
        /// <param name="activator">Фабрика для создания/получения объекта, предоставляющего сервис.</param>
        /// <param name = "cleanup">Метод для освобождения экземпляра сервиса, будет вызыван при освобождении сервис-локатора.</param>
        /// <remarks>При освобождении сервис-локатора, сервисы полученные в результате активации также будут освобождены.</remarks>
        public void Register<T>(Func<T> activator, Action<T> cleanup) {
            Safe.ArgumentNotNull(activator, "activator");

            AssertNotDisposed();

            Unregister(typeof(T));
            
            var serviceEntry = new ServiceEntry();
            serviceEntry.activator = () => activator();
            if (cleanup != null)
                serviceEntry.cleanup = instance => cleanup((T)instance);
            m_services[typeof(T)] = serviceEntry;
        }

        public void Register<T>(Func<T> activator) {
            Register(activator, null);
        }

        /// <summary>
        /// Регистрирует объект, предоставляющий сервис.
        /// </summary>
        /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam>
        /// <param name="service">Объект, предоставляющий сервис.</param>
        /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception>
        /// <remarks>Сервис-локатором не управляет временем жизни объекта для зарегистрированного сервиса.</remarks>
        public void Register<T>(T service) {
            Register(service, true);
        }

        /// <summary>
        /// Регистрирует объект, предоставляющий сервис. Повторная регистрация отменяет уже существующую.
        /// </summary>
        /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam>
        /// <param name="service">Объект, предоставляющий сервис.</param>
        /// <param name="shared">Признак того, что объект является разделяемым и сервис-локатор не должен его освобождать.</param>
        public void Register<T>(T service, bool shared) {
            Safe.ArgumentNotNull(service, "service");

            AssertNotDisposed();

            Unregister(typeof(T));
            
            m_services[typeof(T)] = new ServiceEntry { service = service, shared = shared };
        }

        public void Unregister(Type serviceType) {
            Safe.ArgumentNotNull(serviceType, "serviceType");

            AssertNotDisposed();
            
            ServiceEntry se;
            if (m_services.TryGetValue(serviceType, out se)) {
                if (se.origin != null) {
                    var pe = m_services[se.origin];
                    pe.associated.Remove(serviceType);
                }
                // освобождаем ресурсы
                se.Dispose();
                m_services.Remove(serviceType);

                // убираем связанные записи
                if (se.associated != null)
                    foreach (var item in se.associated)
                        m_services.Remove(item);
            }
        }

        /// <summary>
        /// Освобождает зарегистрированные сервисы (которые требуется освобоить).
        /// </summary>
        /// <param name="disposing">Призанак того, что нужно освободить ресурсы.</param>
        protected override void Dispose(bool disposing) {
            if (disposing) {

                foreach (var entry in m_services.Values)
                    entry.Dispose();

            }
            base.Dispose(disposing);
        }
    }
}