< Summary

Class:FastMigrations.Runtime.FastMigrationsConverter
Assembly:FastMigrations.Runtime
File(s):/github/workspace/FastMigrations.Unity/Assets/FastMigrations/Runtime/LigthMigrationsConverter.cs
Covered lines:96
Uncovered lines:3
Coverable lines:99
Total lines:227
Line coverage:96.9% (96 of 99)
Covered branches:0
Total branches:0
Covered methods:9
Total methods:9
Method coverage:100% (9 of 9)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
FastMigrationsConverter(...)0%220100%
CanConvert(...)0%330100%
WriteJson(...)0%22093.33%
ReadJson(...)0%11.0111096.15%
RunMigrations(...)0%55095%
GetMigratableAttribute(...)0%220100%
GetMigrateMethod(...)0%440100%

File(s)

/github/workspace/FastMigrations.Unity/Assets/FastMigrations/Runtime/LigthMigrationsConverter.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Reflection;
 5using System.Threading;
 6using Newtonsoft.Json;
 7using Newtonsoft.Json.Linq;
 8
 9namespace FastMigrations.Runtime
 10{
 11    /// <summary>
 12    /// Variant of handling missing "Migrate_<see cref="MigratableAttribute.Version"/>(JObject data)" method
 13    /// </summary>
 14    /// <seealso cref="MigratorMissingMethodHandling"/>
 15    public enum MigratorMissingMethodHandling
 16    {
 17        /// <summary>Throws <see cref="MigrationException"/> if "Migrate_<see cref="MigratableAttribute.Version"/>(JObje
 18        ThrowException,
 19        /// <summary>Skips migration if "Migrate_<see cref="MigratableAttribute.Version"/>(JObject data)" method doesn't
 20        Ignore
 21    }
 22
 23    internal delegate JObject MigrateMethod(JObject data);
 24
 25    /// <summary>
 26    /// Thread safe JsonConverter who calls "Migrate_<see cref="MigratableAttribute.Version"/>(JObject data)" methods on
 27    ///
 28    /// All methods must have signature "private/protected static JObject Migrate_<see cref="MigratableAttribute.Version
 29    ///
 30    /// All methods will be called from current version to target version (inclusive).
 31    /// </summary>
 32    ///
 33    /// <remarks>
 34    /// By default all classes have <see cref="MigratableAttribute.Version"/> 0.
 35    /// You can mark all potentially migratable objects with <see cref="MigratableAttribute"/>.
 36    /// </remarks>
 37    ///
 38    /// <example>
 39    /// Methods you have to implement in your class:
 40    /// <code>
 41    /// [Migratable(1)]
 42    /// public class YouObjectType
 43    /// {
 44    ///    private static JObject Migrate_1(JObject data)
 45    ///    // OR
 46    ///    protected static JObject Migrate_1(JObject data)
 47    ///    // !public modifier is not allowed!
 48    /// }
 49    /// </code>
 50    /// How to add migrator to JsonConverter:
 51    /// <code>
 52    /// var migrator = new FastMigrationsConverterMock(MigratorMissingMethodHandling.ThrowException);
 53    /// var person = JsonConvert.DeserializeObject&lt;YouObjectType&gt;(json, migrator);
 54    /// // OR
 55    /// JsonConvert.DefaultSettings = () => new JsonSerializerSettings
 56    /// {
 57    ///     Converters = new List&lt;JsonConverter&gt; { new FastMigrationsConverter(MigratorMissingMethodHandling.Throw
 58    /// };
 59    /// </code>
 60    /// </example>
 61    ///
 62    /// <exception cref="MigrationException">"Migrate_<see cref="MigratableAttribute.Version"/>(JObject data)" method do
 63    public class FastMigrationsConverter : JsonConverter
 64    {
 3265        public override bool CanRead => true;
 266        public override bool CanWrite => true;
 67
 68        private readonly MigratorMissingMethodHandling _methodHandling;
 69
 70        private readonly ThreadLocal<HashSet<Type>> _migrationInProgress;
 71        private readonly IDictionary<Type, MigratableAttribute> _attributeByTypeCache;
 72        private readonly IDictionary<Type, IDictionary<int, MigrateMethod>> _migrateMethodsByType;
 73
 74        /// <param name="methodHandling">Variant of handling missing "Migrate_<see cref="MigratableAttribute.Version"/>(
 75        /// <seealso cref="MigratorMissingMethodHandling"/>
 2376        public FastMigrationsConverter(MigratorMissingMethodHandling methodHandling)
 2377        {
 4378            _migrationInProgress = new ThreadLocal<HashSet<Type>>(() => new HashSet<Type>());
 2379            _attributeByTypeCache = new ConcurrentDictionary<Type, MigratableAttribute>();
 2380            _migrateMethodsByType = new ConcurrentDictionary<Type, IDictionary<int, MigrateMethod>>();
 81
 2382            _methodHandling = methodHandling;
 2383        }
 84
 85        public override bool CanConvert(Type objectType)
 14386        {
 14387            MigratableAttribute attribute = GetMigratableAttribute(objectType, _attributeByTypeCache);
 88
 14389            if (attribute == null)
 7690                return false;
 91
 6792            if (attribute.Version == MigratorConstants.DefaultVersion)
 493                return false;
 94
 6395            return !_migrationInProgress.Value.Contains(objectType);
 14396        }
 97
 98        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
 299        {
 2100            Type valueType = value.GetType();
 101
 102            try
 2103            {
 2104                if (_migrationInProgress.Value.Contains(valueType))
 0105                    return;
 106
 2107                _migrationInProgress.Value.Add(valueType);
 108
 2109                var jObject = JObject.FromObject(value, serializer);
 2110                var migratableAttribute = GetMigratableAttribute(valueType, _attributeByTypeCache);
 2111                jObject.Add(MigratorConstants.VersionJsonFieldName, migratableAttribute.Version);
 2112                jObject.WriteTo(writer);
 2113            }
 114            finally
 2115            {
 2116                _migrationInProgress.Value.Remove(valueType);
 2117            }
 2118        }
 119
 120        public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
 121            JsonSerializer serializer)
 32122        {
 123            try
 32124            {
 32125                if (_migrationInProgress.Value.Contains(objectType))
 0126                    return existingValue;
 127
 32128                _migrationInProgress.Value.Add(objectType);
 129
 32130                var jObject = JObject.Load(reader);
 131
 132                //don't try and repeat migration for objects serialized as refs to previous
 32133                if (jObject["$ref"] != null)
 4134                    return serializer.ReferenceResolver?.ResolveReference(serializer, (string)jObject["$ref"]);
 135
 28136                int fromVersion = MigratorConstants.DefaultVersion;
 137
 28138                if (jObject.ContainsKey(MigratorConstants.VersionJsonFieldName))
 2139                    fromVersion = jObject[MigratorConstants.VersionJsonFieldName].ToObject<int>();
 140
 28141                var migratableAttribute = GetMigratableAttribute(objectType, _attributeByTypeCache);
 28142                uint toVersion = migratableAttribute.Version;
 143
 28144                if (toVersion + fromVersion != 0 && fromVersion != toVersion)
 27145                    jObject = RunMigrations(jObject, objectType, fromVersion, toVersion,
 146                        _methodHandling);
 147
 27148                if (existingValue != null && serializer.ObjectCreationHandling != ObjectCreationHandling.Replace)
 1149                {
 1150                    using (JsonReader jObjReader = jObject.CreateReader())
 1151                    {
 1152                        serializer.Populate(jObjReader, existingValue);
 1153                        return existingValue;
 154                    }
 155                }
 156
 26157                return jObject.ToObject(objectType, serializer);
 158            }
 159            finally
 32160            {
 32161                _migrationInProgress.Value.Remove(objectType);
 32162            }
 31163        }
 164
 165        private JObject RunMigrations(JObject jObject, Type objectType, int fromVersion,
 166            uint toVersion, MigratorMissingMethodHandling methodHandling)
 27167        {
 27168            fromVersion += MigratorConstants.MinVersionToStartMigration;
 169
 126170            for (int currVersion = fromVersion; currVersion <= toVersion; ++currVersion)
 37171            {
 37172                var migrationMethod = GetMigrateMethod(objectType, currVersion, _migrateMethodsByType);
 173
 37174                if (migrationMethod == null)
 9175                {
 9176                    switch (methodHandling)
 177                    {
 178                        case MigratorMissingMethodHandling.ThrowException:
 1179                        {
 1180                            var methodName = string.Format(MigratorConstants.MigrateMethodFormat, currVersion);
 1181                            throw new MigrationException($"Migration method {methodName} not found in {objectType.Name}"
 182                        }
 183                        case MigratorMissingMethodHandling.Ignore:
 8184                        {
 8185                            continue;
 186                        }
 187                    }
 0188                }
 189
 28190                jObject = migrationMethod(jObject);
 28191            }
 26192            return jObject;
 26193        }
 194
 195        private static MigratableAttribute GetMigratableAttribute(Type objectType, IDictionary<Type, MigratableAttribute
 173196        {
 173197            if (cache.TryGetValue(objectType, out MigratableAttribute attribute))
 97198                return attribute;
 199
 76200            attribute = (MigratableAttribute)objectType.GetCustomAttribute(typeof(MigratableAttribute), false);
 76201            cache[objectType] = attribute;
 76202            return attribute;
 173203        }
 204
 205        private static MigrateMethod GetMigrateMethod(Type objectType, int version, IDictionary<Type, IDictionary<int, M
 37206        {
 37207            if (!cache.TryGetValue(objectType, out IDictionary<int, MigrateMethod> methodsByVersion))
 21208            {
 21209                methodsByVersion = new ConcurrentDictionary<int, MigrateMethod>();
 21210                cache[objectType] = methodsByVersion;
 21211            }
 212
 37213            if (methodsByVersion.TryGetValue(version, out MigrateMethod method))
 6214                return method;
 215
 31216            var methodName = string.Format(MigratorConstants.MigrateMethodFormat, version);
 31217            var methodInfo = objectType.GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic);
 218
 31219            if (methodInfo == null)
 9220                return null;
 221
 22222            MigrateMethod newMethodDelegate = (MigrateMethod)methodInfo.CreateDelegate(typeof(MigrateMethod));
 22223            methodsByVersion[version] = newMethodDelegate;
 22224            return newMethodDelegate;
 37225        }
 226    }
 227}