< Summary

Information
Class: Ice.Properties
Assembly: Ice
File(s): /_/csharp/src/Ice/Properties.cs
Tag: 91_21789722663
Line coverage
86%
Covered lines: 250
Uncovered lines: 39
Coverable lines: 289
Total lines: 887
Line coverage: 86.5%
Branch coverage
86%
Covered branches: 178
Total branches: 206
Branch coverage: 86.4%
Method coverage
88%
Covered methods: 32
Total methods: 36
Method coverage: 88.8%

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%2.06275%
getPropertyAsList(...)100%210%
getIcePropertyAsList(...)100%11100%
getPropertyAsListWithDefault(...)66.67%6.56675%
getPropertiesForPrefix(...)100%66100%
setProperty(...)86.36%22.422290.48%
getCommandLineOptions()0%620%
parseCommandLineOptions(...)91.67%12.041293.33%
parseIceCommandLineOptions(...)100%44100%
load(...)100%1.04166.67%
Clone()100%11100%
ice_clone_()100%210%
getUnusedProperties()0%2040%
validatePropertiesWithPrefix(...)100%1414100%
findIcePropertyArray(...)100%22100%
findProperty(...)95%20.182092.31%
getDefaultProperty(...)100%44100%
loadArgs(...)91.67%12.021294.74%
parse(...)100%2.15266.67%
parseLine(...)84.72%78.557289.19%
loadConfig()75%12.081291.67%
get_value()100%11100%
set_value(...)100%11100%
get_used()100%11100%
set_used(...)100%11100%
.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 name="PropertyException">Thrown if 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 name="PropertyException">Thrown if 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 name="PropertyException">Thrown if 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 name="PropertyException">Thrown if 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            }
 0171            catch (FormatException)
 172            {
 0173                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, "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>
 0188    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, "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 if 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, "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            }
 238            else
 239            {
 1240                return result;
 241            }
 242        }
 1243    }
 244
 245    /// <summary>
 246    /// Get all properties whose keys begins with prefix.
 247    /// If prefix is an empty string, then all
 248    /// properties are returned.
 249    /// </summary>
 250    /// <param name="prefix">The prefix to search for (empty string if none).</param>
 251    /// <returns>The matching property set.</returns>
 252    public Dictionary<string, string> getPropertiesForPrefix(string prefix)
 253    {
 1254        lock (_mutex)
 255        {
 1256            var result = new Dictionary<string, string>();
 257
 1258            foreach (string s in _propertySet.Keys)
 259            {
 1260                if (prefix.Length == 0 || s.StartsWith(prefix, StringComparison.Ordinal))
 261                {
 1262                    PropertyValue pv = _propertySet[s];
 1263                    pv.used = true;
 1264                    result[s] = pv.value;
 265                }
 266            }
 1267            return result;
 268        }
 1269    }
 270
 271    /// <summary>
 272    /// Set a property.
 273    /// To unset a property, set it to the empty string.
 274    /// </summary>
 275    /// <param name="key">The property key.</param>
 276    /// <param name="value">The property value.</param>
 277    public void setProperty(string key, string value)
 278    {
 279        //
 280        // Trim whitespace
 281        //
 1282        if (key != null)
 283        {
 1284            key = key.Trim();
 285        }
 1286        if (key == null || key.Length == 0)
 287        {
 0288            throw new InitializationException("Attempt to set property with empty key");
 289        }
 290
 291        // Check if the property is in an Ice property prefix. If so, check that it's a valid property.
 1292        if (findIcePropertyArray(key) is PropertyArray propertyArray)
 293        {
 1294            if (propertyArray.isOptIn && !_optInPrefixes.Contains(propertyArray.name))
 295            {
 1296                throw new PropertyException(
 1297                    $"Unable to set '{key}': property prefix '{propertyArray.name}' is opt-in and must be explicitly ena
 298            }
 299
 1300            Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 1301                throw new PropertyException($"unknown Ice property: {key}");
 302
 303            // If the property is deprecated, log a warning.
 1304            if (prop.deprecated)
 305            {
 0306                Util.getProcessLogger().warning($"setting deprecated property: {key}");
 307            }
 308        }
 309
 1310        lock (_mutex)
 311        {
 312            // Set or clear the property.
 1313            if (value is not null && value.Length > 0)
 314            {
 1315                if (_propertySet.TryGetValue(key, out PropertyValue? pv))
 316                {
 1317                    pv.value = value;
 318                }
 319                else
 320                {
 1321                    pv = new PropertyValue(value, false);
 322                }
 1323                _propertySet[key] = pv;
 324            }
 325            else
 326            {
 1327                _propertySet.Remove(key);
 328            }
 1329        }
 1330    }
 331
 332    /// <summary>
 333    /// Get a sequence of command-line options that is equivalent to this property set.
 334    /// Each element of the returned
 335    /// sequence is a command-line option of the form --key=value.
 336    /// </summary>
 337    /// <returns>The command line options for this property set.</returns>
 338    public string[] getCommandLineOptions()
 339    {
 0340        lock (_mutex)
 341        {
 0342            string[] result = new string[_propertySet.Count];
 0343            int i = 0;
 0344            foreach (KeyValuePair<string, PropertyValue> entry in _propertySet)
 345            {
 0346                result[i++] = "--" + entry.Key + "=" + entry.Value.value;
 347            }
 0348            return result;
 349        }
 0350    }
 351
 352    /// <summary>
 353    /// Convert a sequence of command-line options into properties.
 354    /// All options that begin with
 355    /// --prefix. are converted into properties. If the prefix is empty, all options that begin with
 356    /// -- are converted to properties.
 357    /// </summary>
 358    /// <param name="prefix">The property prefix, or an empty string to convert all options starting with --.</param>
 359    /// <param name="options">The command-line options.</param>
 360    /// <returns>The command-line options that do not start with the specified prefix, in their original order.</returns
 361    public string[] parseCommandLineOptions(string prefix, string[] options)
 362    {
 1363        if (prefix.Length > 0 && prefix[^1] != '.')
 364        {
 1365            prefix += '.';
 366        }
 1367        prefix = "--" + prefix;
 368
 1369        List<string> result = [];
 1370        for (int i = 0; i < options.Length; i++)
 371        {
 1372            string opt = options[i];
 1373            if (opt.StartsWith(prefix, StringComparison.Ordinal))
 374            {
 1375                if (!opt.Contains('=', StringComparison.Ordinal))
 376                {
 0377                    opt += "=1";
 378                }
 379
 1380                parseLine(opt[2..]);
 381            }
 382            else
 383            {
 1384                result.Add(opt);
 385            }
 386        }
 1387        string[] arr = new string[result.Count];
 1388        if (arr.Length != 0)
 389        {
 1390            result.CopyTo(arr);
 391        }
 1392        return arr;
 393    }
 394
 395    /// <summary>
 396    /// Convert a sequence of command-line options into properties.
 397    /// All options that begin with one of the following
 398    /// prefixes are converted into properties: --Ice, --IceBox, --IceGrid,
 399    /// --IceSSL, --IceStorm, and --Glacier2.
 400    /// </summary>
 401    /// <param name="options">The command-line options.</param>
 402    /// <returns>The command-line options that do not start with one of the listed prefixes, in their original order.
 403    /// </returns>
 404    public string[] parseIceCommandLineOptions(string[] options)
 405    {
 1406        string[] args = options;
 1407        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 408        {
 1409            args = parseCommandLineOptions(name, args);
 410        }
 1411        return args;
 412    }
 413
 414    /// <summary>
 415    /// Load properties from a file.
 416    /// </summary>
 417    /// <param name="file">The property file.</param>
 418    public void load(string file)
 419    {
 420        try
 421        {
 1422            using var sr = new StreamReader(file);
 1423            parse(sr);
 1424        }
 0425        catch (IOException ex)
 426        {
 0427            throw new FileException($"Cannot read '{file}'", ex);
 428        }
 1429    }
 430
 431    /// <summary>
 432    /// Create a copy of this property set.
 433    /// </summary>
 434    /// <returns>A copy of this property set.</returns>
 1435    public Properties Clone() => new(this);
 436
 437    /// <summary>
 438    /// Alias for <see cref="Clone" />. Provided for source compatibility with Ice 3.7 and earlier versions.
 439    /// </summary>
 440    /// <returns>A copy of this property set.</returns>
 441    [Obsolete("Use Clone instead.")]
 0442    public Properties ice_clone_() => Clone();
 443
 444    /// <summary>
 445    /// Get the unused properties in the property set.
 446    /// </summary>
 447    /// <returns>A list containing the names of the unused properties in this property set.</returns>
 448    public List<string> getUnusedProperties()
 449    {
 0450        lock (_mutex)
 451        {
 0452            var unused = new List<string>();
 0453            foreach ((string key, PropertyValue value) in _propertySet)
 454            {
 0455                if (!value.used)
 456                {
 0457                    unused.Add(key);
 458                }
 459            }
 0460            return unused;
 461        }
 0462    }
 463
 464    /// <summary>
 465    /// Validate the properties for the given prefix.
 466    /// </summary>
 467    /// <param name="prefix">The property prefix to validate.</param>
 468    /// <param name="properties">The properties to consider. </param>
 469    /// <param name="propertyArray">The property array to search against.</param>
 470    /// <exception cref="PropertyException"> Thrown if unknown properties are found.</exception>
 471    internal static void validatePropertiesWithPrefix(
 472        string prefix,
 473        Properties properties,
 474        PropertyArray propertyArray)
 475    {
 476        // Do not check for unknown properties if Ice prefix, ie Ice, Glacier2, etc
 1477        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 478        {
 1479            if (prefix.StartsWith($"{name}.", StringComparison.Ordinal))
 480            {
 1481                return;
 482            }
 483        }
 484
 1485        var unknownProps = new List<string>();
 1486        Dictionary<string, string> props = properties.getPropertiesForPrefix($"{prefix}.");
 487
 1488        foreach (string p in props.Keys)
 489        {
 490            // Plus one to include the dot.
 1491            if (findProperty(p[(prefix.Length + 1)..], propertyArray) is null)
 492            {
 1493                unknownProps.Add(p);
 494            }
 495        }
 496
 1497        if (unknownProps.Count > 0)
 498        {
 1499            var message = new StringBuilder($"Found unknown properties for {propertyArray.name} : '{prefix}'");
 1500            foreach (string s in unknownProps)
 501            {
 1502                message.Append("\n    ");
 1503                message.Append(s);
 504            }
 505
 1506            throw new PropertyException(message.ToString());
 507        }
 1508    }
 509
 510    /// <summary>
 511    /// Find the Ice property array for a given property name.
 512    /// </summary>
 513    /// <param name="key">The property key.</param>
 514    /// <returns>The Ice property array if found, else null.</returns>
 515    private static PropertyArray? findIcePropertyArray(string key)
 516    {
 1517        int dotPos = key.IndexOf('.', StringComparison.Ordinal);
 518
 519        // If the key doesn't contain a dot, it's not a valid Ice property
 1520        if (dotPos == -1)
 521        {
 1522            return null;
 523        }
 524
 1525        string prefix = key[..dotPos];
 526
 527        // Search for the property list that matches the prefix
 1528        return PropertyNames.validProps.FirstOrDefault(properties => prefix == properties.name);
 529    }
 530
 531    /// <summary>
 532    /// Find a property in the property array.
 533    /// </summary>
 534    /// <param name="key">The property key.</param>
 535    /// <param name="propertyArray">The property array to check.</param>
 536    /// <returns>The <see cref="Property" /> if found, null otherwise.</returns>
 537    private static Property? findProperty(string key, PropertyArray propertyArray)
 538    {
 1539        foreach (Property prop in propertyArray.properties)
 540        {
 541            // If the key is an exact match, return the property unless it has a property class which is prefix only.
 542            // If the key is a regex match, return the property. A property cannot have a property class and use regex.
 1543            if (key == prop.pattern)
 544            {
 1545                if (prop.propertyArray is not null && prop.propertyArray.prefixOnly)
 546                {
 0547                    return null;
 548                }
 1549                return prop;
 550            }
 1551            else if (prop.usesRegex && Regex.IsMatch(key, prop.pattern))
 552            {
 1553                return prop;
 554            }
 555
 556            // If the property has a property class, check if the key is a prefix of the property.
 1557            if (prop.propertyArray is not null)
 558            {
 559                // Check if the key is a prefix of the property.
 560                // The key must be:
 561                // - shorter than the property pattern
 562                // - the property pattern must start with the key
 563                // - the pattern character after the key must be a dot
 1564                if (key.Length > prop.pattern.Length && key.StartsWith($"{prop.pattern}.", StringComparison.Ordinal))
 565                {
 566                    // Plus one to skip the dot.
 1567                    string substring = key[(prop.pattern.Length + 1)..];
 568
 569                    // Check if the suffix is a valid property. If so, return it. If it's not, continue searching
 570                    // the current property array.
 1571                    if (findProperty(substring, prop.propertyArray) is Property subProp)
 572                    {
 1573                        return subProp;
 574                    }
 575                }
 576            }
 577        }
 1578        return null;
 579    }
 580
 581    /// <summary>
 582    /// Gets the default value for a given Ice property.
 583    /// </summary>
 584    /// <param name="key">The Ice property key.</param>
 585    /// <returns>The default property value, or an empty string the default is unspecified.</returns>
 586    /// <exception cref="PropertyException">Thrown if the property is not a known Ice property.</exception>
 587    private static string getDefaultProperty(string key)
 588    {
 589        // Find the property, don't log any warnings.
 1590        PropertyArray propertyArray = findIcePropertyArray(key) ??
 1591            throw new PropertyException($"unknown Ice property: {key}");
 592
 593        // Find the property in the property array.
 1594        Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 1595            throw new PropertyException($"unknown Ice property: {key}");
 596
 1597        return prop.defaultValue;
 598    }
 599
 600    /// <summary>
 601    /// Initializes a new instance of the <see cref="Properties" /> class by copying the given properties.
 602    /// </summary>
 603    /// <param name="defaults">Default values for the new Properties object.</param>
 1604    private Properties(Properties? defaults)
 605    {
 1606        if (defaults is not null)
 607        {
 1608            lock (defaults._mutex)
 609            {
 1610                foreach (KeyValuePair<string, PropertyValue> entry in defaults._propertySet)
 611                {
 1612                    _propertySet[entry.Key] = entry.Value.Clone();
 613                }
 614            }
 615
 1616            _optInPrefixes = defaults._optInPrefixes;
 617        }
 618        // else remains empty
 1619    }
 620
 621    // Helper method called exclusively by constructors.
 622    private void loadArgs(ref string[] args)
 623    {
 1624        bool loadConfigFiles = false;
 625
 1626        for (int i = 0; i < args.Length; i++)
 627        {
 1628            if (args[i].StartsWith("--Ice.Config", StringComparison.Ordinal))
 629            {
 1630                string line = args[i];
 1631                if (!line.Contains('=', StringComparison.Ordinal))
 632                {
 0633                    line += "=1";
 634                }
 1635                parseLine(line[2..]);
 1636                loadConfigFiles = true;
 637
 1638                string[] arr = new string[args.Length - 1];
 1639                Array.Copy(args, 0, arr, 0, i);
 1640                if (i < args.Length - 1)
 641                {
 1642                    Array.Copy(args, i + 1, arr, i, args.Length - i - 1);
 643                }
 1644                args = arr;
 645            }
 646        }
 647
 1648        if (!loadConfigFiles)
 649        {
 650            // If Ice.Config is not set, load from ICE_CONFIG (if set).
 1651            loadConfigFiles = !_propertySet.ContainsKey("Ice.Config");
 652        }
 653
 1654        if (loadConfigFiles)
 655        {
 1656            loadConfig();
 657        }
 658
 1659        args = parseIceCommandLineOptions(args);
 1660    }
 661
 662    private void parse(StreamReader input)
 663    {
 664        try
 665        {
 666            string? line;
 1667            while ((line = input.ReadLine()) is not null)
 668            {
 1669                parseLine(line);
 670            }
 1671        }
 0672        catch (IOException ex)
 673        {
 0674            throw new SyscallException(ex);
 675        }
 1676    }
 677
 678    private const int ParseStateKey = 0;
 679    private const int ParseStateValue = 1;
 680
 681    private void parseLine(string line)
 682    {
 1683        string key = "";
 1684        string val = "";
 685
 1686        int state = ParseStateKey;
 687
 1688        string whitespace = "";
 1689        string escapedSpace = "";
 1690        bool finished = false;
 1691        for (int i = 0; i < line.Length; ++i)
 692        {
 1693            char c = line[i];
 694            switch (state)
 695            {
 696                case ParseStateKey:
 697                {
 698                    switch (c)
 699                    {
 700                        case '\\':
 1701                            if (i < line.Length - 1)
 702                            {
 1703                                c = line[++i];
 704                                switch (c)
 705                                {
 706                                    case '\\':
 707                                    case '#':
 708                                    case '=':
 1709                                        key += whitespace;
 1710                                        whitespace = "";
 1711                                        key += c;
 1712                                        break;
 713
 714                                    case ' ':
 1715                                        if (key.Length != 0)
 716                                        {
 1717                                            whitespace += c;
 718                                        }
 1719                                        break;
 720
 721                                    default:
 1722                                        key += whitespace;
 1723                                        whitespace = "";
 1724                                        key += '\\';
 1725                                        key += c;
 1726                                        break;
 727                                }
 728                            }
 729                            else
 730                            {
 0731                                key += whitespace;
 0732                                key += c;
 733                            }
 0734                            break;
 735
 736                        case ' ':
 737                        case '\t':
 738                        case '\r':
 739                        case '\n':
 1740                            if (key.Length != 0)
 741                            {
 1742                                whitespace += c;
 743                            }
 1744                            break;
 745
 746                        case '=':
 1747                            whitespace = "";
 1748                            state = ParseStateValue;
 1749                            break;
 750
 751                        case '#':
 1752                            finished = true;
 1753                            break;
 754
 755                        default:
 1756                            key += whitespace;
 1757                            whitespace = "";
 1758                            key += c;
 1759                            break;
 760                    }
 761                    break;
 762                }
 763
 764                case ParseStateValue:
 765                {
 766                    switch (c)
 767                    {
 768                        case '\\':
 1769                            if (i < line.Length - 1)
 770                            {
 1771                                c = line[++i];
 772                                switch (c)
 773                                {
 774                                    case '\\':
 775                                    case '#':
 776                                    case '=':
 1777                                        val += val.Length == 0 ? escapedSpace : whitespace;
 1778                                        whitespace = "";
 1779                                        escapedSpace = "";
 1780                                        val += c;
 1781                                        break;
 782
 783                                    case ' ':
 1784                                        whitespace += c;
 1785                                        escapedSpace += c;
 1786                                        break;
 787
 788                                    default:
 1789                                        val += val.Length == 0 ? escapedSpace : whitespace;
 1790                                        whitespace = "";
 1791                                        escapedSpace = "";
 1792                                        val += '\\';
 1793                                        val += c;
 1794                                        break;
 795                                }
 796                            }
 797                            else
 798                            {
 0799                                val += val.Length == 0 ? escapedSpace : whitespace;
 0800                                val += c;
 801                            }
 0802                            break;
 803
 804                        case ' ':
 805                        case '\t':
 806                        case '\r':
 807                        case '\n':
 1808                            if (val.Length != 0)
 809                            {
 1810                                whitespace += c;
 811                            }
 1812                            break;
 813
 814                        case '#':
 1815                            finished = true;
 1816                            break;
 817
 818                        default:
 1819                            val += val.Length == 0 ? escapedSpace : whitespace;
 1820                            whitespace = "";
 1821                            escapedSpace = "";
 1822                            val += c;
 823                            break;
 824                    }
 825                    break;
 826                }
 827            }
 1828            if (finished)
 829            {
 830                break;
 831            }
 832        }
 1833        val += escapedSpace;
 834
 1835        if ((state == ParseStateKey && key.Length != 0) || (state == ParseStateValue && key.Length == 0))
 836        {
 0837            Util.getProcessLogger().warning("invalid config file entry: \"" + line + "\"");
 0838            return;
 839        }
 1840        else if (key.Length == 0)
 841        {
 1842            return;
 843        }
 844
 1845        setProperty(key, val);
 1846    }
 847
 848    private void loadConfig()
 849    {
 1850        string val = getIceProperty("Ice.Config");
 1851        if (val.Length == 0 || val == "1")
 852        {
 1853            string? s = Environment.GetEnvironmentVariable("ICE_CONFIG");
 1854            if (s != null && s.Length != 0)
 855            {
 0856                val = s;
 857            }
 858        }
 859
 1860        if (val.Length > 0)
 861        {
 1862            char[] separator = { ',' };
 1863            string[] files = val.Split(separator);
 1864            for (int i = 0; i < files.Length; i++)
 865            {
 1866                load(files[i].Trim());
 867            }
 868
 1869            _propertySet["Ice.Config"] = new PropertyValue(val, true);
 870        }
 1871    }
 872
 873    private class PropertyValue
 874    {
 1875        public string value { get; set; }
 876
 1877        public bool used { get; set; }
 878
 1879        public PropertyValue(string value, bool used)
 880        {
 1881            this.value = value;
 1882            this.used = used;
 1883        }
 884
 1885        public PropertyValue Clone() => new(value, used);
 886    }
 887}