< Summary

Information
Class: Ice.Properties
Assembly: Ice
File(s): /_/csharp/src/Ice/Properties.cs
Tag: 125_37167941578
Line coverage
87%
Covered lines: 249
Uncovered lines: 36
Coverable lines: 285
Total lines: 881
Line coverage: 87.3%
Branch coverage
85%
Covered branches: 177
Total branches: 206
Branch coverage: 85.9%
Method coverage
90%
Covered methods: 29
Fully covered methods: 19
Total methods: 32
Method coverage: 90.6%
Full method coverage: 59.3%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
.ctor(...)100%11100%
.ctor(...)100%44100%
.ctor(...)100%11100%
getProperty(...)100%22100%
getIceProperty(...)100%11100%
getPropertyWithDefault(...)100%22100%
getPropertyAsInt(...)100%11100%
getIcePropertyAsInt(...)100%22100%
getPropertyAsIntWithDefault(...)100%22100%
getPropertyAsList(...)100%11100%
getIcePropertyAsList(...)100%11100%
getPropertyAsListWithDefault(...)75%9875%
getPropertiesForPrefix(...)100%66100%
setProperty(...)86.36%222290.48%
getCommandLineOptions()0%620%
parseCommandLineOptions(...)91.67%121293.33%
parseIceCommandLineOptions(...)100%44100%
load(...)100%1166.67%
Clone()100%11100%
ice_clone_()100%210%
getUnusedProperties()0%2040%
validatePropertiesWithPrefix(...)100%1414100%
findIcePropertyArray(...)100%22100%
findProperty(...)95%202092.31%
getDefaultProperty(...)75%44100%
loadArgs(...)90%101094.12%
parse(...)100%2266.67%
parseLine(...)84.72%797289.19%
loadConfig()75%121291.67%
.ctor(...)100%11100%
Clone()100%11100%

File(s)

/_/csharp/src/Ice/Properties.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3#nullable enable
 4
 5using Ice.Internal;
 6using System.Collections.Immutable;
 7using System.ComponentModel;
 8using System.Globalization;
 9using System.Text;
 10using System.Text.RegularExpressions;
 11
 12namespace Ice;
 13
 14/// <summary>
 15/// Represents a set of properties used to configure Ice and Ice-based applications. A property is a key/value pair,
 16/// where both the key and the value are strings. By convention, property keys should have the form
 17/// <c>application-name[.category[.sub-category]].name</c>.
 18/// </summary>
 19/// <remarks>This class is thread-safe: multiple threads can safely read and write the properties.</remarks>
 20public sealed class Properties
 21{
 122    private readonly Dictionary<string, PropertyValue> _propertySet = [];
 123    private readonly ImmutableList<string> _optInPrefixes = [];
 124    private readonly object _mutex = new(); // protects _propertySet
 25
 26    /// <summary>
 27    /// Initializes a new instance of the <see cref="Properties" /> class. The property set is initially empty.
 28    /// </summary>
 129    public Properties()
 30    {
 131    }
 32
 33    /// <summary>
 34    /// Initializes a new instance of the <see cref="Properties" /> class, loads the configuration files specified by
 35    /// the <c>Ice.Config</c> property or the <c>ICE_CONFIG</c> environment variable, and then parses Ice properties
 36    /// from <paramref name="args" />.
 37    /// </summary>
 38    /// <param name="args">The command-line arguments. This constructor parses arguments starting with <c>--</c> and one
 39    /// of the reserved prefixes (Ice, IceSSL, etc.) as properties and removes these elements from the list. If there is
 40    /// an argument starting with <c>--Ice.Config</c>, this constructor loads the specified configuration file. When the
 41    /// same property is set in a configuration file and through a command-line argument, the command-line setting takes
 42    /// precedence.</param>
 43    /// <param name="defaults">Default values for the new Properties object. Settings in configuration files and the
 44    /// arguments override these defaults.</param>
 45    /// <remarks>This constructor loads properties from files specified by the <c>ICE_CONFIG</c> environment variable
 46    /// when there is no <c>--Ice.Config</c> command-line argument.</remarks>
 47    public Properties(ref string[] args, Properties? defaults = null)
 148        : this(defaults) =>
 149        loadArgs(ref args);
 50
 51    /// <summary>
 52    /// Initializes a new instance of the <see cref="Properties" /> class, loads the configuration files specified by
 53    /// the <c>Ice.Config</c> property or the <c>ICE_CONFIG</c> environment variable, and then parses Ice properties
 54    /// from <paramref name="args" />.
 55    /// </summary>
 56    /// <param name="args">The command-line arguments. This constructor parses arguments starting with <c>--</c> and one
 57    /// of the reserved prefixes (Ice, IceSSL, etc.) as properties and removes these elements from the list. If there is
 58    /// an argument starting with <c>--Ice.Config</c>, this constructor loads the specified configuration file. When the
 59    /// same property is set in a configuration file and through a command-line argument, the command-line setting takes
 60    /// precedence.</param>
 61    /// <param name="optInPrefixes">Optional reserved prefixes to enable in this new Properties object.</param>
 62    /// <remarks>This constructor loads properties from files specified by the <c>ICE_CONFIG</c> environment variable
 63    /// when there is no <c>--Ice.Config</c> command-line argument.</remarks>
 64    [EditorBrowsable(EditorBrowsableState.Never)] // hidden because optInPrefixes is only for internal use in C#
 165    public Properties(ref string[] args, ImmutableList<string> optInPrefixes)
 66    {
 167        _optInPrefixes = optInPrefixes;
 168        loadArgs(ref args);
 169    }
 70
 71    /// <summary>
 72    /// Get a property by key.
 73    /// If the property is not set, an empty string is returned.
 74    /// </summary>
 75    /// <param name="key">The property key.</param>
 76    /// <returns>The property value.</returns>
 77    public string getProperty(string key)
 78    {
 179        lock (_mutex)
 80        {
 181            string result = "";
 182            if (_propertySet.TryGetValue(key, out PropertyValue? pv))
 83            {
 184                pv.used = true;
 185                result = pv.value;
 86            }
 187            return result;
 88        }
 189    }
 90
 91    /// <summary>
 92    /// Get an Ice property by key.
 93    /// If the property is not set, its default value is returned.
 94    /// </summary>
 95    /// <param name="key">The property key.</param>
 96    /// <returns>The property value or the default value.</returns>
 97    /// <exception cref="PropertyException">Thrown when the property is not a known Ice property.</exception>
 198    public string getIceProperty(string key) => getPropertyWithDefault(key, getDefaultProperty(key));
 99
 100    /// <summary>
 101    /// Get a property by key.
 102    /// If the property is not set, the given default value is returned.
 103    /// </summary>
 104    /// <param name="key">The property key.</param>
 105    /// <param name="value">The default value to use if the property does not exist.</param>
 106    /// <returns>The property value or the default value.</returns>
 107    public string getPropertyWithDefault(string key, string value)
 108    {
 1109        lock (_mutex)
 110        {
 1111            string result = value;
 1112            if (_propertySet.TryGetValue(key, out PropertyValue? pv))
 113            {
 1114                pv.used = true;
 1115                result = pv.value;
 116            }
 1117            return result;
 118        }
 1119    }
 120
 121    /// <summary>
 122    /// Get a property as an integer.
 123    /// If the property is not set, 0 is returned.
 124    /// </summary>
 125    /// <param name="key">The property key.</param>
 126    /// <returns>The property value interpreted as an integer.</returns>
 127    /// <exception cref="PropertyException">Thrown when the property value is not a valid integer.</exception>
 1128    public int getPropertyAsInt(string key) => getPropertyAsIntWithDefault(key, 0);
 129
 130    /// <summary>
 131    /// Get an Ice property as an integer.
 132    /// If the property is not set, its default value is returned.
 133    /// </summary>
 134    /// <param name="key">The property key.</param>
 135    /// <returns>The property value interpreted as an integer, or the default value.</returns>
 136    /// <exception cref="PropertyException">Thrown when the property is not a known Ice property or the value is not a
 137    /// valid integer.</exception>
 138    public int getIcePropertyAsInt(string key)
 139    {
 1140        string defaultValueString = getDefaultProperty(key);
 1141        int defaultValue = 0;
 1142        if (defaultValueString.Length > 0)
 143        {
 1144            defaultValue = int.Parse(defaultValueString, CultureInfo.InvariantCulture);
 145        }
 146
 1147        return getPropertyAsIntWithDefault(key, defaultValue);
 148    }
 149
 150    /// <summary>
 151    /// Get a property as an integer.
 152    /// If the property is not set, the given default value is returned.
 153    /// </summary>
 154    /// <param name="key">The property key.</param>
 155    /// <param name="value">The default value to use if the property does not exist.</param>
 156    /// <returns>The property value interpreted as an integer, or the default value.</returns>
 157    /// <exception cref="PropertyException">Thrown when the property value is not a valid integer.</exception>
 158    public int getPropertyAsIntWithDefault(string key, int value)
 159    {
 1160        lock (_mutex)
 161        {
 1162            if (!_propertySet.TryGetValue(key, out PropertyValue? pv))
 163            {
 1164                return value;
 165            }
 1166            pv.used = true;
 167            try
 168            {
 1169                return int.Parse(pv.value, CultureInfo.InvariantCulture);
 170            }
 1171            catch (System.Exception ex) when (ex is System.FormatException or System.OverflowException)
 172            {
 1173                throw new PropertyException($"property '{key}' has an invalid integer value: '{pv.value}'");
 174            }
 175        }
 1176    }
 177
 178    /// <summary>
 179    /// Get a property as a list of strings.
 180    /// The strings must be separated by whitespace or comma. If the property is
 181    /// not set, an empty list is returned. The strings in the list can contain whitespace and commas if they are
 182    /// enclosed in single or double quotes. If quotes are mismatched, an empty list is returned. Within single quotes
 183    /// or double quotes, you can escape the quote in question with a backslash, e.g. O'Reilly can be written as
 184    /// "O'Reilly" or 'O\'Reilly'.
 185    /// </summary>
 186    /// <param name="key">The property key.</param>
 187    /// <returns>The property value interpreted as a list of strings.</returns>
 1188    public string[] getPropertyAsList(string key) => getPropertyAsListWithDefault(key, []);
 189
 190    /// <summary>
 191    /// Get an Ice property as a list of strings.
 192    /// The strings must be separated by whitespace or comma. If the property is
 193    /// not set, its default list is returned. The strings in the list can contain whitespace and commas if they are
 194    /// enclosed in single or double quotes. If quotes are mismatched, the default list is returned. Within single
 195    /// quotes or double quotes, you can escape the quote in question with a backslash, e.g. O'Reilly can be written as
 196    /// "O'Reilly" or 'O\'Reilly'.
 197    /// </summary>
 198    /// <param name="key">The property key.</param>
 199    /// <returns>The property value interpreted as list of strings, or the default value.</returns>
 200    /// <exception cref="PropertyException">Thrown when the property is not a known Ice property.</exception>
 201    public string[] getIcePropertyAsList(string key)
 202    {
 1203        string[] defaultList = UtilInternal.StringUtil.splitString(getDefaultProperty(key), ", \t\r\n");
 1204        return getPropertyAsListWithDefault(key, defaultList);
 205    }
 206
 207    /// <summary>
 208    /// Get a property as a list of strings.
 209    /// The strings must be separated by whitespace or comma. If the property is
 210    /// not set, the default list is returned. The strings in the list can contain whitespace and commas if they are
 211    /// enclosed in single or double quotes. If quotes are mismatched, the default list is returned. Within single
 212    /// quotes or double quotes, you can escape the quote in question with a backslash, e.g. O'Reilly can be written as
 213    /// "O'Reilly" or 'O\'Reilly'.
 214    /// </summary>
 215    /// <param name="key">The property key.</param>
 216    /// <param name="value">The default value to use if the property is not set.</param>
 217    /// <returns>The property value interpreted as list of strings, or the default value.</returns>
 218    public string[] getPropertyAsListWithDefault(string key, string[] value)
 219    {
 1220        value ??= [];
 221
 1222        lock (_mutex)
 223        {
 1224            if (!_propertySet.TryGetValue(key, out PropertyValue? pv))
 225            {
 1226                return value;
 227            }
 228
 1229            pv.used = true;
 230
 1231            string[] result = Ice.UtilInternal.StringUtil.splitString(pv.value, ", \t\r\n");
 1232            if (result == null)
 233            {
 0234                Util.getProcessLogger().warning(
 0235                    $"mismatched quotes in property {key}'s value, returning default value");
 0236                return value;
 237            }
 1238            return result.Length == 0 ? value : result;
 239        }
 1240    }
 241
 242    /// <summary>
 243    /// Get all properties whose keys begin with <paramref name="prefix" />.
 244    /// If <paramref name="prefix" /> is an empty string, then all
 245    /// properties are returned.
 246    /// </summary>
 247    /// <param name="prefix">The prefix to search for (empty string if none).</param>
 248    /// <returns>The matching property set.</returns>
 249    public Dictionary<string, string> getPropertiesForPrefix(string prefix)
 250    {
 1251        lock (_mutex)
 252        {
 1253            var result = new Dictionary<string, string>();
 254
 1255            foreach (string s in _propertySet.Keys)
 256            {
 1257                if (prefix.Length == 0 || s.StartsWith(prefix, StringComparison.Ordinal))
 258                {
 1259                    PropertyValue pv = _propertySet[s];
 1260                    pv.used = true;
 1261                    result[s] = pv.value;
 262                }
 263            }
 1264            return result;
 265        }
 1266    }
 267
 268    /// <summary>
 269    /// Set a property.
 270    /// To unset a property, set it to the empty string.
 271    /// </summary>
 272    /// <param name="key">The property key.</param>
 273    /// <param name="value">The property value.</param>
 274    public void setProperty(string key, string value)
 275    {
 276        //
 277        // Trim whitespace
 278        //
 1279        if (key != null)
 280        {
 1281            key = key.Trim();
 282        }
 1283        if (key == null || key.Length == 0)
 284        {
 0285            throw new InitializationException("Attempt to set property with empty key");
 286        }
 287
 288        // Check if the property is in an Ice property prefix. If so, check that it's a valid property.
 1289        if (findIcePropertyArray(key) is PropertyArray propertyArray)
 290        {
 1291            if (propertyArray.isOptIn && !_optInPrefixes.Contains(propertyArray.name))
 292            {
 1293                throw new PropertyException(
 1294                    $"Unable to set '{key}': property prefix '{propertyArray.name}' is opt-in and must be explicitly ena
 295            }
 296
 1297            Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 1298                throw new PropertyException($"unknown Ice property: {key}");
 299
 300            // If the property is deprecated, log a warning.
 1301            if (prop.deprecated)
 302            {
 0303                Util.getProcessLogger().warning($"setting deprecated property: {key}");
 304            }
 305        }
 306
 1307        lock (_mutex)
 308        {
 309            // Set or clear the property.
 1310            if (value is not null && value.Length > 0)
 311            {
 1312                if (_propertySet.TryGetValue(key, out PropertyValue? pv))
 313                {
 1314                    pv.value = value;
 315                }
 316                else
 317                {
 1318                    pv = new PropertyValue(value, false);
 319                }
 1320                _propertySet[key] = pv;
 321            }
 322            else
 323            {
 1324                _propertySet.Remove(key);
 325            }
 1326        }
 1327    }
 328
 329    /// <summary>
 330    /// Get a sequence of command-line options that is equivalent to this property set.
 331    /// Each element of the returned
 332    /// sequence is a command-line option of the form --key=value.
 333    /// </summary>
 334    /// <returns>The command line options for this property set.</returns>
 335    public string[] getCommandLineOptions()
 336    {
 0337        lock (_mutex)
 338        {
 0339            string[] result = new string[_propertySet.Count];
 0340            int i = 0;
 0341            foreach (KeyValuePair<string, PropertyValue> entry in _propertySet)
 342            {
 0343                result[i++] = "--" + entry.Key + "=" + entry.Value.value;
 344            }
 0345            return result;
 346        }
 0347    }
 348
 349    /// <summary>
 350    /// Convert a sequence of command-line options into properties.
 351    /// All options that begin with
 352    /// --prefix. are converted into properties. If the prefix is empty, all options that begin with
 353    /// -- are converted to properties.
 354    /// </summary>
 355    /// <param name="prefix">The property prefix, or an empty string to convert all options starting with --.</param>
 356    /// <param name="options">The command-line options.</param>
 357    /// <returns>The command-line options that do not start with the specified prefix, in their original order.</returns
 358    public string[] parseCommandLineOptions(string prefix, string[] options)
 359    {
 1360        if (prefix.Length > 0 && prefix[^1] != '.')
 361        {
 1362            prefix += '.';
 363        }
 1364        prefix = "--" + prefix;
 365
 1366        List<string> result = [];
 1367        for (int i = 0; i < options.Length; i++)
 368        {
 1369            string opt = options[i];
 1370            if (opt.StartsWith(prefix, StringComparison.Ordinal))
 371            {
 1372                if (!opt.Contains('=', StringComparison.Ordinal))
 373                {
 0374                    opt += "=1";
 375                }
 376
 1377                parseLine(opt[2..]);
 378            }
 379            else
 380            {
 1381                result.Add(opt);
 382            }
 383        }
 1384        string[] arr = new string[result.Count];
 1385        if (arr.Length != 0)
 386        {
 1387            result.CopyTo(arr);
 388        }
 1389        return arr;
 390    }
 391
 392    /// <summary>
 393    /// Convert a sequence of command-line options into properties.
 394    /// All options that begin with one of the reserved Ice prefixes (--Ice, --IceSSL, --IceBox, etc.) are converted
 395    /// into properties.
 396    /// </summary>
 397    /// <param name="options">The command-line options.</param>
 398    /// <returns>The command-line options that do not start with one of the reserved prefixes, in their original
 399    /// order.</returns>
 400    public string[] parseIceCommandLineOptions(string[] options)
 401    {
 1402        string[] args = options;
 1403        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 404        {
 1405            args = parseCommandLineOptions(name, args);
 406        }
 1407        return args;
 408    }
 409
 410    /// <summary>
 411    /// Load properties from a file.
 412    /// </summary>
 413    /// <param name="file">The property file.</param>
 414    public void load(string file)
 415    {
 416        try
 417        {
 1418            using var sr = new StreamReader(file);
 1419            parse(sr);
 1420        }
 0421        catch (IOException ex)
 422        {
 0423            throw new FileException($"Cannot read '{file}'", ex);
 424        }
 1425    }
 426
 427    /// <summary>
 428    /// Create a copy of this property set.
 429    /// </summary>
 430    /// <returns>A copy of this property set.</returns>
 1431    public Properties Clone() => new(this);
 432
 433    /// <summary>
 434    /// Alias for <see cref="Clone" />. Provided for source compatibility with Ice 3.7 and earlier versions.
 435    /// </summary>
 436    /// <returns>A copy of this property set.</returns>
 437    [Obsolete("Use Clone instead.")]
 0438    public Properties ice_clone_() => Clone();
 439
 440    /// <summary>
 441    /// Get the unused properties in the property set.
 442    /// </summary>
 443    /// <returns>A list containing the names of the unused properties in this property set.</returns>
 444    public List<string> getUnusedProperties()
 445    {
 0446        lock (_mutex)
 447        {
 0448            var unused = new List<string>();
 0449            foreach ((string key, PropertyValue value) in _propertySet)
 450            {
 0451                if (!value.used)
 452                {
 0453                    unused.Add(key);
 454                }
 455            }
 0456            return unused;
 457        }
 0458    }
 459
 460    /// <summary>
 461    /// Validate the properties for the given prefix.
 462    /// </summary>
 463    /// <param name="prefix">The property prefix to validate.</param>
 464    /// <param name="properties">The properties to consider. </param>
 465    /// <param name="propertyArray">The property array to search against.</param>
 466    /// <exception cref="PropertyException">Thrown when unknown properties are found.</exception>
 467    internal static void validatePropertiesWithPrefix(
 468        string prefix,
 469        Properties properties,
 470        PropertyArray propertyArray)
 471    {
 472        // Do not check for unknown properties if Ice prefix, ie Ice, Glacier2, etc
 1473        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 474        {
 1475            if (prefix.StartsWith($"{name}.", StringComparison.Ordinal))
 476            {
 1477                return;
 478            }
 479        }
 480
 1481        var unknownProps = new List<string>();
 1482        Dictionary<string, string> props = properties.getPropertiesForPrefix($"{prefix}.");
 483
 1484        foreach (string p in props.Keys)
 485        {
 486            // Plus one to skip the dot.
 1487            if (findProperty(p[(prefix.Length + 1)..], propertyArray) is null)
 488            {
 1489                unknownProps.Add(p);
 490            }
 491        }
 492
 1493        if (unknownProps.Count > 0)
 494        {
 1495            var message = new StringBuilder($"Found unknown properties for {propertyArray.name} : '{prefix}'");
 1496            foreach (string s in unknownProps)
 497            {
 1498                message.Append("\n    ");
 1499                message.Append(s);
 500            }
 501
 1502            throw new PropertyException(message.ToString());
 503        }
 1504    }
 505
 506    /// <summary>
 507    /// Find the Ice property array for a given property name.
 508    /// </summary>
 509    /// <param name="key">The property key.</param>
 510    /// <returns>The Ice property array if found, else null.</returns>
 511    private static PropertyArray? findIcePropertyArray(string key)
 512    {
 1513        int dotPos = key.IndexOf('.', StringComparison.Ordinal);
 514
 515        // If the key doesn't contain a dot, it's not a valid Ice property
 1516        if (dotPos == -1)
 517        {
 1518            return null;
 519        }
 520
 1521        string prefix = key[..dotPos];
 522
 523        // Search for the property list that matches the prefix
 1524        return PropertyNames.validProps.FirstOrDefault(properties => prefix == properties.name);
 525    }
 526
 527    /// <summary>
 528    /// Find a property in the property array.
 529    /// </summary>
 530    /// <param name="key">The property key.</param>
 531    /// <param name="propertyArray">The property array to check.</param>
 532    /// <returns>The <see cref="Property" /> if found; otherwise, <see langword="null"/>.</returns>
 533    private static Property? findProperty(string key, PropertyArray propertyArray)
 534    {
 1535        foreach (Property prop in propertyArray.properties)
 536        {
 537            // If the key is an exact match, return the property unless it has a property class which is prefix only.
 538            // If the key is a regex match, return the property. A property cannot have a property class and use regex.
 1539            if (key == prop.pattern)
 540            {
 1541                if (prop.propertyArray is not null && prop.propertyArray.prefixOnly)
 542                {
 0543                    return null;
 544                }
 1545                return prop;
 546            }
 1547            else if (prop.usesRegex && Regex.IsMatch(key, prop.pattern))
 548            {
 1549                return prop;
 550            }
 551
 552            // If the property has a property class, check if the key is a prefix of the property.
 1553            if (prop.propertyArray is not null)
 554            {
 555                // Check if the key is a prefix of the property.
 556                // The key must be:
 557                // - shorter than the property pattern
 558                // - the property pattern must start with the key
 559                // - the pattern character after the key must be a dot
 1560                if (key.Length > prop.pattern.Length && key.StartsWith($"{prop.pattern}.", StringComparison.Ordinal))
 561                {
 562                    // Plus one to skip the dot.
 1563                    string substring = key[(prop.pattern.Length + 1)..];
 564
 565                    // Check if the suffix is a valid property. If so, return it. If it's not, continue searching
 566                    // the current property array.
 1567                    if (findProperty(substring, prop.propertyArray) is Property subProp)
 568                    {
 1569                        return subProp;
 570                    }
 571                }
 572            }
 573        }
 1574        return null;
 575    }
 576
 577    /// <summary>
 578    /// Gets the default value for a given Ice property.
 579    /// </summary>
 580    /// <param name="key">The Ice property key.</param>
 581    /// <returns>The default property value, or an empty string if the default is unspecified.</returns>
 582    /// <exception cref="PropertyException">Thrown when the property is not a known Ice property.</exception>
 583    private static string getDefaultProperty(string key)
 584    {
 585        // Find the property, don't log any warnings.
 1586        PropertyArray propertyArray = findIcePropertyArray(key) ??
 1587            throw new PropertyException($"unknown Ice property: {key}");
 588
 589        // Find the property in the property array.
 1590        Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 1591            throw new PropertyException($"unknown Ice property: {key}");
 592
 1593        return prop.defaultValue;
 594    }
 595
 596    /// <summary>
 597    /// Initializes a new instance of the <see cref="Properties" /> class by copying the given properties.
 598    /// </summary>
 599    /// <param name="defaults">Default values for the new Properties object.</param>
 1600    private Properties(Properties? defaults)
 601    {
 1602        if (defaults is not null)
 603        {
 1604            lock (defaults._mutex)
 605            {
 1606                foreach (KeyValuePair<string, PropertyValue> entry in defaults._propertySet)
 607                {
 1608                    _propertySet[entry.Key] = entry.Value.Clone();
 609                }
 610            }
 611
 1612            _optInPrefixes = defaults._optInPrefixes;
 613        }
 614        // else remains empty
 1615    }
 616
 617    // Helper method called exclusively by constructors.
 618    private void loadArgs(ref string[] args)
 619    {
 1620        bool loadConfigFiles = false;
 1621        List<string> remainingArgs = [];
 622
 1623        foreach (string arg in args)
 624        {
 1625            if (arg.StartsWith("--Ice.Config", StringComparison.Ordinal))
 626            {
 1627                string line = arg;
 1628                if (!line.Contains('=', StringComparison.Ordinal))
 629                {
 0630                    line += "=1";
 631                }
 1632                parseLine(line[2..]);
 1633                loadConfigFiles = true;
 634            }
 635            else
 636            {
 1637                remainingArgs.Add(arg);
 638            }
 639        }
 1640        args = [.. remainingArgs];
 641
 1642        if (!loadConfigFiles)
 643        {
 644            // If Ice.Config is not set, load from ICE_CONFIG (if set).
 1645            loadConfigFiles = !_propertySet.ContainsKey("Ice.Config");
 646        }
 647
 1648        if (loadConfigFiles)
 649        {
 1650            loadConfig();
 651        }
 652
 1653        args = parseIceCommandLineOptions(args);
 1654    }
 655
 656    private void parse(StreamReader input)
 657    {
 658        try
 659        {
 660            string? line;
 1661            while ((line = input.ReadLine()) is not null)
 662            {
 1663                parseLine(line);
 664            }
 1665        }
 0666        catch (IOException ex)
 667        {
 0668            throw new SyscallException(ex);
 669        }
 1670    }
 671
 672    private const int ParseStateKey = 0;
 673    private const int ParseStateValue = 1;
 674
 675    private void parseLine(string line)
 676    {
 1677        string key = "";
 1678        string val = "";
 679
 1680        int state = ParseStateKey;
 681
 1682        string whitespace = "";
 1683        string escapedSpace = "";
 1684        bool finished = false;
 1685        for (int i = 0; i < line.Length; ++i)
 686        {
 1687            char c = line[i];
 688            switch (state)
 689            {
 690                case ParseStateKey:
 691                {
 692                    switch (c)
 693                    {
 694                        case '\\':
 1695                            if (i < line.Length - 1)
 696                            {
 1697                                c = line[++i];
 698                                switch (c)
 699                                {
 700                                    case '\\':
 701                                    case '#':
 702                                    case '=':
 1703                                        key += whitespace;
 1704                                        whitespace = "";
 1705                                        key += c;
 1706                                        break;
 707
 708                                    case ' ':
 1709                                        if (key.Length != 0)
 710                                        {
 1711                                            whitespace += c;
 712                                        }
 1713                                        break;
 714
 715                                    default:
 1716                                        key += whitespace;
 1717                                        whitespace = "";
 1718                                        key += '\\';
 1719                                        key += c;
 1720                                        break;
 721                                }
 722                            }
 723                            else
 724                            {
 0725                                key += whitespace;
 0726                                key += c;
 727                            }
 0728                            break;
 729
 730                        case ' ':
 731                        case '\t':
 732                        case '\r':
 733                        case '\n':
 1734                            if (key.Length != 0)
 735                            {
 1736                                whitespace += c;
 737                            }
 1738                            break;
 739
 740                        case '=':
 1741                            whitespace = "";
 1742                            state = ParseStateValue;
 1743                            break;
 744
 745                        case '#':
 1746                            finished = true;
 1747                            break;
 748
 749                        default:
 1750                            key += whitespace;
 1751                            whitespace = "";
 1752                            key += c;
 1753                            break;
 754                    }
 755                    break;
 756                }
 757
 758                case ParseStateValue:
 759                {
 760                    switch (c)
 761                    {
 762                        case '\\':
 1763                            if (i < line.Length - 1)
 764                            {
 1765                                c = line[++i];
 766                                switch (c)
 767                                {
 768                                    case '\\':
 769                                    case '#':
 770                                    case '=':
 1771                                        val += val.Length == 0 ? escapedSpace : whitespace;
 1772                                        whitespace = "";
 1773                                        escapedSpace = "";
 1774                                        val += c;
 1775                                        break;
 776
 777                                    case ' ':
 1778                                        whitespace += c;
 1779                                        escapedSpace += c;
 1780                                        break;
 781
 782                                    default:
 1783                                        val += val.Length == 0 ? escapedSpace : whitespace;
 1784                                        whitespace = "";
 1785                                        escapedSpace = "";
 1786                                        val += '\\';
 1787                                        val += c;
 1788                                        break;
 789                                }
 790                            }
 791                            else
 792                            {
 0793                                val += val.Length == 0 ? escapedSpace : whitespace;
 0794                                val += c;
 795                            }
 0796                            break;
 797
 798                        case ' ':
 799                        case '\t':
 800                        case '\r':
 801                        case '\n':
 1802                            if (val.Length != 0)
 803                            {
 1804                                whitespace += c;
 805                            }
 1806                            break;
 807
 808                        case '#':
 1809                            finished = true;
 1810                            break;
 811
 812                        default:
 1813                            val += val.Length == 0 ? escapedSpace : whitespace;
 1814                            whitespace = "";
 1815                            escapedSpace = "";
 1816                            val += c;
 817                            break;
 818                    }
 819                    break;
 820                }
 821            }
 1822            if (finished)
 823            {
 824                break;
 825            }
 826        }
 1827        val += escapedSpace;
 828
 1829        if ((state == ParseStateKey && key.Length != 0) || (state == ParseStateValue && key.Length == 0))
 830        {
 0831            Util.getProcessLogger().warning("invalid config file entry: \"" + line + "\"");
 0832            return;
 833        }
 1834        else if (key.Length == 0)
 835        {
 1836            return;
 837        }
 838
 1839        setProperty(key, val);
 1840    }
 841
 842    private void loadConfig()
 843    {
 1844        string val = getIceProperty("Ice.Config");
 1845        if (val.Length == 0 || val == "1")
 846        {
 1847            string? s = Environment.GetEnvironmentVariable("ICE_CONFIG");
 1848            if (s != null && s.Length != 0)
 849            {
 0850                val = s;
 851            }
 852        }
 853
 1854        if (val.Length > 0)
 855        {
 1856            char[] separator = { ',' };
 1857            string[] files = val.Split(separator);
 1858            for (int i = 0; i < files.Length; i++)
 859            {
 1860                load(files[i].Trim());
 861            }
 862
 1863            _propertySet["Ice.Config"] = new PropertyValue(val, true);
 864        }
 1865    }
 866
 867    private class PropertyValue
 868    {
 869        public string value { get; set; }
 870
 871        public bool used { get; set; }
 872
 1873        public PropertyValue(string value, bool used)
 874        {
 1875            this.value = value;
 1876            this.used = used;
 1877        }
 878
 1879        public PropertyValue Clone() => new(value, used);
 880    }
 881}