diff Implab/ServiceLocator.cs @ 40:fe33f4e02ad5

improved tracing added text listeners (file,console)
author cin
date Tue, 15 Apr 2014 17:52:09 +0400
parents
children 9dd6a896a385
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/ServiceLocator.cs	Tue Apr 15 17:52:09 2014 +0400
@@ -0,0 +1,209 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+
+namespace Implab {
+    /// <summary>
+    /// Коллекция сервисов, позволяет регистрировать и получать сервисы.
+    /// </summary>
+    public class ServiceLocator: Component, IServiceLocator, IServiceProvider {
+        // запись об сервисе
+        struct ServiceEntry {
+            public object service; // сервис
+            public bool shared; // признак того, что сервис НЕ нужно освобождать
+            public Func<object> activator; // активатор сервиса при первом обращении
+            public List<Type> associated; // ссылки на текущую запись
+            public Type origin; // ссылка на оригинальную запись о сервисе
+        }
+
+        // словарь существующих сервисов
+        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>() {
+            return (T)GetService(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) {
+            AssertNotDisposed();
+
+            try {
+                service = GetService<T>();
+                return true;
+            } catch(KeyNotFoundException) {
+                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) {
+            if (serviceType == null)
+                throw new ArgumentNullException("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)
+                    throw new ApplicationException(String.Format("{0} doesn't provide {1} service",this,serviceType));
+
+                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)
+                return se.service;
+
+            // текущая запись является ссылкой
+            if (se.origin != null) {
+                se.service = GetService(se.origin);
+                m_services[serviceType] = se;
+                return se.service;
+            }
+
+            // текущая запись не является ссылкой и не имеет информации о сервисе
+            // она должна сожержать информацию об активации
+            if (se.activator != null) {
+                se.service = se.activator();
+
+                m_services[serviceType] = se;
+
+                return se.service;
+            }
+
+            throw new Exception("Unable to create a service instance");
+        }
+
+        /// <summary>
+        /// Регистрирует фабрику для активации сервиса по первому требованию.
+        /// </summary>
+        /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam>
+        /// <param name="activator">Фабрика для создания/получения объекта, предоставляющего сервис.</param>
+        /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception>
+        /// <remarks>При освобождении сервис-локатора, сервисы полученные в результате активации также будут освобождены.</remarks>
+        public void Register<T>(Func<T> activator) {
+            if (activator == null)
+                throw new ArgumentNullException("activator");
+
+            AssertNotDisposed();
+
+            Unregister(typeof(T));
+            
+            m_services[typeof(T)] = new ServiceEntry {
+                activator = () => activator() as object
+            };
+        }
+
+        /// <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>
+        /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception>
+        public void Register<T>(T service, bool shared) {
+            if (service == null)
+                throw new ArgumentNullException("service");
+
+            AssertNotDisposed();
+
+            Unregister(typeof(T));
+            
+            m_services[typeof(T)] = new ServiceEntry { service = service, shared = shared };
+        }
+
+        public void Unregister(Type serviceType) {
+            if (serviceType == null)
+                throw new ArgumentNullException("serviceType");
+
+            AssertNotDisposed();
+            
+            ServiceEntry se;
+            if (m_services.TryGetValue(serviceType, out se)) {
+                // освобождаем ресурсы
+                if (se.service != null && !se.shared)
+                    ((IDisposable)se.service).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)
+                    if (!entry.shared && entry.service is IDisposable)
+                        ((IDisposable)entry.service).Dispose();
+
+            }
+            base.Dispose(disposing);
+        }
+    }
+}
\ No newline at end of file