< Summary

Information
Class: Ice.Properties
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/Properties.cs
Tag: 71_18251537082
Line coverage
86%
Covered lines: 250
Uncovered lines: 39
Coverable lines: 289
Total lines: 871
Line coverage: 86.5%
Branch coverage
86%
Covered branches: 182
Total branches: 210
Branch coverage: 86.6%
Method coverage
88%
Covered methods: 30
Total methods: 34
Method coverage: 88.2%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_value()100%11100%
set_value(...)100%11100%
get_used()100%11100%
set_used(...)100%11100%
.ctor(...)100%11100%
Clone()100%11100%
.ctor()100%11100%
.ctor(...)100%11100%
.ctor(...)94.44%18.011896.67%
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%22100%
ice_clone_()100%210%
getUnusedProperties()0%2040%
validatePropertiesWithPrefix(...)100%1414100%
findIcePropertyArray(...)100%22100%
findProperty(...)95%20.182092.31%
getDefaultProperty(...)100%44100%
parse(...)100%2.15266.67%
parseLine(...)84.72%78.557289.19%
loadConfig()75%12.081291.67%

File(s)

/home/runner/work/ice/ice/csharp/src/Ice/Properties.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3#nullable enable
 4
 5using Ice.Internal;
 6using System.Globalization;
 7using System.Text;
 8using System.Text.RegularExpressions;
 9
 10namespace Ice;
 11
 12/// <summary>
 13/// A property set used to configure Ice and Ice applications. Properties are key/value pairs, with both keys and
 14/// values being strings.
 15/// </summary>
 16public sealed class Properties
 17{
 18    private class PropertyValue
 19    {
 120        public string value { get; set; }
 21
 122        public bool used { get; set; }
 23
 124        public PropertyValue(string value, bool used)
 25        {
 126            this.value = value;
 127            this.used = used;
 128        }
 29
 130        public PropertyValue Clone() => new(value, used);
 31    }
 32
 33    /// <summary>
 34    /// Initializes a new instance of the <see cref="Properties" /> class. The property set is initially empty.
 35    /// </summary>
 136    public Properties()
 37    {
 138    }
 39
 40    /// <summary>
 41    /// Initializes a new instance of the <see cref="Properties" /> class with a list of opt-in prefixes.
 42    /// </summary>
 43    /// <param name="optInPrefixes">A list of opt-in prefixes to allow in the property set.</param>
 144    public Properties(List<string> optInPrefixes) => _optInPrefixes = optInPrefixes;
 45
 46    /// <summary>
 47    /// Initializes a new instance of the <see cref="Properties" /> class. The property set is initialized from the
 48    /// provided argument vector.
 49    /// </summary>
 50    /// <param name="args">A command-line argument vector, possibly containing options to set properties. If the
 51    /// command-line options include a --Ice.Config option, the corresponding configuration files are parsed. If the
 52    /// same property is set in a configuration file and in the argument vector, the argument vector takes precedence.
 53    /// This method modifies the argument vector by removing any Ice-related options.</param>
 54    /// <param name="defaults">Default values for the property set. Settings in configuration files and args override
 55    /// these defaults. May be null.</param>
 156    public Properties(ref string[] args, Properties? defaults = null)
 57    {
 158        if (defaults is not null)
 59        {
 160            foreach (KeyValuePair<string, PropertyValue> entry in defaults._properties)
 61            {
 162                _properties[entry.Key] = entry.Value.Clone();
 63            }
 64
 165            _optInPrefixes.AddRange(defaults._optInPrefixes);
 66        }
 67
 168        if (_properties.TryGetValue("Ice.ProgramName", out PropertyValue? pv))
 69        {
 170            pv.used = true;
 71        }
 72        else
 73        {
 174            _properties["Ice.ProgramName"] = new PropertyValue(AppDomain.CurrentDomain.FriendlyName, true);
 75        }
 76
 177        bool loadConfigFiles = false;
 78
 179        for (int i = 0; i < args.Length; i++)
 80        {
 181            if (args[i].StartsWith("--Ice.Config", StringComparison.Ordinal))
 82            {
 183                string line = args[i];
 184                if (!line.Contains('=', StringComparison.Ordinal))
 85                {
 086                    line += "=1";
 87                }
 188                parseLine(line[2..]);
 189                loadConfigFiles = true;
 90
 191                string[] arr = new string[args.Length - 1];
 192                Array.Copy(args, 0, arr, 0, i);
 193                if (i < args.Length - 1)
 94                {
 195                    Array.Copy(args, i + 1, arr, i, args.Length - i - 1);
 96                }
 197                args = arr;
 98            }
 99        }
 100
 1101        if (!loadConfigFiles)
 102        {
 103            //
 104            // If Ice.Config is not set, load from ICE_CONFIG (if set)
 105            //
 1106            loadConfigFiles = !_properties.ContainsKey("Ice.Config");
 107        }
 108
 1109        if (loadConfigFiles)
 110        {
 1111            loadConfig();
 112        }
 113
 1114        args = parseIceCommandLineOptions(args);
 1115    }
 116
 117    /// <summary>
 118    /// Get a property by key.
 119    /// If the property is not set, an empty string is returned.
 120    /// </summary>
 121    /// <param name="key">The property key.</param>
 122    /// <returns>The property value.</returns>
 123    public string getProperty(string key)
 124    {
 1125        lock (_mutex)
 126        {
 1127            string result = "";
 1128            if (_properties.TryGetValue(key, out PropertyValue? pv))
 129            {
 1130                pv.used = true;
 1131                result = pv.value;
 132            }
 1133            return result;
 134        }
 1135    }
 136
 137    /// <summary>
 138    /// Get an Ice property by key.
 139    /// If the property is not set, its default value is returned.
 140    /// </summary>
 141    /// <param name="key">The property key.</param>
 142    /// <returns>The property value or the default value.</returns>
 143    /// <exception name="PropertyException">Thrown if the property is not a known Ice property.</exception>
 1144    public string getIceProperty(string key) => getPropertyWithDefault(key, getDefaultProperty(key));
 145
 146    /// <summary>
 147    /// Get a property by key.
 148    /// If the property is not set, the given default value is returned.
 149    /// </summary>
 150    /// <param name="key">The property key.</param>
 151    /// <param name="value">The default value to use if the property does not exist.</param>
 152    /// <returns>The property value or the default value.</returns>
 153    public string getPropertyWithDefault(string key, string value)
 154    {
 1155        lock (_mutex)
 156        {
 1157            string result = value;
 1158            if (_properties.TryGetValue(key, out PropertyValue? pv))
 159            {
 1160                pv.used = true;
 1161                result = pv.value;
 162            }
 1163            return result;
 164        }
 1165    }
 166
 167    /// <summary>
 168    /// Get a property as an integer.
 169    /// If the property is not set, 0 is returned.
 170    /// </summary>
 171    /// <param name="key">The property key.</param>
 172    /// <returns>The property value interpreted as an integer.</returns>
 173    /// <exception name="PropertyException">Thrown if the property value is not a valid integer.</exception>
 1174    public int getPropertyAsInt(string key) => getPropertyAsIntWithDefault(key, 0);
 175
 176    /// <summary>
 177    /// Get an Ice property as an integer.
 178    /// If the property is not set, its default value is returned.
 179    /// </summary>
 180    /// <param name="key">The property key.</param>
 181    /// <returns>The property value interpreted as an integer, or the default value.</returns>
 182    /// <exception name="PropertyException">Thrown if the property is not a known Ice property or the value is not a
 183    /// valid integer.</exception>
 184    public int getIcePropertyAsInt(string key)
 185    {
 1186        string defaultValueString = getDefaultProperty(key);
 1187        int defaultValue = 0;
 1188        if (defaultValueString.Length > 0)
 189        {
 1190            defaultValue = int.Parse(defaultValueString, CultureInfo.InvariantCulture);
 191        }
 192
 1193        return getPropertyAsIntWithDefault(key, defaultValue);
 194    }
 195
 196    /// <summary>
 197    /// Get a property as an integer.
 198    /// If the property is not set, the given default value is returned.
 199    /// </summary>
 200    /// <param name="key">The property key.</param>
 201    /// <param name="value">The default value to use if the property does not exist.</param>
 202    /// <returns>The property value interpreted as an integer, or the default value.</returns>
 203    /// <exception name="PropertyException">Thrown if the property value is not a valid integer.</exception>
 204    public int getPropertyAsIntWithDefault(string key, int value)
 205    {
 1206        lock (_mutex)
 207        {
 1208            if (!_properties.TryGetValue(key, out PropertyValue? pv))
 209            {
 1210                return value;
 211            }
 1212            pv.used = true;
 213            try
 214            {
 1215                return int.Parse(pv.value, CultureInfo.InvariantCulture);
 216            }
 0217            catch (FormatException)
 218            {
 0219                throw new PropertyException($"property '{key}' has an invalid integer value: '{pv.value}'");
 220            }
 221        }
 1222    }
 223
 224    /// <summary>
 225    /// Get a property as a list of strings.
 226    /// The strings must be separated by whitespace or comma. If the property is
 227    /// not set, an empty list is returned. The strings in the list can contain whitespace and commas if they are
 228    /// enclosed in single or double quotes. If quotes are mismatched, an empty list is returned. Within single quotes
 229    /// or double quotes, you can escape the quote in question with a backslash, e.g. O'Reilly can be written as
 230    /// O'Reilly, "O'Reilly" or 'O\'Reilly'.
 231    /// </summary>
 232    /// <param name="key">The property key.</param>
 233    /// <returns>The property value interpreted as a list of strings.</returns>
 0234    public string[] getPropertyAsList(string key) => getPropertyAsListWithDefault(key, []);
 235
 236    /// <summary>
 237    /// Get an Ice property as a list of strings.
 238    /// The strings must be separated by whitespace or comma. If the property is
 239    /// not set, its default list is returned. The strings in the list can contain whitespace and commas if they are
 240    /// enclosed in single or double quotes. If quotes are mismatched, the default list is returned. Within single
 241    /// quotes or double quotes, you can escape the quote in question with a backslash, e.g. O'Reilly can be written as
 242    /// O'Reilly, "O'Reilly" or 'O\'Reilly'.
 243    /// </summary>
 244    /// <param name="key">The property key.</param>
 245    /// <returns>The property value interpreted as list of strings, or the default value.</returns>
 246    /// <exception cref="PropertyException">Thrown if the property is not a known Ice property.</exception>
 247    public string[] getIcePropertyAsList(string key)
 248    {
 1249        string[] defaultList = UtilInternal.StringUtil.splitString(getDefaultProperty(key), ", \t\r\n");
 1250        return getPropertyAsListWithDefault(key, defaultList);
 251    }
 252
 253    /// <summary>
 254    /// Get a property as a list of strings.
 255    /// The strings must be separated by whitespace or comma. If the property is
 256    /// not set, the default list is returned. The strings in the list can contain whitespace and commas if they are
 257    /// enclosed in single or double quotes. If quotes are mismatched, the default list is returned. Within single
 258    /// quotes or double quotes, you can escape the quote in question with a backslash, e.g. O'Reilly can be written as
 259    /// O'Reilly, "O'Reilly" or 'O\'Reilly'.
 260    /// </summary>
 261    /// <param name="key">The property key.</param>
 262    /// <param name="value">The default value to use if the property is not set.</param>
 263    /// <returns>The property value interpreted as list of strings, or the default value.</returns>
 264    public string[] getPropertyAsListWithDefault(string key, string[] value)
 265    {
 1266        value ??= [];
 267
 1268        lock (_mutex)
 269        {
 1270            if (!_properties.TryGetValue(key, out PropertyValue? pv))
 271            {
 1272                return value;
 273            }
 274
 1275            pv.used = true;
 276
 1277            string[] result = Ice.UtilInternal.StringUtil.splitString(pv.value, ", \t\r\n");
 1278            if (result == null)
 279            {
 0280                Util.getProcessLogger().warning(
 0281                    $"mismatched quotes in property {key}'s value, returning default value");
 0282                return value;
 283            }
 284            else
 285            {
 1286                return result;
 287            }
 288        }
 1289    }
 290
 291    /// <summary>
 292    /// Get all properties whose keys begins with prefix.
 293    /// If prefix is an empty string, then all
 294    /// properties are returned.
 295    /// </summary>
 296    /// <param name="prefix">The prefix to search for (empty string if none).</param>
 297    /// <returns>The matching property set.</returns>
 298    public Dictionary<string, string> getPropertiesForPrefix(string prefix)
 299    {
 1300        lock (_mutex)
 301        {
 1302            var result = new Dictionary<string, string>();
 303
 1304            foreach (string s in _properties.Keys)
 305            {
 1306                if (prefix.Length == 0 || s.StartsWith(prefix, StringComparison.Ordinal))
 307                {
 1308                    PropertyValue pv = _properties[s];
 1309                    pv.used = true;
 1310                    result[s] = pv.value;
 311                }
 312            }
 1313            return result;
 314        }
 1315    }
 316
 317    /// <summary>
 318    /// Set a property.
 319    /// To unset a property, set it to the empty string.
 320    /// </summary>
 321    /// <param name="key">The property key.</param>
 322    /// <param name="value">The property value.</param>
 323    public void setProperty(string key, string value)
 324    {
 325        //
 326        // Trim whitespace
 327        //
 1328        if (key != null)
 329        {
 1330            key = key.Trim();
 331        }
 1332        if (key == null || key.Length == 0)
 333        {
 0334            throw new InitializationException("Attempt to set property with empty key");
 335        }
 336
 337        // Check if the property is in an Ice property prefix. If so, check that it's a valid property.
 1338        if (findIcePropertyArray(key) is PropertyArray propertyArray)
 339        {
 1340            if (propertyArray.isOptIn && !_optInPrefixes.Contains(propertyArray.name))
 341            {
 1342                throw new PropertyException(
 1343                    $"Unable to set '{key}': property prefix '{propertyArray.name}' is opt-in and must be explicitly ena
 344            }
 345
 1346            Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 1347                throw new PropertyException($"unknown Ice property: {key}");
 348
 349            // If the property is deprecated, log a warning.
 1350            if (prop.deprecated)
 351            {
 0352                Util.getProcessLogger().warning($"setting deprecated property: {key}");
 353            }
 354        }
 355
 1356        lock (_mutex)
 357        {
 358            // Set or clear the property.
 1359            if (value is not null && value.Length > 0)
 360            {
 1361                if (_properties.TryGetValue(key, out PropertyValue? pv))
 362                {
 1363                    pv.value = value;
 364                }
 365                else
 366                {
 1367                    pv = new PropertyValue(value, false);
 368                }
 1369                _properties[key] = pv;
 370            }
 371            else
 372            {
 1373                _properties.Remove(key);
 374            }
 1375        }
 1376    }
 377
 378    /// <summary>
 379    /// Get a sequence of command-line options that is equivalent to this property set.
 380    /// Each element of the returned
 381    /// sequence is a command-line option of the form --key=value.
 382    /// </summary>
 383    /// <returns>The command line options for this property set.</returns>
 384    public string[] getCommandLineOptions()
 385    {
 0386        lock (_mutex)
 387        {
 0388            string[] result = new string[_properties.Count];
 0389            int i = 0;
 0390            foreach (KeyValuePair<string, PropertyValue> entry in _properties)
 391            {
 0392                result[i++] = "--" + entry.Key + "=" + entry.Value.value;
 393            }
 0394            return result;
 395        }
 0396    }
 397
 398    /// <summary>
 399    /// Convert a sequence of command-line options into properties.
 400    /// All options that begin with
 401    /// --prefix. are converted into properties. If the prefix is empty, all options that begin with
 402    /// -- are converted to properties.
 403    /// </summary>
 404    /// <param name="prefix">The property prefix, or an empty string to convert all options starting with --.</param>
 405    /// <param name="options">The command-line options.</param>
 406    /// <returns>The command-line options that do not start with the specified prefix, in their original order.</returns
 407    public string[] parseCommandLineOptions(string prefix, string[] options)
 408    {
 1409        if (prefix.Length > 0 && prefix[^1] != '.')
 410        {
 1411            prefix += '.';
 412        }
 1413        prefix = "--" + prefix;
 414
 1415        List<string> result = [];
 1416        for (int i = 0; i < options.Length; i++)
 417        {
 1418            string opt = options[i];
 1419            if (opt.StartsWith(prefix, StringComparison.Ordinal))
 420            {
 1421                if (!opt.Contains('=', StringComparison.Ordinal))
 422                {
 0423                    opt += "=1";
 424                }
 425
 1426                parseLine(opt[2..]);
 427            }
 428            else
 429            {
 1430                result.Add(opt);
 431            }
 432        }
 1433        string[] arr = new string[result.Count];
 1434        if (arr.Length != 0)
 435        {
 1436            result.CopyTo(arr);
 437        }
 1438        return arr;
 439    }
 440
 441    /// <summary>
 442    /// Convert a sequence of command-line options into properties.
 443    /// All options that begin with one of the following
 444    /// prefixes are converted into properties: --Ice, --IceBox, --IceGrid,
 445    /// --IceSSL, --IceStorm, and --Glacier2.
 446    /// </summary>
 447    /// <param name="options">The command-line options.</param>
 448    /// <returns>The command-line options that do not start with one of the listed prefixes, in their original order.
 449    /// </returns>
 450    public string[] parseIceCommandLineOptions(string[] options)
 451    {
 1452        string[] args = options;
 1453        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 454        {
 1455            args = parseCommandLineOptions(name, args);
 456        }
 1457        return args;
 458    }
 459
 460    /// <summary>
 461    /// Load properties from a file.
 462    /// </summary>
 463    /// <param name="file">The property file.</param>
 464    public void load(string file)
 465    {
 466        try
 467        {
 1468            using var sr = new StreamReader(file);
 1469            parse(sr);
 1470        }
 0471        catch (IOException ex)
 472        {
 0473            throw new FileException($"Cannot read '{file}'", ex);
 474        }
 1475    }
 476
 477    /// <summary>
 478    /// Create a copy of this property set.
 479    /// </summary>
 480    /// <returns>A copy of this property set.</returns>
 481    public Properties Clone()
 482    {
 1483        var clonedProperties = new Properties();
 1484        lock (_mutex)
 485        {
 1486            foreach ((string key, PropertyValue value) in _properties)
 487            {
 1488                clonedProperties._properties[key] = value.Clone();
 489            }
 490        }
 1491        return clonedProperties;
 492    }
 493
 494    /// <summary>
 495    /// Alias for <see cref="Clone" />. Provided for source compatibility with Ice 3.7 and earlier versions.
 496    /// </summary>
 497    /// <returns>A copy of this property set.</returns>
 498    [Obsolete("Use Clone instead.")]
 0499    public Properties ice_clone_() => Clone();
 500
 501    /// <summary>
 502    /// Get the unused properties in the property set.
 503    /// </summary>
 504    /// <returns>A list containing the names of the unused properties in this property set.</returns>
 505    public List<string> getUnusedProperties()
 506    {
 0507        lock (_mutex)
 508        {
 0509            var unused = new List<string>();
 0510            foreach ((string key, PropertyValue value) in _properties)
 511            {
 0512                if (!value.used)
 513                {
 0514                    unused.Add(key);
 515                }
 516            }
 0517            return unused;
 518        }
 0519    }
 520
 521    /// <summary>
 522    /// Validate the properties for the given prefix.
 523    /// </summary>
 524    /// <param name="prefix">The property prefix to validate.</param>
 525    /// <param name="properties">The properties to consider. </param>
 526    /// <param name="propertyArray">The property array to search against.</param>
 527    /// <exception cref="PropertyException"> Thrown if unknown properties are found.</exception>
 528    internal static void validatePropertiesWithPrefix(
 529        string prefix,
 530        Properties properties,
 531        PropertyArray propertyArray)
 532    {
 533        // Do not check for unknown properties if Ice prefix, ie Ice, Glacier2, etc
 1534        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 535        {
 1536            if (prefix.StartsWith($"{name}.", StringComparison.Ordinal))
 537            {
 1538                return;
 539            }
 540        }
 541
 1542        var unknownProps = new List<string>();
 1543        Dictionary<string, string> props = properties.getPropertiesForPrefix($"{prefix}.");
 544
 1545        foreach (string p in props.Keys)
 546        {
 547            // Plus one to include the dot.
 1548            if (findProperty(p[(prefix.Length + 1)..], propertyArray) is null)
 549            {
 1550                unknownProps.Add(p);
 551            }
 552        }
 553
 1554        if (unknownProps.Count > 0)
 555        {
 1556            var message = new StringBuilder($"Found unknown properties for {propertyArray.name} : '{prefix}'");
 1557            foreach (string s in unknownProps)
 558            {
 1559                message.Append("\n    ");
 1560                message.Append(s);
 561            }
 562
 1563            throw new PropertyException(message.ToString());
 564        }
 1565    }
 566
 567    /// <summary>
 568    /// Find the Ice property array for a given property name.
 569    /// </summary>
 570    /// <param name="key">The property key.</param>
 571    /// <returns>The Ice property array if found, else null.</returns>
 572    private static PropertyArray? findIcePropertyArray(string key)
 573    {
 1574        int dotPos = key.IndexOf('.', StringComparison.Ordinal);
 575
 576        // If the key doesn't contain a dot, it's not a valid Ice property
 1577        if (dotPos == -1)
 578        {
 1579            return null;
 580        }
 581
 1582        string prefix = key[..dotPos];
 583
 584        // Search for the property list that matches the prefix
 1585        return PropertyNames.validProps.FirstOrDefault(properties => prefix == properties.name);
 586    }
 587
 588    /// <summary>
 589    /// Find a property in the property array.
 590    /// </summary>
 591    /// <param name="key">The property key.</param>
 592    /// <param name="propertyArray">The property array to check.</param>
 593    /// <returns>The <see cref="Property" /> if found, null otherwise.</returns>
 594    private static Property? findProperty(string key, PropertyArray propertyArray)
 595    {
 1596        foreach (Property prop in propertyArray.properties)
 597        {
 598            // If the key is an exact match, return the property unless it has a property class which is prefix only.
 599            // If the key is a regex match, return the property. A property cannot have a property class and use regex.
 1600            if (key == prop.pattern)
 601            {
 1602                if (prop.propertyArray is not null && prop.propertyArray.prefixOnly)
 603                {
 0604                    return null;
 605                }
 1606                return prop;
 607            }
 1608            else if (prop.usesRegex && Regex.IsMatch(key, prop.pattern))
 609            {
 1610                return prop;
 611            }
 612
 613            // If the property has a property class, check if the key is a prefix of the property.
 1614            if (prop.propertyArray is not null)
 615            {
 616                // Check if the key is a prefix of the property.
 617                // The key must be:
 618                // - shorter than the property pattern
 619                // - the property pattern must start with the key
 620                // - the pattern character after the key must be a dot
 1621                if (key.Length > prop.pattern.Length && key.StartsWith($"{prop.pattern}.", StringComparison.Ordinal))
 622                {
 623                    // Plus one to skip the dot.
 1624                    string substring = key[(prop.pattern.Length + 1)..];
 625
 626                    // Check if the suffix is a valid property. If so, return it. If it's not, continue searching
 627                    // the current property array.
 1628                    if (findProperty(substring, prop.propertyArray) is Property subProp)
 629                    {
 1630                        return subProp;
 631                    }
 632                }
 633            }
 634        }
 1635        return null;
 636    }
 637
 638    /// <summary>
 639    /// Gets the default value for a given Ice property.
 640    /// </summary>
 641    /// <param name="key">The Ice property key.</param>
 642    /// <returns>The default property value, or an empty string the default is unspecified.</returns>
 643    /// <exception cref="PropertyException">Thrown if the property is not a known Ice property.</exception>
 644    private static string getDefaultProperty(string key)
 645    {
 646        // Find the property, don't log any warnings.
 1647        PropertyArray propertyArray = findIcePropertyArray(key) ??
 1648            throw new PropertyException($"unknown Ice property: {key}");
 649
 650        // Find the property in the property array.
 1651        Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 1652            throw new PropertyException($"unknown Ice property: {key}");
 653
 1654        return prop.defaultValue;
 655    }
 656
 657    private void parse(StreamReader input)
 658    {
 659        try
 660        {
 661            string? line;
 1662            while ((line = input.ReadLine()) is not null)
 663            {
 1664                parseLine(line);
 665            }
 1666        }
 0667        catch (IOException ex)
 668        {
 0669            throw new SyscallException(ex);
 670        }
 1671    }
 672
 673    private const int ParseStateKey = 0;
 674    private const int ParseStateValue = 1;
 675
 676    private void parseLine(string line)
 677    {
 1678        string key = "";
 1679        string val = "";
 680
 1681        int state = ParseStateKey;
 682
 1683        string whitespace = "";
 1684        string escapedSpace = "";
 1685        bool finished = false;
 1686        for (int i = 0; i < line.Length; ++i)
 687        {
 1688            char c = line[i];
 689            switch (state)
 690            {
 691                case ParseStateKey:
 692                {
 693                    switch (c)
 694                    {
 695                        case '\\':
 1696                            if (i < line.Length - 1)
 697                            {
 1698                                c = line[++i];
 699                                switch (c)
 700                                {
 701                                    case '\\':
 702                                    case '#':
 703                                    case '=':
 1704                                        key += whitespace;
 1705                                        whitespace = "";
 1706                                        key += c;
 1707                                        break;
 708
 709                                    case ' ':
 1710                                        if (key.Length != 0)
 711                                        {
 1712                                            whitespace += c;
 713                                        }
 1714                                        break;
 715
 716                                    default:
 1717                                        key += whitespace;
 1718                                        whitespace = "";
 1719                                        key += '\\';
 1720                                        key += c;
 1721                                        break;
 722                                }
 723                            }
 724                            else
 725                            {
 0726                                key += whitespace;
 0727                                key += c;
 728                            }
 0729                            break;
 730
 731                        case ' ':
 732                        case '\t':
 733                        case '\r':
 734                        case '\n':
 1735                            if (key.Length != 0)
 736                            {
 1737                                whitespace += c;
 738                            }
 1739                            break;
 740
 741                        case '=':
 1742                            whitespace = "";
 1743                            state = ParseStateValue;
 1744                            break;
 745
 746                        case '#':
 1747                            finished = true;
 1748                            break;
 749
 750                        default:
 1751                            key += whitespace;
 1752                            whitespace = "";
 1753                            key += c;
 1754                            break;
 755                    }
 756                    break;
 757                }
 758
 759                case ParseStateValue:
 760                {
 761                    switch (c)
 762                    {
 763                        case '\\':
 1764                            if (i < line.Length - 1)
 765                            {
 1766                                c = line[++i];
 767                                switch (c)
 768                                {
 769                                    case '\\':
 770                                    case '#':
 771                                    case '=':
 1772                                        val += val.Length == 0 ? escapedSpace : whitespace;
 1773                                        whitespace = "";
 1774                                        escapedSpace = "";
 1775                                        val += c;
 1776                                        break;
 777
 778                                    case ' ':
 1779                                        whitespace += c;
 1780                                        escapedSpace += c;
 1781                                        break;
 782
 783                                    default:
 1784                                        val += val.Length == 0 ? escapedSpace : whitespace;
 1785                                        whitespace = "";
 1786                                        escapedSpace = "";
 1787                                        val += '\\';
 1788                                        val += c;
 1789                                        break;
 790                                }
 791                            }
 792                            else
 793                            {
 0794                                val += val.Length == 0 ? escapedSpace : whitespace;
 0795                                val += c;
 796                            }
 0797                            break;
 798
 799                        case ' ':
 800                        case '\t':
 801                        case '\r':
 802                        case '\n':
 1803                            if (val.Length != 0)
 804                            {
 1805                                whitespace += c;
 806                            }
 1807                            break;
 808
 809                        case '#':
 1810                            finished = true;
 1811                            break;
 812
 813                        default:
 1814                            val += val.Length == 0 ? escapedSpace : whitespace;
 1815                            whitespace = "";
 1816                            escapedSpace = "";
 1817                            val += c;
 818                            break;
 819                    }
 820                    break;
 821                }
 822            }
 1823            if (finished)
 824            {
 825                break;
 826            }
 827        }
 1828        val += escapedSpace;
 829
 1830        if ((state == ParseStateKey && key.Length != 0) || (state == ParseStateValue && key.Length == 0))
 831        {
 0832            Util.getProcessLogger().warning("invalid config file entry: \"" + line + "\"");
 0833            return;
 834        }
 1835        else if (key.Length == 0)
 836        {
 1837            return;
 838        }
 839
 1840        setProperty(key, val);
 1841    }
 842
 843    private void loadConfig()
 844    {
 1845        string val = getIceProperty("Ice.Config");
 1846        if (val.Length == 0 || val == "1")
 847        {
 1848            string? s = Environment.GetEnvironmentVariable("ICE_CONFIG");
 1849            if (s != null && s.Length != 0)
 850            {
 0851                val = s;
 852            }
 853        }
 854
 1855        if (val.Length > 0)
 856        {
 1857            char[] separator = { ',' };
 1858            string[] files = val.Split(separator);
 1859            for (int i = 0; i < files.Length; i++)
 860            {
 1861                load(files[i].Trim());
 862            }
 863
 1864            _properties["Ice.Config"] = new PropertyValue(val, true);
 865        }
 1866    }
 867
 1868    private readonly Dictionary<string, PropertyValue> _properties = [];
 1869    private readonly List<string> _optInPrefixes = [];
 1870    private readonly object _mutex = new();
 871}