File: Shared\Options\OptionBase.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 Newtonsoft.Json.Linq;
 
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
 
namespace Microsoft.Tools.ServiceModel.Svcutil
{
    internal class OptionBase
    {
        public OptionBase(string name, params string[] aliases) : this(name, null, aliases)
        {
        }
 
        /// <summary>
        /// .ctr
        /// </summary>
        /// <param name="name">the option main name.</param>
        /// <param name="originalValue">Mainly used for initializaing options which value is a collection.</param>
        /// <param name="aliases">Used for identifying the option with different names, this allows for supporting different property names in the JSON schema.</param>
        public OptionBase(string name, object originalValue, params string[] aliases)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException(nameof(name));
            }
 
            this.Name = name;
            _serializationName = name;
            _value = originalValue;
            this.Aliases = new List<string>();
 
            if (aliases != null && aliases.Length > 0)
            {
                this.Aliases.AddRange(aliases);
            }
 
            this.IsInitialized = true;
        }
 
        #region Properties and events
 
        // This property ensures an option is deserialized only once (in case it is defined multiple times in the JSON).
        internal bool IsInitialized { get; set; }
 
        // Aliases allows for deserializing a property that is referred to with a different name in the json file.
        public List<string> Aliases { get; private set; }
 
        public string Name { get; }
 
        private object _value;
        public object Value
        {
            get { return _value; }
            set { SetValue(value); }
        }
 
        // this is used in serialization to avoid serializing the option when unset. 
        public virtual bool HasValue { get { return this.Value != null; } }
 
        // this is used in serialization to avoid serializing the option when it set to its default value.
        // obseve that this can be different from the CLR type's default value (Enum with a default different from the first value). 
        public object DefaultValue { get; set; }
 
        // This allows for supporting a JSON schema with different property names (WCFCS vs SVCUTIL naming conventions).
        private string _serializationName;
        public string SerializationName
        {
            get { return _serializationName; }
            set { if (value != null && !Aliases.Contains(value)) Aliases.Add(value); _serializationName = value; }
        }
 
        // Determines whether the option can be serialized. 
        // This can be a static value for options that should not be automatically serialized like the 'options' options.
        private bool _canSerialize = true;
        public virtual bool CanSerialize
        {
            get { return _canSerialize && this.Value != null && !this.Value.Equals(this.DefaultValue); }
            set { _canSerialize = value; }
        }
 
        // events
        public event EventHandler<OptionEventArgs> ValueChanging;
        public event EventHandler<OptionEventArgs> ValueChanged;
 
        public event EventHandler<OptionEventArgs> Serializing;
        public event EventHandler<OptionEventArgs> Serialized;
 
        public event EventHandler<OptionDeserializingEventArgs> Deserializing;
        public event EventHandler<EventArgs> Deserialized;
        #endregion
 
        public bool HasSameId(string optionId)
        {
            return StringComparer.OrdinalIgnoreCase.Compare(this.Name, optionId) == 0 ||
                this.Aliases.Any(a => StringComparer.OrdinalIgnoreCase.Compare(a, optionId) == 0);
        }
 
        public virtual void CopyTo(OptionBase other)
        {
            // serialization name should not be copied as options may define different serialization names for their options.
            // other._serializationName = this._serializationName;
 
            // copy the canserialize field, not the property.  The field contains a hardcoded value while the property may be computed dynamically!
            other._canSerialize = _canSerialize;
 
            other.Value = this.Value;
            other.DefaultValue = this.DefaultValue;
 
            other.ValueChanging = this.ValueChanging;
            other.ValueChanged = this.ValueChanged;
            other.Serializing = this.Serializing;
            other.Serialized = this.Serialized;
            other.Deserializing = this.Deserializing;
            other.Deserialized = this.Deserialized;
        }
 
        public OptionBase Clone()
        {
            var other = new OptionBase((string)this.Name, this.Aliases.ToArray());
            CopyTo(other);
            return other;
        }
 
        protected virtual void SetValue(object value)
        {
            if (this.Value == null || !this.Value.Equals(value))
            {
                var oldValue = this.Value;
 
                // notify event handlers (if any) to pre-process the value.
                value = OnValueChanging(value);
 
                _value = value;
 
                if (oldValue != _value)
                {
                    OnValueChanged(oldValue);
                }
            }
        }
 
        internal void Deserialize(JToken jToken)
        {
            // Raise event for handlers to preprocess the JSON value.
            var value = OnDeserializing(jToken);
 
            // validate value; observe that null values are not allowed during deserialization.
            var stringValue = value as string;
            OptionValueParser.ThrowInvalidValueIf(value == null || (stringValue != null && string.IsNullOrWhiteSpace(stringValue)), jToken, this);
 
            _value = value;
            OnDeserialized();
        }
 
        internal void Serialize(JsonWriter writer, JsonSerializer serializer)
        {
            if (this.CanSerialize)
            {
                // ensure the value is converted to a primitive type or a collection of primitive types
                // that can be safely serialized.
                var value = OptionValueParser.GetSerializationValue(this.Value);
 
                // notify event handlers (if any) to pre-process the JSON value.
                value = OnSerializing(value);
 
                serializer.Serialize(writer, value, value?.GetType());
                OnSerialized(value);
            }
        }
 
        protected virtual object OnDeserializing(JToken jToken)
        {
            if (this.IsInitialized)
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Shared.Resources.ErrorOptionAlreadyDeserializedFormat, this.SerializationName));
            }
 
            object value = null;
            if (Deserializing != null)
            {
                var e = new OptionDeserializingEventArgs(jToken);
                Deserializing(this, e);
                value = e.Value;
            }
            return value;
        }
 
        protected virtual void OnDeserialized()
        {
            this.IsInitialized = true;
            this.Deserialized?.Invoke(this, new EventArgs());
        }
 
        protected virtual object OnSerializing(object value)
        {
            if (Serializing != null)
            {
                var e = new OptionEventArgs(value);
                Serializing(this, e);
                value = e.Value;
            }
            return value;
        }
 
        protected virtual void OnSerialized(object value)
        {
            // provide the actual serialized value.
            this.Serialized?.Invoke(this, new OptionEventArgs(value));
        }
 
        protected virtual object OnValueChanging(object value)
        {
            if (this.IsInitialized && this.ValueChanging != null)
            {
                var eventArgs = new OptionEventArgs(value);
                this.ValueChanging.Invoke(this, eventArgs);
                value = eventArgs.Value;
            }
            return value;
        }
 
        protected virtual void OnValueChanged(object oldValue)
        {
            if (this.IsInitialized)
            {
                this.ValueChanged?.Invoke(this, new OptionEventArgs(oldValue));
            }
        }
 
        public override string ToString()
        {
            var value = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", this.Name, this.Value?.ToString());
            return value;
        }
    }
 
    public class OptionEventArgs : EventArgs
    {
        public object Value { get; set; }
        public OptionEventArgs(object value)
        {
            Value = value;
        }
    }
 
    public class OptionDeserializingEventArgs : OptionEventArgs
    {
        public JToken JToken { get; set; }
        public OptionDeserializingEventArgs(JToken jToken) : base(null)
        {
            JToken = jToken;
        }
    }
}