/// <summary>Returns the processed value for the given type and property.</summary>
        /// <param name="content">The <see cref="IPublishedContent" /> to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo" /></param>
        /// <param name="config">The Ditto configuration for the type.</param>
        /// <param name="mappableProperty">Information about the mappable property.</param>
        /// <param name="instance">The instance to assign the value to.</param>
        /// <param name="chainContext">The <see cref="DittoChainContext"/> for the current processor chain.</param>
        /// <returns>The <see cref="object" /> representing the Umbraco value.</returns>
        private static object GetProcessedValue(
            IPublishedContent content,
            CultureInfo culture,
            DittoTypeInfo config,
            DittoTypeInfo.DittoTypePropertyInfo mappableProperty,
            object instance,
            DittoChainContext chainContext)
        {
            using (DittoDisposableTimer.DebugDuration(typeof(Ditto), $"Processing '{mappableProperty.PropertyInfo.Name}' ({content.Id})"))
            {
                // Create a base processor context for this current chain level
                var baseProcessorContext = new DittoProcessorContext
                {
                    Content      = content,
                    TargetType   = config.TargetType,
                    PropertyInfo = mappableProperty.PropertyInfo,
                    Culture      = culture
                };

                // Check for cache attribute
                if (mappableProperty.IsCacheable)
                {
                    var ctx = new DittoCacheContext(mappableProperty.CacheInfo, content, config.TargetType, mappableProperty.PropertyInfo, culture);
                    return(mappableProperty.CacheInfo.GetCacheItem(ctx, () => DoGetProcessedValue(content, mappableProperty, baseProcessorContext, chainContext)));
                }
                else
                {
                    return(DoGetProcessedValue(content, mappableProperty, baseProcessorContext, chainContext));
                }
            }
        }
        public static DittoTypeInfo GetOrAdd(Type type)
        {
            if (_cache.TryGetValue(type, out DittoTypeInfo config) == false)
            {
                config = DittoTypeInfo.Create(type);
                _cache.TryAdd(type, config);
            }

            return(config);
        }
 /// <summary>Fires off the various on converted events.</summary>
 /// <param name="content">The <see cref="IPublishedContent"/> to convert.</param>
 /// <param name="config">The Ditto configuration for the type.</param>
 /// <param name="culture">The culture.</param>
 /// <param name="instance">The instance to assign the value to.</param>
 /// <param name="callback">The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.</param>
 private static void OnConverted(
     IPublishedContent content,
     DittoTypeInfo config,
     CultureInfo culture,
     object instance,
     Action <DittoConversionHandlerContext> callback)
 {
     OnConvert <DittoOnConvertedAttribute>(
         DittoConversionHandlerType.OnConverted,
         content,
         config,
         culture,
         instance,
         callback);
 }
        /// <summary>Convenience method for calling converting/converter handlers.</summary>
        /// <typeparam name="TAttributeType">The type of the attribute type.</typeparam>
        /// <param name="conversionType">Type of the conversion.</param>
        /// <param name="content">The content.</param>
        /// <param name="config">The Ditto configuration for the type.</param>
        /// <param name="culture">The culture.</param>
        /// <param name="instance">The instance.</param>
        /// <param name="callback">The callback.</param>
        private static void OnConvert <TAttributeType>(
            DittoConversionHandlerType conversionType,
            IPublishedContent content,
            DittoTypeInfo config,
            CultureInfo culture,
            object instance,
            Action <DittoConversionHandlerContext> callback)
            where TAttributeType : Attribute
        {
            // Trigger conversion handlers
            var conversionCtx = new DittoConversionHandlerContext
            {
                Content   = content,
                Culture   = culture,
                ModelType = config.TargetType,
                Model     = instance
            };

            // Run the registered handlers
            foreach (var handler in config.ConversionHandlers)
            {
                handler.Run(conversionCtx, conversionType);
            }

            var methods = conversionType == DittoConversionHandlerType.OnConverting
                ? config.ConvertingMethods
                : config.ConvertedMethods;

            if (methods.Any())
            {
                foreach (var method in methods)
                {
                    // TODO: Review this, `Invoke` could be CPU heavy?!
                    method.Invoke(instance, new object[] { conversionCtx });
                    // Could we use a RuntimeMethodHandle?
                    // https://web.archive.org/web/20150118044646/http://msdn.microsoft.com:80/en-us/magazine/cc163759.aspx#S8
                }
            }

            // Check for a callback function
            callback?.Invoke(conversionCtx);
        }
        public static DittoTypeInfo Create(Type type)
        {
            var config = new DittoTypeInfo
            {
                TargetType = type
            };

            // constructor
            //
            // Check the validity of the mapped type constructor as early as possible.
            var constructorParams = type.GetConstructorParameters();

            if (constructorParams != null)
            {
                // Is it a PublishedContent or similar?
                if (constructorParams.Length == 1 && constructorParams[0].ParameterType == typeof(IPublishedContent))
                {
                    config.ConstructorHasPublishedContentParameter = true;
                }

                if (constructorParams.Length == 0 || config.ConstructorHasPublishedContentParameter)
                {
                    config.ConstructorIsValid = true;
                }
            }

            // No valid constructor, but see if the value can be cast to the type
            if (type.IsAssignableFrom(typeof(IPublishedContent)))
            {
                config.IsOfTypePublishedContent = true;
                config.ConstructorIsValid       = true;
            }

            // attributes
            //
            config.CustomAttributes = type.GetCustomAttributes();

            // cacheable
            //
            var conversionHandlers = new List <DittoConversionHandler>();

            foreach (var attr in config.CustomAttributes)
            {
                if (attr is DittoCacheAttribute)
                {
                    config.IsCacheable = true;
                    config.CacheInfo   = (DittoCacheAttribute)attr;
                }

                // Check for class level DittoConversionHandlerAttribute
                if (attr is DittoConversionHandlerAttribute)
                {
                    conversionHandlers.Add(((DittoConversionHandlerAttribute)attr).HandlerType.GetInstance <DittoConversionHandler>());
                }
            }

            // properties (lazy & eager)
            //
            var lazyProperties    = new List <DittoTypePropertyInfo>();
            var lazyPropertyNames = new List <string>();
            var eagerProperties   = new List <DittoTypePropertyInfo>();

            // Collect all the properties of the given type and loop through writable ones.
            foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (property.CanWrite == false)
                {
                    continue;
                }

                if (property.GetSetMethod() == null)
                {
                    continue;
                }

                var attributes = new List <Attribute>(property.GetCustomAttributes());

                if (attributes.Any(x => x is DittoIgnoreAttribute))
                {
                    continue;
                }

                var propertyType = property.PropertyType;

                var propertyConfig = new DittoTypePropertyInfo
                {
                    CustomAttributes = attributes,
                    PropertyInfo     = property,
                };


                // Check the property for any explicit processor attributes
                var processors = attributes.Where(x => x is DittoProcessorAttribute).Cast <DittoProcessorAttribute>().ToList();
                if (processors.Count == 0)
                {
                    // Adds the default processor for this conversion
                    var defaultProcessor = DittoProcessorRegistry.Instance.GetDefaultProcessorFor(type);
                    // Forces the default processor to be the very first processor
                    defaultProcessor.Order = -1;
                    processors.Add(defaultProcessor);
                }

                // Check for registered processors on the property's type
                processors.AddRange(propertyType.GetCustomAttributes <DittoProcessorAttribute>(true));

                // Check any type arguments in generic enumerable types.
                // This should return false against typeof(string) etc also.
                if (propertyType.IsCastableEnumerableType())
                {
                    propertyConfig.IsEnumerable   = true;
                    propertyConfig.EnumerableType = propertyType.GenericTypeArguments[0];

                    processors.AddRange(propertyConfig.EnumerableType.GetCustomAttributes <DittoProcessorAttribute>(true));
                }

                // Sort the order of the processors
                processors.Sort((x, y) => x.Order.CompareTo(y.Order));

                // Check for globally registered processors
                processors.AddRange(DittoProcessorRegistry.Instance.GetRegisteredProcessorAttributesFor(propertyType));

                // Add any core processors onto the end
                processors.AddRange(DittoProcessorRegistry.Instance.GetPostProcessorAttributes());

                propertyConfig.Processors = processors;


                var propertyCache = attributes.Where(x => x is DittoCacheAttribute).Cast <DittoCacheAttribute>().FirstOrDefault();
                if (propertyCache != null)
                {
                    propertyConfig.IsCacheable = true;
                    propertyConfig.CacheInfo   = propertyCache;
                }

                // detect if the property should be lazy-loaded
                if (property.ShouldAttemptLazyLoad())
                {
                    lazyProperties.Add(propertyConfig);
                    lazyPropertyNames.Add(property.Name);
                }
                else
                {
                    eagerProperties.Add(propertyConfig);
                }
            }

            if (lazyProperties.Count > 0)
            {
                config.ConstructorRequiresProxyType = true;
                config.HasLazyProperties            = true;
                config.LazyPropertyNames            = lazyPropertyNames; // lazyProperties.Select(x => x.PropertyInfo.Name);
                config.LazyProperties = lazyProperties;
            }

            if (eagerProperties.Count > 0)
            {
                config.HasEagerProperties = true;
                config.EagerProperties    = eagerProperties;
            }



            // conversion handlers
            //

            // Check for globaly registered handlers
            foreach (var handlerType in DittoConversionHandlerRegistry.Instance.GetRegisteredHandlerTypesFor(type))
            {
                conversionHandlers.Add(handlerType.GetInstance <DittoConversionHandler>());
            }

            config.ConversionHandlers = conversionHandlers;

            var before = new List <MethodInfo>();
            var after  = new List <MethodInfo>();

            // Check for method level DittoOnConvert[ing|ed]Attribute
            foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
            {
                var ing = method.GetCustomAttribute <DittoOnConvertingAttribute>();
                var ed  = method.GetCustomAttribute <DittoOnConvertedAttribute>();

                if (ing == null && ed == null)
                {
                    continue;
                }

                var p = method.GetParameters();
                if (p.Length == 1 && p[0].ParameterType == typeof(DittoConversionHandlerContext))
                {
                    if (ing != null)
                    {
                        before.Add(method);
                    }

                    if (ed != null)
                    {
                        after.Add(method);
                    }
                }
            }

            config.ConvertingMethods = before;
            config.ConvertedMethods  = after;


            return(config);
        }
        /// <summary>Returns an object representing the given <see cref="Type"/>.</summary>
        /// <param name="content">The <see cref="IPublishedContent"/> to convert.</param>
        /// <param name="config">The Ditto configuration for the type.</param>
        /// <param name="culture">The <see cref="CultureInfo"/></param>
        /// <param name="instance">An existing instance of T to populate</param>
        /// <param name="onConverting">The <see cref="Action{ConversionHandlerContext}"/> to fire when converting.</param>
        /// <param name="onConverted">The <see cref="Action{ConversionHandlerContext}"/> to fire when converted.</param>
        /// <param name="chainContext">The <see cref="DittoChainContext"/> for the current processor chain.</param>
        /// <returns>The converted <see cref="Object"/> as the given type.</returns>
        /// <exception cref="InvalidOperationException">Thrown if the given type has invalid constructors.</exception>
        private static object ConvertContent(
            IPublishedContent content,
            DittoTypeInfo config,
            CultureInfo culture,
            object instance,
            Action <DittoConversionHandlerContext> onConverting,
            Action <DittoConversionHandlerContext> onConverted,
            DittoChainContext chainContext)
        {
            // If not already an instance, create an instance of the object.
            if (instance == null)
            {
                // Check the validity of the mapped type constructor.
                if (config.ConstructorIsValid == false)
                {
                    throw new InvalidOperationException(
                              $"Cannot convert IPublishedContent to {config.TargetType} as it has no valid constructor. " +
                              "A valid constructor is either empty, or one accepting a single IPublishedContent parameter.");
                }

                // We can only proxy new instances.
                if (config.ConstructorRequiresProxyType)
                {
                    var factory = new ProxyFactory();
                    instance = config.ConstructorHasPublishedContentParameter
                        ? factory.CreateProxy(config.TargetType, config.LazyPropertyNames, content)
                        : factory.CreateProxy(config.TargetType, config.LazyPropertyNames);
                }
                else if (config.IsOfTypePublishedContent)
                {
                    instance = content;
                }
                else
                {
                    // 1: This extension method is about 7x faster than the native implementation.
                    // 2: Internally this uses Activator.CreateInstance which is heavily optimized.
                    instance = config.ConstructorHasPublishedContentParameter
                                                                 // TODO: Review this, as we could get the Constructor metadata from the "type-cache"?
                        ? config.TargetType.GetInstance(content) // 1
                        : config.TargetType.GetInstance();       // 2
                }
            }

            // We have the instance object but haven't yet populated properties
            // so fire the on converting event handlers
            OnConverting(content, config, culture, instance, onConverting);

            if (config.HasLazyProperties)
            {
                // A dictionary to store lazily invoked values.
                var lazyMappings = new Dictionary <string, Lazy <object> >();
                foreach (var lazyProperty in config.LazyProperties)
                {
                    // Configure lazy properties
                    lazyMappings.Add(lazyProperty.PropertyInfo.Name, new Lazy <object>(() => GetProcessedValue(content, culture, config, lazyProperty, instance, chainContext)));
                }

                ((IProxy)instance).Interceptor = new LazyInterceptor(lazyMappings);
            }

            // Process any eager properties
            if (config.HasEagerProperties)
            {
                foreach (var eagerProperty in config.EagerProperties)
                {
                    // Set the value normally.
                    var value = GetProcessedValue(content, culture, config, eagerProperty, instance, chainContext);

                    // This over 4x faster as propertyInfo.SetValue(instance, value, null);
                    FastPropertyAccessor.SetValue(eagerProperty.PropertyInfo, instance, value);
                }
            }

            // We have now finished populating the instance object so go ahead
            // and fire the on converted event handlers
            OnConverted(content, config, culture, instance, onConverted);

            return(instance);
        }
 public static void Add(Type type)
 {
     _cache.TryAdd(type, DittoTypeInfo.Create(type));
 }