File: Shared\Options\ApplicationOptions.cs
Web Access
Project: src\src\dotnet-svcutil\lib\src\dotnet-svcutil-lib.csproj (dotnet-svcutil-lib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using Newtonsoft.Json;
 
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
 
namespace Microsoft.Tools.ServiceModel.Svcutil
{
    /// <summary>
    /// This is the options container OM base class and it defines the basic json schema (see options serializer class).
    /// Provides base serialization and option handling functionality.
    /// </summary>
    internal partial class ApplicationOptions : IOptionsSerializationHandler
    {
        // dictionary of options name/object, kept sorted for serialization purposes and to ease of troubleshooting.
        private SortedDictionary<string, OptionBase> PropertyBag { get; set; }
 
        // a list of value-parsing warngins (used mainly for deserialization)
        private List<string> _warnings { get; set; } = new List<string>();
        public IEnumerable<string> Warnings { get { return this._warnings; } }
 
        // a list of value-parsing errors (used mainly for deserialization)
        private List<Exception> _errors { get; set; } = new List<Exception>();
        public IEnumerable<Exception> Errors { get { return this._errors; } }
 
        public virtual string Json { get { return Serialize<ApplicationOptions, OptionsSerializer<ApplicationOptions>>(); } }
 
        #region option keys
        // basic json schema (see options serializer).
        public const string InputsKey = "inputs";
        public const string OptionsKey = "options";
        public const string ProviderIdKey = "providerId";
        public const string VersionKey = "version";
        #endregion
 
        #region properties
        public ListValue<Uri> Inputs { get { return GetValue<ListValue<Uri>>(InputsKey); } }
        private string Options { get { return GetValue<string>(OptionsKey); } set { SetValue(OptionsKey, value); } }
        public string ProviderId { get { return GetValue<string>(ProviderIdKey); } set { SetValue(ProviderIdKey, value); } }
        public string Version { get { return GetValue<string>(VersionKey); } set { SetValue(VersionKey, value); } }
        #endregion
 
        #region construction methods.
        public ApplicationOptions()
        {
            this.PropertyBag = new SortedDictionary<string, OptionBase>();
 
            // base options implementing the JSON schema.
            RegisterOptions(
                new ListValueOption<Uri>(InputsKey),
                new SingleValueOption<string>(OptionsKey) { CanSerialize = false },
                new SingleValueOption<string>(ProviderIdKey),
                new SingleValueOption<string>(VersionKey));
        }
 
        /// <summary>
        /// Options registration method.  
        /// This is the way a derived options class declare what options it cares about.
        /// This mechanism works well with the property bag and the serialization and cloning of options so that the container works only with the registered options 
        /// </summary>
        /// <param name="options"></param>
        protected void RegisterOptions(params OptionBase[] options)
        {
            foreach (var option in options)
            {
                var existingOption = GetOption(option.Name, throwOnMissing: false);
                if (existingOption != null)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Shared.Resources.ErrorOptionAlreadyRegisteredFormat, option.Name));
                }
                this.PropertyBag[option.Name] = option;
            }
        }
 
        /// <summary>
        /// Copy option values between different options container types.
        /// Observe that only the options that have been set in the source container are considered (see GetOptions), and only the registered options in the destination container are copied (see GetOption).
        /// </summary>
        /// <typeparam name="TOptionsBase"></typeparam>
        /// <param name="other"></param>
        public void CopyTo<TOptionsBase>(TOptionsBase other) where TOptionsBase : ApplicationOptions
        {
            foreach (var thisOption in this.GetOptions())
            {
                var otherOption = other.GetOption(thisOption.Name, throwOnMissing: false);
                if (otherOption != null)
                {
                    thisOption.CopyTo(otherOption);
                }
            }
        }
 
        public TOptionsBase CloneAs<TOptionsBase>() where TOptionsBase : ApplicationOptions, new()
        {
            var options = new TOptionsBase();
            this.CopyTo(options);
            return options;
        }
        #endregion
 
        #region serialization methods and events.
        protected string Serialize<TOptionsBase, TSerializer>() where TOptionsBase : ApplicationOptions, new()
                                                                where TSerializer : OptionsSerializer<TOptionsBase>, new()
        {
            var serializer = new TSerializer();
            string jsonText = JsonConvert.SerializeObject(this, Formatting.Indented, serializer);
            return jsonText;
        }
 
        protected static TOptionsBase Deserialize<TOptionsBase, TSerializer>(FileInfo fileInfo, bool throwOnError = true)
            where TOptionsBase : ApplicationOptions, new()
            where TSerializer : OptionsSerializer<TOptionsBase>, new()
        {
            return Deserialize<TOptionsBase, TSerializer>(null, fileInfo, throwOnError);
        }
 
        protected static TOptionsBase Deserialize<TOptionsBase, TSerializer>(string jsonText, bool throwOnError = true)
            where TOptionsBase : ApplicationOptions, new()
            where TSerializer : OptionsSerializer<TOptionsBase>, new()
        {
            return Deserialize<TOptionsBase, TSerializer>(jsonText, null, throwOnError);
        }
 
        private static TOptionsBase Deserialize<TOptionsBase, TSerializer>(string jsonText, FileInfo fileInfo, bool throwOnError = true)
            where TOptionsBase : ApplicationOptions, new()
            where TSerializer : OptionsSerializer<TOptionsBase>, new()
        {
            TOptionsBase options = new TOptionsBase();
 
            try
            {
                if (string.IsNullOrEmpty(jsonText))
                {
                    jsonText = File.ReadAllText(fileInfo.FullName);
                    fileInfo = null;
                }
                var serializer = new TSerializer();
                options = JsonConvert.DeserializeObject<TOptionsBase>(jsonText, serializer);
            }
            catch (Exception ex)
            {
                if (Utils.IsFatalOrUnexpected(ex)) throw;
                options._errors.Add(ex);
            }
 
            if (options._errors.Count > 0 && throwOnError)
            {
                var ex = new AggregateException(options.Errors);
                throw ex;
            }
 
            return options;
        }
 
        protected static TOptionsBase FromFile<TOptionsBase>(string filePath, bool throwOnError = true) where TOptionsBase : ApplicationOptions, new()
        {
            return Deserialize<TOptionsBase, OptionsSerializer<TOptionsBase>>(null, new FileInfo(filePath), throwOnError);
        }
 
        protected static TOptionsBase FromJson<TOptionsBase>(string jsonText, bool throwOnError = true) where TOptionsBase : ApplicationOptions, new()
        {
            return Deserialize<TOptionsBase, OptionsSerializer<TOptionsBase>>(jsonText, null, throwOnError);
        }
 
        public void Save(string filePath)
        {
            File.WriteAllText(filePath, this.Json);
        }
 
        protected virtual void OnBeforeSerialize() { }
        protected virtual void OnAfterSerialize() { }
        protected virtual void OnBeforeDeserialize() { ResetOptions(); }
        protected virtual void OnAfterDeserialize() { }
 
        void IOptionsSerializationHandler.RaiseBeforeSerializeEvent() { OnBeforeSerialize(); }
        void IOptionsSerializationHandler.RaiseAfterSerializeEvent() { OnAfterSerialize(); }
        void IOptionsSerializationHandler.RaiseBeforeDeserializeEvent() { OnBeforeDeserialize(); }
        void IOptionsSerializationHandler.RaiseAfterDeserializeEvent() { OnAfterDeserialize(); }
        #endregion
 
        #region options/values get/set
        public OptionBase GetOption(string optionId, bool throwOnMissing = true)
        {
            return GetOption<OptionBase>(optionId, throwOnMissing);
        }
 
        /// <summary>
        /// Returns the option requested by the option's ID, which can be the option's name or any of the option's aliases.
        /// The option type is casted to the specified generic type.
        /// </summary>
        public TOptionBase GetOption<TOptionBase>(string optionId, bool throwOnMissing = true) where TOptionBase : OptionBase
        {
            var option = this.PropertyBag.ContainsKey(optionId) ?
                this.PropertyBag[optionId] :
                this.PropertyBag.Values.FirstOrDefault(v => v.HasSameId(optionId));
 
            if (option == null && throwOnMissing)
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Shared.Resources.WarningUnrecognizedOptionFormat, optionId));
            }
 
            return (TOptionBase)option;
        }
 
        public bool TryGetOption(string optionId, out OptionBase option)
        {
            option = GetOption(optionId, throwOnMissing: false);
            return option != null;
        }
 
        /// <summary>
        /// Return the options that have been set.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<OptionBase> GetOptions(bool allOptions = false)
        {
            var requestedOptions = this.PropertyBag.Where(p =>
            {
                var option = p.Value;
                // exclude the 'options' option and return only the options that have been set.
                return option.Name != OptionsKey && (allOptions || option.HasValue);
            }).Select(o => o.Value);
 
            return requestedOptions;
        }
 
        public IEnumerable<OptionBase> GetAllOptions()
        {
            return GetOptions(allOptions: true);
        }
 
        /// <summary>
        /// Get the value of the specified option casted to the specified generic type.
        /// </summary>
        public TValue GetValue<TValue>(string optionId, bool throwOnMissing = true)
        {
            var option = GetOption(optionId, throwOnMissing);
            if (option != null && option.Value != null)
            {
                return (TValue)option.Value;
            }
            return default(TValue);
        }
 
        internal void SetValue(string optionId, object value)
        {
            var option = GetOption(optionId);
            if (value != option.Value)
            {
                option.Value = value;
            }
        }
 
        /// <summary>
        /// This is used when deserializing into an options object.  
        /// Option values set in the constructor must be reset so they can be initialized from the deserialized object,
        /// </summary>
        private void ResetOptions()
        {
            GetOption(OptionsKey).IsInitialized = false;
            foreach (var option in GetAllOptions())
            {
                option.IsInitialized = false;
            }
        }
        #endregion
 
        public void AddWarning(string warning, int idx = -1)
        {
            if (idx == -1)
            {
                this._warnings.Add(warning);
            }
            else
            {
                this._warnings.Insert(idx, warning);
            }
        }
 
        public void AddError(string error)
        {
            var ex = new ArgumentException(error);
            AddError(ex);
        }
 
        public void AddError(Exception ex)
        {
            this._errors.Add(ex);
        }
 
        public override string ToString()
        {
            return ToString(false);
        }
 
        public string ToString(bool prettyFormat)
        {
            var value = OptionsSerializer<ApplicationOptions>.SerializeToString(this, prettyFormat);
            return value;
        }
    }
}