diff --git a/src/Hocon.API.Tests/HoconAPISpec.ApproveConfiguration.approved.txt b/src/Hocon.API.Tests/HoconAPISpec.ApproveConfiguration.approved.txt index 59ba6da2..fbec1921 100644 --- a/src/Hocon.API.Tests/HoconAPISpec.ApproveConfiguration.approved.txt +++ b/src/Hocon.API.Tests/HoconAPISpec.ApproveConfiguration.approved.txt @@ -7,30 +7,47 @@ namespace Hocon protected CDataConfigurationElement() { } protected override void DeserializeElement(System.Xml.XmlReader reader, bool serializeCollectionKey) { } } - public class Config : Hocon.HoconRoot, System.IEquatable, System.Runtime.Serialization.ISerializable + public class Config : System.Runtime.Serialization.ISerializable { - [System.ObsoleteAttribute("For json serialization/deserialization only", true)] - protected Config() { } - protected Config(Hocon.HoconValue value) { } - protected Config(Hocon.HoconValue value, Hocon.Config fallback) { } + public static readonly Hocon.Config Empty; public Config(Hocon.HoconRoot root) { } - public Config(Hocon.HoconRoot root, Hocon.Config fallback) { } + public Config(Hocon.Config source) { } + public Config(Hocon.Config source, Hocon.Config fallback) { } [System.ObsoleteAttribute("Used for serialization only", true)] - public Config(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public static Hocon.Config Empty { get; } - public virtual System.Collections.Generic.IReadOnlyList Fallbacks { get; } + protected Config(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + public Hocon.Config Fallback { get; } public virtual bool IsEmpty { get; } public Hocon.HoconValue Root { get; } - protected System.Collections.Generic.List _fallbacks { get; } - public override System.Collections.Generic.IEnumerable> AsEnumerable() { } - public virtual bool Equals(Hocon.Config other) { } - public override bool Equals(object obj) { } + public System.Collections.Generic.IEnumerable Substitutions { get; set; } + public Hocon.HoconValue Value { get; } + public virtual System.Collections.Generic.IEnumerable> AsEnumerable() { } + protected Hocon.Config Copy(Hocon.Config fallback = null) { } + public virtual bool GetBoolean(string path, bool default = False) { } + public virtual System.Collections.Generic.IList GetBooleanList(string path) { } + public virtual System.Collections.Generic.IList GetByteList(string path) { } + public virtual System.Nullable GetByteSize(string path) { } public virtual Hocon.Config GetConfig(string path) { } - public virtual Hocon.Config GetConfig(Hocon.HoconPath path) { } - protected override Hocon.HoconValue GetNode(Hocon.HoconPath path) { } + public virtual decimal GetDecimal(string path, [System.Runtime.CompilerServices.DecimalConstantAttribute(0, 0, 0u, 0u, 0u)] decimal @default) { } + public virtual System.Collections.Generic.IList GetDecimalList(string path) { } + public virtual double GetDouble(string path, double default = 0) { } + public virtual System.Collections.Generic.IList GetDoubleList(string path) { } + public virtual float GetFloat(string path, float default = 0) { } + public virtual System.Collections.Generic.IList GetFloatList(string path) { } + public virtual int GetInt(string path, int default = 0) { } + public virtual System.Collections.Generic.IList GetIntList(string path) { } + public virtual long GetLong(string path, long default = 0) { } + public virtual System.Collections.Generic.IList GetLongList(string path) { } public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public string ToString(bool useFallbackValues) { } - protected override bool TryGetNode(Hocon.HoconPath path, out Hocon.HoconValue result) { } + public virtual string GetString(string path, string default = null) { } + public virtual string GetString(Hocon.HoconPath path, string default = null) { } + public virtual System.Collections.Generic.IList GetStringList(string path) { } + public virtual System.Collections.Generic.IList GetStringList(Hocon.HoconPath path) { } + public virtual System.TimeSpan GetTimeSpan(string path, System.Nullable default = null, bool allowInfinite = True) { } + public Hocon.HoconValue GetValue(string path) { } + public virtual bool HasPath(string path) { } + public string PrettyPrint(int indentSize) { } + public override string ToString() { } + public string ToString(bool includeFallback) { } public virtual Hocon.Config WithFallback(Hocon.Config fallback) { } public static Hocon.Config +(Hocon.Config config, string fallback) { } public static Hocon.Config +(string configHocon, Hocon.Config fallbackConfig) { } diff --git a/src/Hocon.API.Tests/HoconAPISpec.ApproveCore.approved.txt b/src/Hocon.API.Tests/HoconAPISpec.ApproveCore.approved.txt index f33e6d5f..ac294bc9 100644 --- a/src/Hocon.API.Tests/HoconAPISpec.ApproveCore.approved.txt +++ b/src/Hocon.API.Tests/HoconAPISpec.ApproveCore.approved.txt @@ -18,6 +18,7 @@ namespace Hocon public Hocon.HoconType Type { get; } public void Add(Hocon.HoconValue value) { } public Hocon.IHoconElement Clone(Hocon.IHoconElement newParent) { } + public Hocon.IHoconElement Copy(Hocon.IHoconElement newParent) { } public bool Equals(Hocon.IHoconElement other) { } public override bool Equals(object obj) { } public System.Collections.Generic.IList GetArray() { } @@ -79,6 +80,7 @@ namespace Hocon public Hocon.HoconType Type { get; } public Hocon.HoconValue Value { get; } public Hocon.IHoconElement Clone(Hocon.IHoconElement newParent) { } + public Hocon.IHoconElement Copy(Hocon.IHoconElement newParent) { } public bool Equals(Hocon.IHoconElement other) { } public override bool Equals(object obj) { } public System.Collections.Generic.IList GetArray() { } @@ -107,6 +109,7 @@ namespace Hocon public abstract Hocon.HoconType Type { get; } public virtual string Value { get; } public abstract Hocon.IHoconElement Clone(Hocon.IHoconElement newParent); + public Hocon.IHoconElement Copy(Hocon.IHoconElement newParent) { } public bool Equals(Hocon.IHoconElement other) { } public override bool Equals(object obj) { } public System.Collections.Generic.IList GetArray() { } @@ -153,7 +156,7 @@ namespace Hocon public override string Value { get; } public override Hocon.IHoconElement Clone(Hocon.IHoconElement newParent) { } } - public class HoconObject : System.Collections.Generic.Dictionary, Hocon.IHoconElement, System.IEquatable + public class HoconObject : System.Collections.Generic.SortedDictionary, Hocon.IHoconElement, System.IEquatable { public HoconObject(Hocon.IHoconElement parent) { } public static Hocon.HoconObject Empty { get; } @@ -166,6 +169,7 @@ namespace Hocon public Hocon.HoconType Type { get; } public System.Collections.Generic.IDictionary Unwrapped { get; } public Hocon.IHoconElement Clone(Hocon.IHoconElement newParent) { } + public Hocon.IHoconElement Copy(Hocon.IHoconElement newParent) { } public bool Equals(Hocon.IHoconElement other) { } public override bool Equals(object obj) { } public void FallbackMerge(Hocon.HoconObject other) { } @@ -356,6 +360,7 @@ namespace Hocon public Hocon.HoconValue ResolvedValue { get; } public Hocon.HoconType Type { get; } public Hocon.IHoconElement Clone(Hocon.IHoconElement newParent) { } + public Hocon.IHoconElement Copy(Hocon.IHoconElement newParent) { } public bool Equals(Hocon.IHoconElement other) { } public override bool Equals(object obj) { } public System.Collections.Generic.IList GetArray() { } @@ -401,6 +406,7 @@ namespace Hocon { public HoconValue(Hocon.IHoconElement parent) { } public System.Collections.ObjectModel.ReadOnlyCollection Children { get; } + public bool IsEmpty { get; } public Hocon.IHoconElement Parent { get; } public virtual string Raw { get; } public Hocon.HoconType Type { get; } @@ -409,6 +415,7 @@ namespace Hocon public Hocon.HoconRoot AtKey(string key) { } public new void Clear() { } public virtual Hocon.IHoconElement Clone(Hocon.IHoconElement newParent) { } + public Hocon.IHoconElement Copy(Hocon.IHoconElement newParent) { } public virtual bool Equals(Hocon.IHoconElement other) { } public override bool Equals(object obj) { } public virtual System.Collections.Generic.IList GetArray() { } @@ -479,6 +486,7 @@ namespace Hocon string Raw { get; } Hocon.HoconType Type { get; } Hocon.IHoconElement Clone(Hocon.IHoconElement newParent); + Hocon.IHoconElement Copy(Hocon.IHoconElement newParent); System.Collections.Generic.IList GetArray(); Hocon.HoconObject GetObject(); string GetString(); diff --git a/src/Hocon.Configuration.Test/ConfigurationSpec.cs b/src/Hocon.Configuration.Test/ConfigurationSpec.cs index 04309a5f..63f47c2a 100644 --- a/src/Hocon.Configuration.Test/ConfigurationSpec.cs +++ b/src/Hocon.Configuration.Test/ConfigurationSpec.cs @@ -305,7 +305,7 @@ public void Fallback_should_not_be_modified() } }"); - var merged = config1.WithFallback(config2).Value.GetObject(); // Perform values loading + var merged = config1.WithFallback(config2).Root.GetObject(); // Perform values loading config1.GetInt("a.b.c").Should().Be(5); config1.GetInt("a.b.e").Should().Be(7); @@ -515,9 +515,9 @@ public void Config_will_not_throw_on_duplicate_fallbacks() // normal fallback var f1 = c1.WithFallback(c2).WithFallback(Config.Empty); - c1.Fallbacks.Count.Should().Be(0); // original copy should not have been modified. + c1.Fallback.Should().BeNull(); // original copy should not have been modified. - // someone adds the same fallback again with realizing it + // someone adds the same fallback again without realizing it f1.WithFallback(Config.Empty).GetString("bar.biz").Should().Be("fuber"); // shouldn't throw var final = f1.WithFallback(c2); @@ -539,7 +539,7 @@ public void Quoted_key_should_be_parsed() } "); - var megred = config2.WithFallback(config1).Value; + var megred = config2.WithFallback(config1).Root; // Is throwing at "/weird/*" key parsing megred.Invoking(r => r.GetObject()).Should().NotThrow(); } @@ -560,7 +560,7 @@ public void Quoted_key_with_dot_should_be_parsed() }" ); - var megred = config2.WithFallback(config1).Value; + var megred = config2.WithFallback(config1).Root; // Is throwing at "System.Byte[]" key parsing megred.Invoking(r => r.GetObject()).Should().NotThrow(); } @@ -585,7 +585,7 @@ public void HoconValue_GetObject_should_use_fallback_values_with_complex_objects var configWithFallback = config1.WithFallback(config2); var config = configWithFallback.GetConfig("akka.actor.deployment"); - var rootObj = config.Value.GetObject(); + var rootObj = config.Root.GetObject(); rootObj.Unwrapped.Should().ContainKeys("/worker1", "/worker2"); rootObj["/worker1.router"].Raw.Should().Be("round-robin-group1"); rootObj["/worker1.router"].Raw.Should().Be("round-robin-group1"); @@ -652,7 +652,7 @@ public void WithFallback_ShouldNotChangeOriginalConfig() a.WithFallback(b); a.WithFallback(b); - a.Fallbacks.Count.Should().Be(0); + a.Fallback.Should().BeNull(); a.GetString("akka.other-key", null).Should().BeNull(); ReferenceEquals(oldA, a).Should().BeTrue(); oldAContent.Should().Equals(a); @@ -687,8 +687,8 @@ public void WithFallback_ShouldMergeSubstitutionProperly() dedicated-thread-pool.substring = substring "); - var result = combined.Root.ToString(1, 2); - var expected = expectedConfig.Root.ToString(1, 2); + var result = combined.DumpConfig(false); + var expected = expectedConfig.DumpConfig(false); expected.Should().BeEquivalentTo(result); combined.GetInt("dedicated-thread-pool.thread-count").Should().Be(4); diff --git a/src/Hocon.Configuration.Test/SerializationSpecs.cs b/src/Hocon.Configuration.Test/SerializationSpecs.cs index 404f9492..c28a24c9 100644 --- a/src/Hocon.Configuration.Test/SerializationSpecs.cs +++ b/src/Hocon.Configuration.Test/SerializationSpecs.cs @@ -73,7 +73,7 @@ public void QuotedKeyWithInvalidCharactersShouldSerializeProperly() Assert.True(config.GetBoolean("this.\"should[]\".work")); Assert.True(deserialized.GetBoolean("this.\"should[]\".work")); - Assert.Equal(config, deserialized); + Assert.Equal(config.ToString(), deserialized.ToString()); } } diff --git a/src/Hocon.Configuration/Config.cs b/src/Hocon.Configuration/Config.cs index 2c417dcd..75b04574 100644 --- a/src/Hocon.Configuration/Config.cs +++ b/src/Hocon.Configuration/Config.cs @@ -1,320 +1,605 @@ -// ----------------------------------------------------------------------- +//----------------------------------------------------------------------- // -// Copyright (C) 2013 - 2020 .NET Foundation +// Copyright (C) 2009-2018 Lightbend Inc. +// Copyright (C) 2013-2018 .NET Foundation // -// ----------------------------------------------------------------------- +//----------------------------------------------------------------------- using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; namespace Hocon { /// - /// This class represents the main configuration object used by a project - /// when configuring objects within the system. To put it simply, it's - /// the internal representation of a HOCON (Human-Optimized Config Object Notation) - /// configuration string. + /// This class represents the main configuration object used by Akka.NET + /// when configuring objects within the system. To put it simply, it's + /// the internal representation of a HOCON (Human-Optimized Config Object Notation) + /// configuration string. /// [Serializable] - public class Config : HoconRoot, ISerializable, IEquatable + public class Config:ISerializable { - private static readonly HoconValue EmptyValue; + /// + /// A static "Empty" configuration we can use instead of null in some key areas. + /// + public static readonly Config Empty = new Config(); private const string SerializedPropertyName = "_data"; - static Config() + private Config() { - EmptyValue = new HoconValue(null); - EmptyValue.Add(new HoconObject(EmptyValue)); + var value = new HoconValue(null); + var obj = new HoconObject(value); + value.Add(obj); + Value = value; + + value = new HoconValue(null); + obj = new HoconObject(value); + value.Add(obj); + Root = value; + + Substitutions = Array.Empty(); } - [Obsolete("For json serialization/deserialization only", true)] - protected Config() + /// + /// Initializes a new instance of the class. + /// + /// The root node to base this configuration. + /// This exception is thrown if the given value is undefined. + public Config(HoconRoot root) { + if (root.Value == null) + throw new ArgumentNullException(nameof(root), "The root value cannot be null."); + + Value = root.Value; + Substitutions = root.Substitutions; + Root = (HoconValue)root.Value.Copy(null); } - /// /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected Config(HoconValue value) + /// The configuration to use as the primary source. + /// This exception is thrown if the given is undefined. + public Config(Config source) { - Value = (HoconValue)value.Clone(null); - Root = (HoconValue)value.Clone(null); + if (source == null) + throw new ArgumentNullException(nameof(source), "The source configuration cannot be null."); + + Value = source.Value; + Root = source.Root; + Substitutions = source.Substitutions; } - /// - protected Config(HoconValue value, Config fallback) : this(value) + /// + /// Initializes a new instance of the class. + /// + /// The configuration to use as the primary source. + /// The configuration to use as a secondary source. + /// This exception is thrown if the given is undefined. + public Config(Config source, Config fallback):this(source) { - MergeConfig(fallback); + Fallback = fallback; } - /// - /// The root node to base this configuration. - /// "The root value cannot be null." - public Config(HoconRoot root) + /// + /// The configuration used as a secondary source. + /// + public Config Fallback { get; private set; } + + /// + /// Determines if this root node contains any values + /// + public virtual bool IsEmpty { - Value = (HoconValue)root.Value.Clone(null); - Root = (HoconValue)root.Value.Clone(null); + get + { + if (Value.GetObject().Count == 0 && Fallback == null) + return true; + return false; + } + } - if (!(root is Config cfg)) - return; + /// + /// The root node of this configuration section + /// + public virtual HoconValue Root { get; private set; } - foreach(var value in cfg._fallbacks) + public HoconValue Value { get; private set; } + + /// + /// An enumeration of substitutions values + /// + public IEnumerable Substitutions { get; set; } + + /// + /// Generates a deep clone of the current configuration. + /// + /// A deep clone of the current configuration + protected Config Copy(Config fallback = null) + { + //deep clone + return new Config { - InsertFallbackValue(value); - } + Fallback = Fallback != null ? Fallback.Copy(fallback) : fallback, + Value = Value, + Root = Root, + Substitutions = Substitutions + }; } - /// - /// The configuration to use as the primary source. - /// The configuration to use as a secondary source. - /// The source configuration cannot be null. - public Config(HoconRoot root, Config fallback) : this(root) + private HoconValue GetNode(string path) + { + return GetNode(HoconPath.Parse(path)); + } + + private HoconValue GetNode(HoconPath path) + { + if (Root.GetObject().TryGetValue(path, out var result)) + return result; + return null; + } + + /// + /// Retrieves a boolean value from the specified path in the configuration. + /// + /// The path that contains the value to retrieve. + /// The default value to return if the value doesn't exist. + /// This exception is thrown if the current node is undefined. + /// The boolean value defined in the specified path. + public virtual bool GetBoolean(string path, bool @default = false) { - MergeConfig(fallback); + HoconValue value = GetNode(path); + if (value == null) + return @default; + + return value.GetBoolean(); } /// - /// Identical to . + /// Retrieves a long value, optionally suffixed with a 'b', from the specified path in the configuration. /// - /// - /// Added for brevity and API backwards-compatibility with Akka.Hocon. - /// - public static Config Empty => CreateEmpty(); + /// The path that contains the value to retrieve. + /// This exception is thrown if the current node is undefined. + /// The long value defined in the specified path. + public virtual long? GetByteSize(string path) + { + HoconValue value = GetNode(path); + if (value == null) return null; + return value.GetByteSize(); + } - protected List _fallbacks { get; } = new List(); - public virtual IReadOnlyList Fallbacks => _fallbacks.ToList().AsReadOnly(); + /// + /// Retrieves an integer value from the specified path in the configuration. + /// + /// The path that contains the value to retrieve. + /// The default value to return if the value doesn't exist. + /// This exception is thrown if the current node is undefined. + /// The integer value defined in the specified path. + public virtual int GetInt(string path, int @default = 0) + { + HoconValue value = GetNode(path); + if (value == null) + return @default; + + return value.GetInt(); + } /// - /// Determines if this root node contains any values + /// Retrieves a long value from the specified path in the configuration. /// - public virtual bool IsEmpty => Value == EmptyValue && _fallbacks.Count == 0; + /// The path that contains the value to retrieve. + /// The default value to return if the value doesn't exist. + /// This exception is thrown if the current node is undefined. + /// The long value defined in the specified path. + public virtual long GetLong(string path, long @default = 0) + { + HoconValue value = GetNode(path); + if (value == null) + return @default; - public HoconValue Root { get; } + return value.GetLong(); + } /// - /// Returns string representation of , allowing to include fallback values + /// Retrieves a string value from the specified path in the configuration. /// - /// If set to true, fallback values are included in the output - public string ToString(bool useFallbackValues) + /// The path that contains the value to retrieve. + /// The default value to return if the value doesn't exist. + /// This exception is thrown if the current node is undefined. + /// The string value defined in the specified path. + public virtual string GetString(string path, string @default = null) { - if (!useFallbackValues) - return Value.ToString(); + HoconValue value = GetNode(path); + if (value == null) + return @default; - return Root.ToString(); + return value.GetString(); } - protected override bool TryGetNode(HoconPath path, out HoconValue result) + public virtual string GetString(HoconPath path, string @default = null) { - result = null; - var currentObject = Value.GetObject(); - if (currentObject == null) - return false; - if (currentObject.TryGetValue(path, out result)) - return true; + HoconValue value = GetNode(path); + if (value == null) + return @default; - foreach (var value in _fallbacks) - { - currentObject = value.GetObject(); - if (currentObject == null) - return false; - if (currentObject.TryGetValue(path, out result)) - return true; - } + return value.GetString(); + } + + /// + /// Retrieves a float value from the specified path in the configuration. + /// + /// The path that contains the value to retrieve. + /// The default value to return if the value doesn't exist. + /// This exception is thrown if the current node is undefined. + /// The float value defined in the specified path. + public virtual float GetFloat(string path, float @default = 0) + { + HoconValue value = GetNode(path); + if (value == null) + return @default; + + return value.GetFloat(); + } + + /// + /// Retrieves a decimal value from the specified path in the configuration. + /// + /// The path that contains the value to retrieve. + /// The default value to return if the value doesn't exist. + /// This exception is thrown if the current node is undefined. + /// The decimal value defined in the specified path. + public virtual decimal GetDecimal(string path, decimal @default = 0) + { + HoconValue value = GetNode(path); + if (value == null) + return @default; - return false; + return value.GetDecimal(); } - protected override HoconValue GetNode(HoconPath path) + /// + /// Retrieves a double value from the specified path in the configuration. + /// + /// The path that contains the value to retrieve. + /// The default value to return if the value doesn't exist. + /// This exception is thrown if the current node is undefined. + /// The double value defined in the specified path. + public virtual double GetDouble(string path, double @default = 0) { - var currentObject = Value.GetObject(); - if (currentObject.TryGetValue(path, out var returnValue)) - return returnValue; + HoconValue value = GetNode(path); + if (value == null) + return @default; - foreach(var value in _fallbacks) - { - currentObject = value.GetObject(); - if (currentObject.TryGetValue(path, out returnValue)) - return returnValue; - } + return value.GetDouble(); + } + + /// + /// Retrieves a list of boolean values from the specified path in the configuration. + /// + /// The path that contains the values to retrieve. + /// This exception is thrown if the current node is undefined. + /// The list of boolean values defined in the specified path. + public virtual IList GetBooleanList(string path) + { + HoconValue value = GetNode(path); + return value.GetBooleanList(); + } - throw new HoconException($"Could not find accessible field at path `{path}` in all fallbacks."); + /// + /// Retrieves a list of decimal values from the specified path in the configuration. + /// + /// The path that contains the values to retrieve. + /// This exception is thrown if the current node is undefined. + /// The list of decimal values defined in the specified path. + public virtual IList GetDecimalList(string path) + { + HoconValue value = GetNode(path); + return value.GetDecimalList(); } /// - /// Retrieves a new configuration from the current configuration - /// with the root node being the supplied path. + /// Retrieves a list of float values from the specified path in the configuration. + /// + /// The path that contains the values to retrieve. + /// This exception is thrown if the current node is undefined. + /// The list of float values defined in the specified path. + public virtual IList GetFloatList(string path) + { + HoconValue value = GetNode(path); + return value.GetFloatList(); + } + + /// + /// Retrieves a list of double values from the specified path in the configuration. + /// + /// The path that contains the values to retrieve. + /// This exception is thrown if the current node is undefined. + /// The list of double values defined in the specified path. + public virtual IList GetDoubleList(string path) + { + HoconValue value = GetNode(path); + return value.GetDoubleList(); + } + + /// + /// Retrieves a list of int values from the specified path in the configuration. + /// + /// The path that contains the values to retrieve. + /// This exception is thrown if the current node is undefined. + /// The list of int values defined in the specified path. + public virtual IList GetIntList(string path) + { + HoconValue value = GetNode(path); + return value.GetIntList(); + } + + /// + /// Retrieves a list of long values from the specified path in the configuration. + /// + /// The path that contains the values to retrieve. + /// This exception is thrown if the current node is undefined. + /// The list of long values defined in the specified path. + public virtual IList GetLongList(string path) + { + HoconValue value = GetNode(path); + return value.GetLongList(); + } + + /// + /// Retrieves a list of byte values from the specified path in the configuration. + /// + /// The path that contains the values to retrieve. + /// This exception is thrown if the current node is undefined. + /// The list of byte values defined in the specified path. + public virtual IList GetByteList(string path) + { + HoconValue value = GetNode(path); + return value.GetByteList(); + } + + /// + /// Retrieves a list of string values from the specified path in the configuration. + /// + /// The path that contains the values to retrieve. + /// This exception is thrown if the current node is undefined. + /// The list of string values defined in the specified path. + public virtual IList GetStringList(string path) + { + HoconValue value = GetNode(path); + if (value == null) return new List(); + return value.GetStringList(); + } + + public virtual IList GetStringList(HoconPath path) + { + HoconValue value = GetNode(path); + if (value == null) return new List(); + return value.GetStringList(); + } + + /// + /// Retrieves a new configuration from the current configuration + /// with the root node being the supplied path. /// /// The path that contains the configuration to retrieve. + /// This exception is thrown if the current node is undefined. /// A new configuration with the root node being the supplied path. public virtual Config GetConfig(string path) { - return GetConfig(HoconPath.Parse(path)); + HoconValue value = GetNode(path); + if (Fallback != null) + { + Config f = Fallback.GetConfig(path); + if (value == null && f == null) + return null; + if (value == null) + return f; + + return new Config(new HoconRoot(value)).WithFallback(f); + } + + if (value == null) + return null; + + return new Config(new HoconRoot(value)); } - public virtual Config GetConfig(HoconPath path) + /// + /// Retrieves a from a specific path. + /// + /// The path that contains the value to retrieve. + /// This exception is thrown if the current node is undefined. + /// The found at the location if one exists, otherwise null. + public HoconValue GetValue(string path) { - if(Root.GetObject().TryGetValue(path, out var result)) - return new Config(result); - return Empty; + HoconValue value = GetNode(path); + return value; } /// - /// Configure the current configuration with a secondary source. - /// If the inserted configuration is already present in the fallback chain, - /// it will be moved to the end of the chain instead. + /// Retrieves a value from the specified path in the configuration. + /// + /// The path that contains the value to retrieve. + /// The default value to return if the value doesn't exist. + /// true if infinite timespans are allowed; otherwise false. + /// This exception is thrown if the current node is undefined. + /// The value defined in the specified path. + public virtual TimeSpan GetTimeSpan(string path, TimeSpan? @default = null, bool allowInfinite = true) + { + HoconValue value = GetNode(path); + if (value == null) + return @default.GetValueOrDefault(); + + return value.GetTimeSpan(allowInfinite); + } + + public string PrettyPrint(int indentSize) + { + return Root.ToString(1, indentSize); + } + + /// + /// Converts the current configuration to a string. + /// + /// A string containing the current configuration. + public override string ToString() + { + return Value == null ? "" : Value.ToString(); + } + + /// + /// Converts the current configuration to a string + /// + /// if true returns string with current config combined with fallback key-values else only current config key-values + /// TBD + public string ToString(bool includeFallback) + { + if (includeFallback == false) + return ToString(); + + return Root.ToString(); + } + + /// + /// Configure the current configuration with a secondary source. /// /// The configuration to use as a secondary source. + /// This exception is thrown if the given is a reference to this instance. /// The current configuration configured with the specified fallback. - /// Config can not have itself as fallback. public virtual Config WithFallback(Config fallback) { - if (ReferenceEquals(fallback, this)) + if (ReferenceEquals(this, fallback)) throw new ArgumentException("Config can not have itself as fallback", nameof(fallback)); if (fallback.IsNullOrEmpty()) - return this; // no-op - - if (IsEmpty) - return fallback; + return this; - return new Config(this, fallback); - } - - private void MergeConfig(Config other) - { - InsertFallbackValue(other.Value); - foreach (var fallbackValue in other.Fallbacks) + var current = this; + while(current.Fallback != null) { - InsertFallbackValue(fallbackValue); + current = current.Fallback; + if (current.Equals(fallback.Root)) + return this; } + + var newRoot = new HoconValue(null); + var mergedRoot = (HoconObject)fallback.Root.GetObject().Clone(newRoot); + mergedRoot.Merge(Root.GetObject()); + newRoot.Add(mergedRoot); + + var mergedConfig = Copy(fallback); + mergedConfig.Root = newRoot; + return mergedConfig; } - private void InsertFallbackValue(HoconValue value) + /// + /// Determine if a HOCON configuration element exists at the specified location + /// + /// The location to check for a configuration value. + /// This exception is thrown if the current node is undefined. + /// true if a value was found, false otherwise. + public virtual bool HasPath(string path) { - HoconValue duplicateValue = null; - foreach(var fallbackValue in _fallbacks) - { - if(fallbackValue == value) - { - duplicateValue = fallbackValue; - break; - } - } - if (duplicateValue == null) - { - var clone = (HoconValue)value.Clone(null); - _fallbacks.Add(clone); - Root.GetObject().FallbackMerge(clone.GetObject()); - } + HoconValue value = GetNode(path); + return value != null; } /// - /// Adds the supplied configuration string as a fallback to the supplied configuration. + /// Adds the supplied configuration string as a fallback to the supplied configuration. /// /// The configuration used as the source. /// The string used as the fallback configuration. /// The supplied configuration configured with the supplied fallback. public static Config operator +(Config config, string fallback) { - return config.WithFallback(HoconConfigurationFactory.ParseString(fallback)); + Config fallbackConfig = HoconConfigurationFactory.ParseString(fallback); + return config.WithFallback(fallbackConfig); } /// - /// Adds the supplied configuration as a fallback to the supplied configuration string. + /// Adds the supplied configuration as a fallback to the supplied configuration string. /// /// The configuration string used as the source. /// The configuration used as the fallback. /// A configuration configured with the supplied fallback. public static Config operator +(string configHocon, Config fallbackConfig) { - return HoconConfigurationFactory.ParseString(configHocon).WithFallback(fallbackConfig); + Config config = HoconConfigurationFactory.ParseString(configHocon); + return config.WithFallback(fallbackConfig); } /// - /// Performs an implicit conversion from to . + /// Performs an implicit conversion from to . /// /// The string that contains a configuration. /// A configuration based on the supplied string. public static implicit operator Config(string str) { - return HoconConfigurationFactory.ParseString(str); + Config config = HoconConfigurationFactory.ParseString(str); + return config; } - /// - public override IEnumerable> AsEnumerable() + /// + /// Retrieves an enumerable key value pair representation of the current configuration. + /// + /// The current configuration represented as an enumerable key value pair. + public virtual IEnumerable> AsEnumerable() { - foreach (var kvp in Root.GetObject()) + var used = new HashSet(); + Config current = this; + while (current != null) { - yield return kvp; + foreach (var kvp in current.Root.GetObject()) + { + if (!used.Contains(kvp.Key)) + { + yield return new KeyValuePair(kvp.Key, kvp.Value.Value); + used.Add(kvp.Key); + } + } + current = current.Fallback; } } - private static Config CreateEmpty() - { - var value = new HoconValue(null); - value.Add(new HoconObject(value)); - return new Config(value); - } - - /// public void GetObjectData(SerializationInfo info, StreamingContext context) { - info.AddValue(SerializedPropertyName, ToString(useFallbackValues: true), typeof(string)); - } - - public virtual bool Equals(Config other) - { - if (IsEmpty && other.IsEmpty) return true; - if (Value == other.Value) return true; - return false; - } - - public override bool Equals(object obj) - { - if (obj == null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj is Config cfg) - return Equals(cfg); - return false; + info.AddValue(SerializedPropertyName, ToString(includeFallback: true), typeof(string)); } [Obsolete("Used for serialization only", true)] - public Config(SerializationInfo info, StreamingContext context):base(null) + protected Config(SerializationInfo info, StreamingContext context) { var config = HoconConfigurationFactory.ParseString(info.GetValue(SerializedPropertyName, typeof(string)) as string); - + Value = config.Value; - _fallbacks.AddRange(config._fallbacks); + Root = config.Root; + Substitutions = config.Substitutions; } } /// - /// This class contains convenience methods for working with . + /// This class contains convenience methods for working with . /// public static class ConfigExtensions { /// - /// Retrieves the current configuration or the fallback - /// configuration if the current one is null. + /// Retrieves the current configuration or the fallback + /// configuration if the current one is null. /// /// The configuration used as the source. /// The configuration to use as a secondary source. /// The current configuration or the fallback configuration if the current one is null. public static Config SafeWithFallback(this Config config, Config fallback) { - return config.IsNullOrEmpty() - ? fallback - : config == fallback - ? config - : config.WithFallback(fallback); + return config.IsNullOrEmpty() + ? fallback.IsNullOrEmpty() ? Config.Empty : fallback + : ReferenceEquals(config, fallback) ? config : config.WithFallback(fallback); } /// - /// Determines if the supplied configuration has any usable content period. + /// Determines if the supplied configuration has any usable content period. /// /// The configuration used as the source. /// true> if the is null or ; otherwise false. @@ -322,5 +607,6 @@ public static bool IsNullOrEmpty(this Config config) { return config == null || config.IsEmpty; } + } } \ No newline at end of file diff --git a/src/Hocon.Configuration/DebuggingExtensions.cs b/src/Hocon.Configuration/DebuggingExtensions.cs index 742fda73..5f2260dc 100644 --- a/src/Hocon.Configuration/DebuggingExtensions.cs +++ b/src/Hocon.Configuration/DebuggingExtensions.cs @@ -23,7 +23,7 @@ public static string DumpConfig(this Config c, bool dumpAsFallbacks = true) var sb = new StringBuilder(); if(!dumpAsFallbacks) { - sb.AppendLine(c.Root.ToString(1, 2)); + sb.AppendLine(c.Root.GetObject().ToString(1, 2)); return sb.ToString(); } @@ -37,13 +37,16 @@ void AppendHocon(HoconValue value, int i) .AppendLine(); } - AppendHocon(c.Value, hoconCount); + var currentConfig = c; + AppendHocon(currentConfig.Value, hoconCount); - foreach(var fallback in c.Fallbacks) + while (currentConfig.Fallback != null) { + currentConfig = currentConfig.Fallback; + // add a header here sb.AppendLine().AppendLine("------------"); - AppendHocon(fallback, ++hoconCount); + AppendHocon(currentConfig.Value, ++hoconCount); } return sb.ToString(); diff --git a/src/Hocon/HoconParser.cs b/src/Hocon/HoconParser.cs index 1edb46fb..585f00d4 100644 --- a/src/Hocon/HoconParser.cs +++ b/src/Hocon/HoconParser.cs @@ -177,6 +177,7 @@ private HoconValue ResolveSubstitution(HoconSubstitution sub) if (IsValueCyclic(subField, sub)) throw new HoconException("A cyclic substitution loop is detected in the Hocon file."); + // TODO: is a full clone needed? or will a copy suffice? // third case, regular substitution _root.GetObject().TryGetValue(sub.Path, out var field); return field?.Clone(field.Parent) as HoconValue; @@ -773,6 +774,7 @@ private HoconArray ParsePlusEqualAssignArray(IHoconElement owner) "Invalid Hocon include. Hocon config substitution type must be the same as the field it's merged into. " + $"Expected type: `{currentArray.Type}`, type returned by include callback: `{includeValue.Type}`"); + // TODO: Is a clone needed? an include object is supposed to be discardable, maybe a direct reference is more appropriate? currentArray.Add((HoconValue) includeValue.Clone(currentArray)); break; diff --git a/src/Hocon/Impl/HoconArray.cs b/src/Hocon/Impl/HoconArray.cs index eb36a203..44d9434a 100644 --- a/src/Hocon/Impl/HoconArray.cs +++ b/src/Hocon/Impl/HoconArray.cs @@ -69,6 +69,14 @@ public IHoconElement Clone(IHoconElement newParent) return newArray; } + /// + public IHoconElement Copy(IHoconElement newParent) + { + var newArray = new HoconArray(newParent); + newArray.AddRange(this); + return newArray; + } + public bool Equals(IHoconElement other) { if (other is null) return false; diff --git a/src/Hocon/Impl/HoconField.cs b/src/Hocon/Impl/HoconField.cs index 11d3a47f..cea3d510 100644 --- a/src/Hocon/Impl/HoconField.cs +++ b/src/Hocon/Impl/HoconField.cs @@ -100,6 +100,14 @@ public IHoconElement Clone(IHoconElement newParent) return newField; } + /// + public IHoconElement Copy(IHoconElement newParent) + { + var newField = new HoconField(Key, (HoconObject)newParent); + newField._internalValues.AddRange(_internalValues); + return newField; + } + public string ToString(int indent, int indentSize) { return Value.ToString(indent, indentSize); diff --git a/src/Hocon/Impl/HoconLiteral.cs b/src/Hocon/Impl/HoconLiteral.cs index 46b7787b..2fa61052 100644 --- a/src/Hocon/Impl/HoconLiteral.cs +++ b/src/Hocon/Impl/HoconLiteral.cs @@ -149,6 +149,12 @@ public override int GetHashCode() return Value?.GetHashCode() ?? 0; } + /// + public IHoconElement Copy(IHoconElement newParent) + { + return Clone(newParent); + } + public static bool operator ==(HoconLiteral left, HoconLiteral right) { return Equals(left, right); diff --git a/src/Hocon/Impl/HoconObject.cs b/src/Hocon/Impl/HoconObject.cs index a43e6fbe..0fbee38e 100644 --- a/src/Hocon/Impl/HoconObject.cs +++ b/src/Hocon/Impl/HoconObject.cs @@ -29,7 +29,7 @@ namespace Hocon /// } /// /// - public class HoconObject : Dictionary, IHoconElement + public class HoconObject : SortedDictionary, IHoconElement { private static readonly HoconObject _empty; public static HoconObject Empty => _empty; @@ -186,6 +186,14 @@ public IHoconElement Clone(IHoconElement newParent) return clone; } + /// + public IHoconElement Copy(IHoconElement newParent) + { + var copy = new HoconObject(newParent); + foreach (var kvp in this) copy.SetField(kvp.Key, kvp.Value); + return copy; + } + /// /// Retrieves the field associated with the supplied key. /// diff --git a/src/Hocon/Impl/HoconSubstitution.cs b/src/Hocon/Impl/HoconSubstitution.cs index 88a01085..cc5ce886 100644 --- a/src/Hocon/Impl/HoconSubstitution.cs +++ b/src/Hocon/Impl/HoconSubstitution.cs @@ -144,6 +144,12 @@ public IHoconElement Clone(IHoconElement newParent) return this; } + /// + public IHoconElement Copy(IHoconElement newParent) + { + return Clone(newParent); + } + public bool Equals(IHoconElement other) { if (other is null) return false; diff --git a/src/Hocon/Impl/HoconValue.cs b/src/Hocon/Impl/HoconValue.cs index 23a7de3e..20c06ed5 100644 --- a/src/Hocon/Impl/HoconValue.cs +++ b/src/Hocon/Impl/HoconValue.cs @@ -42,6 +42,8 @@ public HoconValue(IHoconElement parent) public virtual HoconType Type { get; private set; } = HoconType.Empty; + public bool IsEmpty => Type == HoconType.Empty; + /// public virtual HoconObject GetObject() { @@ -197,6 +199,14 @@ public virtual IHoconElement Clone(IHoconElement newParent) return clone; } + /// + public IHoconElement Copy(IHoconElement newParent) + { + var copy = new HoconValue(newParent); + copy.AddRange(this); + return copy; + } + public new void Clear() { Type = HoconType.Empty; diff --git a/src/Hocon/Impl/IHoconElement.cs b/src/Hocon/Impl/IHoconElement.cs index 47be6a12..60ac2c3f 100644 --- a/src/Hocon/Impl/IHoconElement.cs +++ b/src/Hocon/Impl/IHoconElement.cs @@ -49,6 +49,12 @@ public interface IHoconElement : IEquatable /// A deep company of this element. IHoconElement Clone(IHoconElement newParent); + /// + /// Do shallow copy of this element. + /// + /// A shallow company of this element. + IHoconElement Copy(IHoconElement newParent); + /// /// Retrieves the string representation of this element, indented for pretty printing. ///