/// <summary>
        /// Adds a Plan to the cache.
        /// </summary>
        /// <param name="plan">The plan to add.</param>
        public static void AddPlan(MappingPlan plan)
        {
            var key = GetKey(plan);

            if (Current.Cache.ContainsKey(key))
            {
                Log.Debug($"MappingPlan: Plan for class {plan.TypeFullName} and template {plan.TemplateID} already cached. Replacing.", typeof(PlanCache));
                Current.Cache[key] = plan;
                return;
            }

            Log.Debug($"ModelMapping.PlanCache: Plan for class {plan.TypeFullName} added.", typeof(PlanCache));
            Current.Cache.Add(key, plan);
        }
        private static void MapItemFieldsAndCreatePlan(Item item, object model)
        {
            var type = model.GetType();
            var plan = new MappingPlan(type.FullName, item.TemplateID);

            item.Fields.ReadAll();

            // Here's the interesting part where we map Item fields to model properties.
            foreach (Field field in item.Fields)
            {
                if (ModelMapperConfiguration.Current.IgnoreStandardFields && field.Name.StartsWith("__"))
                {
                    continue;                     // save some time.
                }

                // Get field mappers
                var mappers = ModelMapperConfiguration.Current.GetMappersForFieldType(field.Type);

                try
                {
                    foreach (Type mapperType in mappers)
                    {
                        try
                        {
                            var mapper = (IFieldMapper)Activator.CreateInstance(mapperType);

                            var status = mapper.Map(model, field);

                            switch (status)
                            {
                            case FieldMapStatus.Exception:
                                Log.Error($"Mapping field {field.Name} on Item {item.Name}: Exception handled by field mapper.", typeof(ModelBuilder));
                                break;

                            case FieldMapStatus.TypeMismatch:
                                Log.Warn($"Mapping field {field.Name} on Item {item.Name} to Model {type.Name} failed. The cause of the failure is usually a type mismatch. Is the right field mapper running for this field?", typeof(ModelBuilder));
                                break;

                            case FieldMapStatus.ExplicitIgnore:
                                Log.Debug($"Mapping field {field.Name} on Item {item.Name}: explicitly ignored", typeof(ModelBuilder));
                                break;

                            case FieldMapStatus.NoProperty:
                                Log.Debug($"Mapping field {field.Name} on Item {item.Name}: no matching property name.", typeof(ModelBuilder));
                                break;

                            case FieldMapStatus.FieldEmpty:
                                Log.Debug($"Mapping field {field.Name} on Item {item.Name}: field was empty.", typeof(ModelBuilder));
                                plan.AddField(field.ID);
                                break;

                            case FieldMapStatus.ValueEmpty:
                                Log.Debug($"Mapping field {field.Name} on Item {item.Name}: processed value was empty.", typeof(ModelBuilder));
                                plan.AddField(field.ID);
                                break;

                            case FieldMapStatus.Success:
                                Log.Debug($"Mapping field {field.Name} on Item {item.Name}: success.", typeof(ModelBuilder));
                                plan.AddField(field.ID);
                                break;
                            }
                        }
                        catch (TypeLoadException ex)
                        {
                            Log.Error($"ModelMapper was unable to create FieldMapper type {mapperType.Name}", ex, typeof(ModelBuilder));
                            throw;
                        }
                        catch (Exception ex)
                        {
                            Log.Error($"Mapping field {field.Name} on Item {item.Name} to Model {type.Name} failed.", ex, typeof(ModelBuilder));
                            throw;
                        }
                    }

                    PlanCache.AddPlan(plan);
                }
                catch (Exception)
                {
                    Log.Warn($"ModelMapper did not cache the plan for {item.Name} because there were errors during the mapping process.", typeof(ModelBuilder));
                }
            }
        }
        private static void MapItemFieldsFromPlan(Item item, object model, MappingPlan plan)
        {
            var type = model.GetType();

            Log.Debug($"Mapping Item {item.Name} using a cached mapping plan for {type.FullName}", typeof(ModelBuilder));

            item.Fields.ReadAll();
            var fieldNames = plan.GetFieldIDs();

            // Here's the interesting part where we map Item fields to model properties.
            foreach (var fieldName in fieldNames)
            {
                var field = item.Fields[fieldName];

                // Get field mappers
                var mappers = ModelMapperConfiguration.Current.GetMappersForFieldType(field.Type);

                foreach (Type mapperType in mappers)
                {
                    try
                    {
                        var mapper = (IFieldMapper)Activator.CreateInstance(mapperType);

                        var status = mapper.Map(model, field);

                        switch (status)
                        {
                        case FieldMapStatus.Exception:
                            Log.Error($"Mapping field {field.Name} on Item {item.Name}: Exception handled by field mapper.", typeof(ModelBuilder));
                            break;

                        case FieldMapStatus.TypeMismatch:
                            Log.Warn($"Mapping field {field.Name} on Item {item.Name} to Model {type.Name} failed.", typeof(ModelBuilder));
                            break;

                        case FieldMapStatus.ExplicitIgnore:
                            Log.Debug($"Mapping field {field.Name} on Item {item.Name}: explicitly ignored", typeof(ModelBuilder));
                            break;

                        case FieldMapStatus.NoProperty:
                            Log.Debug($"Mapping field {field.Name} on Item {item.Name}: no matching property name.", typeof(ModelBuilder));
                            break;

                        case FieldMapStatus.FieldEmpty:
                            Log.Debug($"Mapping field {field.Name} on Item {item.Name}: field was empty.", typeof(ModelBuilder));
                            break;

                        case FieldMapStatus.ValueEmpty:
                            Log.Debug($"Mapping field {field.Name} on Item {item.Name}: processed value was empty.", typeof(ModelBuilder));
                            break;

                        case FieldMapStatus.Success:
                            Log.Debug($"Mapping field {field.Name} on Item {item.Name}: success.", typeof(ModelBuilder));
                            break;
                        }
                    }
                    catch (TypeLoadException ex)
                    {
                        Log.Error($"ModelMapper was unable to create FieldMapper type {mapperType.Name}", ex, typeof(ModelBuilder));
                    }
                    catch (Exception ex)
                    {
                        Log.Error($"Mapping field {field.Name} on Item {item.Name} to Model {type.Name} failed.", ex, typeof(ModelBuilder));
                    }
                }
            }
        }
 private static string GetKey(MappingPlan plan)
 {
     return(GetKey(plan.TypeFullName, plan.TemplateID));
 }