File: RuntimeModel\JsonRuntimeFormat.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NuGet.Frameworks;
using NuGet.Versioning;

namespace NuGet.RuntimeModel
{
    public static class JsonRuntimeFormat
    {
        public static RuntimeGraph ReadRuntimeGraph(string filePath)
        {
            using (var fileStream = File.OpenRead(filePath))
            {
                return ReadRuntimeGraph(fileStream);
            }
        }

        public static RuntimeGraph ReadRuntimeGraph(Stream stream)
        {
            using (var streamReader = new StreamReader(stream))
            {
                return ReadRuntimeGraph(streamReader);
            }
        }

        public static RuntimeGraph ReadRuntimeGraph(TextReader textReader)
        {
            var loadSettings = new JsonLoadSettings()
            {
                LineInfoHandling = LineInfoHandling.Ignore,
                CommentHandling = CommentHandling.Ignore
            };

            using (var jsonReader = new JsonTextReader(textReader))
            {
                return ReadRuntimeGraph(JToken.Load(jsonReader, loadSettings));
            }
        }

        public static void WriteRuntimeGraph(string filePath, RuntimeGraph runtimeGraph)
        {
            using (var fileStream = new FileStream(filePath, FileMode.Create))
            using (var textWriter = new StreamWriter(fileStream))
            using (var jsonWriter = new JsonTextWriter(textWriter))
            using (var writer = new JsonObjectWriter(jsonWriter))
            {
                jsonWriter.Formatting = Formatting.Indented;
                jsonWriter.WriteStartObject();
                WriteRuntimeGraph(writer, runtimeGraph);
                jsonWriter.WriteEndObject();
            }
        }

        public static RuntimeGraph ReadRuntimeGraph(JToken json)
        {
            return new RuntimeGraph(
                EachProperty(json["runtimes"]).Select(ReadRuntimeDescription),
                EachProperty(json["supports"]).Select(ReadCompatibilityProfile));
        }

        public static void WriteRuntimeGraph(IObjectWriter writer, RuntimeGraph runtimeGraph)
        {
            if (runtimeGraph != null)
            {
                if (runtimeGraph.Runtimes.Any() == true)
                {
                    writer.WriteObjectStart("runtimes");

                    IOrderedEnumerable<RuntimeDescription> sortedRuntimes = runtimeGraph.Runtimes.Values
                        .OrderBy(runtime => runtime.RuntimeIdentifier, StringComparer.Ordinal);

                    foreach (RuntimeDescription runtime in sortedRuntimes)
                    {
                        WriteRuntimeDescription(writer, runtime);
                    }

                    writer.WriteObjectEnd();
                }

                if (runtimeGraph.Supports.Any() == true)
                {
                    writer.WriteObjectStart("supports");

                    IOrderedEnumerable<CompatibilityProfile> sortedSupports = runtimeGraph.Supports.Values
                        .OrderBy(runtime => runtime.Name, StringComparer.Ordinal);

                    foreach (CompatibilityProfile support in sortedSupports)
                    {
                        WriteCompatibilityProfile(writer, support);
                    }

                    writer.WriteObjectEnd();
                }
            }
        }

        private static void WriteRuntimeDescription(IObjectWriter writer, RuntimeDescription data)
        {
            writer.WriteObjectStart(data.RuntimeIdentifier);

            writer.WriteNameArray("#import", data.InheritedRuntimes);

            var sortedDependencySets = data.RuntimeDependencySets
                                           .OrderBy(pair => pair.Key, StringComparer.Ordinal)
                                           .Select(pair => pair.Value);

            foreach (var set in sortedDependencySets)
            {
                WriteRuntimeDependencySet(writer, set);
            }

            writer.WriteObjectEnd();
        }

        private static void WriteRuntimeDependencySet(IObjectWriter writer, RuntimeDependencySet data)
        {
            writer.WriteObjectStart(data.Id);

            var sortedDependencies = data.Dependencies
                                         .OrderBy(pair => pair.Key, StringComparer.Ordinal)
                                         .Select(pair => pair.Value);

            foreach (var dependency in sortedDependencies)
            {
                WritePackageDependency(writer, dependency);
            }

            writer.WriteObjectEnd();
        }

        private static void WritePackageDependency(IObjectWriter writer, RuntimePackageDependency data)
        {
            writer.WriteNameValue(data.Id, data.VersionRange.ToNormalizedString());
        }

        private static void WriteCompatibilityProfile(IObjectWriter writer, CompatibilityProfile data)
        {
            writer.WriteObjectStart(data.Name);

            var frameworkGroups = data.RestoreContexts.GroupBy(context => context.Framework);

            foreach (var frameworkGroup in frameworkGroups)
            {
                var name = frameworkGroup.Key.GetShortFolderName();
                var runtimes = frameworkGroup.ToList();
                if (runtimes.Count == 1)
                {
                    // Write a string
                    writer.WriteNameValue(name, runtimes[0].RuntimeIdentifier);
                }
                else if (runtimes.Count > 0)
                {
                    writer.WriteNameArray(name, runtimes.Select(rt => rt.RuntimeIdentifier));
                }
            }

            writer.WriteObjectEnd();
        }

        private static CompatibilityProfile ReadCompatibilityProfile(KeyValuePair<string, JToken> json)
        {
            var name = json.Key;
            var sets = new List<FrameworkRuntimePair>();
            foreach (var property in EachProperty(json.Value))
            {
                var profiles = ReadCompatibilitySets(property);
                sets.AddRange(profiles);
            }
            return new CompatibilityProfile(name, sets);
        }

        private static IEnumerable<FrameworkRuntimePair> ReadCompatibilitySets(KeyValuePair<string, JToken> property)
        {
            var framework = NuGetFramework.Parse(property.Key);
            switch (property.Value.Type)
            {
                case JTokenType.Array:
                    foreach (var value in (JArray)property.Value)
                    {
                        yield return new FrameworkRuntimePair(framework, value.Value<string>());
                    }
                    break;
                case JTokenType.String:
                    yield return new FrameworkRuntimePair(framework, property.Value.ToString());
                    break;
                    // Other token types are not supported
            }
        }

        private static RuntimeDescription ReadRuntimeDescription(KeyValuePair<string, JToken> json)
        {
            var name = json.Key;
            List<string>? inheritedRuntimes = null;
            List<RuntimeDependencySet>? additionalDependencies = null;
            foreach (var property in EachProperty(json.Value))
            {
                if (property.Key == "#import")
                {
                    var imports = (JArray)property.Value;
                    foreach (var import in imports)
                    {
                        inheritedRuntimes ??= new List<string>(imports.Count);
                        inheritedRuntimes.Add(import.Value<string>()!);
                    }
                }
                else
                {
                    var dependency = ReadRuntimeDependencySet(property);
                    additionalDependencies ??= new();
                    additionalDependencies.Add(dependency);
                }
            }
            return new RuntimeDescription(name, inheritedRuntimes, additionalDependencies);
        }

        private static RuntimeDependencySet ReadRuntimeDependencySet(KeyValuePair<string, JToken> json)
        {
            return new RuntimeDependencySet(
                json.Key,
                EachProperty(json.Value).Select(ReadRuntimePackageDependency));
        }

        private static RuntimePackageDependency ReadRuntimePackageDependency(KeyValuePair<string, JToken> json)
        {
            return new RuntimePackageDependency(json.Key, VersionRange.Parse(json.Value.Value<string>()!));
        }

        private static IEnumerable<KeyValuePair<string, JToken>> EachProperty(JToken? json)
        {
            return (json as IEnumerable<KeyValuePair<string, JToken>>)
                   ?? Enumerable.Empty<KeyValuePair<string, JToken>>();
        }
    }
}