< Summary

Information
Class: Ice.Properties.PropertyValue
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/Properties.cs
Tag: 71_18251537082
Line coverage
100%
Covered lines: 7
Uncovered lines: 0
Coverable lines: 7
Total lines: 871
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 6
Total methods: 6
Method coverage: 100%

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%

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>
 36    public Properties()
 37    {
 38    }
 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>
 44    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>
 56    public Properties(ref string[] args, Properties? defaults = null)
 57    {
 58        if (defaults is not null)
 59        {
 60            foreach (KeyValuePair<string, PropertyValue> entry in defaults._properties)
 61            {
 62                _properties[entry.Key] = entry.Value.Clone();
 63            }
 64
 65            _optInPrefixes.AddRange(defaults._optInPrefixes);
 66        }
 67
 68        if (_properties.TryGetValue("Ice.ProgramName", out PropertyValue? pv))
 69        {
 70            pv.used = true;
 71        }
 72        else
 73        {
 74            _properties["Ice.ProgramName"] = new PropertyValue(AppDomain.CurrentDomain.FriendlyName, true);
 75        }
 76
 77        bool loadConfigFiles = false;
 78
 79        for (int i = 0; i < args.Length; i++)
 80        {
 81            if (args[i].StartsWith("--Ice.Config", StringComparison.Ordinal))
 82            {
 83                string line = args[i];
 84                if (!line.Contains('=', StringComparison.Ordinal))
 85                {
 86                    line += "=1";
 87                }
 88                parseLine(line[2..]);
 89                loadConfigFiles = true;
 90
 91                string[] arr = new string[args.Length - 1];
 92                Array.Copy(args, 0, arr, 0, i);
 93                if (i < args.Length - 1)
 94                {
 95                    Array.Copy(args, i + 1, arr, i, args.Length - i - 1);
 96                }
 97                args = arr;
 98            }
 99        }
 100
 101        if (!loadConfigFiles)
 102        {
 103            //
 104            // If Ice.Config is not set, load from ICE_CONFIG (if set)
 105            //
 106            loadConfigFiles = !_properties.ContainsKey("Ice.Config");
 107        }
 108
 109        if (loadConfigFiles)
 110        {
 111            loadConfig();
 112        }
 113
 114        args = parseIceCommandLineOptions(args);
 115    }
 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    {
 125        lock (_mutex)
 126        {
 127            string result = "";
 128            if (_properties.TryGetValue(key, out PropertyValue? pv))
 129            {
 130                pv.used = true;
 131                result = pv.value;
 132            }
 133            return result;
 134        }
 135    }
 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>
 144    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    {
 155        lock (_mutex)
 156        {
 157            string result = value;
 158            if (_properties.TryGetValue(key, out PropertyValue? pv))
 159            {
 160                pv.used = true;
 161                result = pv.value;
 162            }
 163            return result;
 164        }
 165    }
 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>
 174    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    {
 186        string defaultValueString = getDefaultProperty(key);
 187        int defaultValue = 0;
 188        if (defaultValueString.Length > 0)
 189        {
 190            defaultValue = int.Parse(defaultValueString, CultureInfo.InvariantCulture);
 191        }
 192
 193        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    {
 206        lock (_mutex)
 207        {
 208            if (!_properties.TryGetValue(key, out PropertyValue? pv))
 209            {
 210                return value;
 211            }
 212            pv.used = true;
 213            try
 214            {
 215                return int.Parse(pv.value, CultureInfo.InvariantCulture);
 216            }
 217            catch (FormatException)
 218            {
 219                throw new PropertyException($"property '{key}' has an invalid integer value: '{pv.value}'");
 220            }
 221        }
 222    }
 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>
 234    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    {
 249        string[] defaultList = UtilInternal.StringUtil.splitString(getDefaultProperty(key), ", \t\r\n");
 250        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    {
 266        value ??= [];
 267
 268        lock (_mutex)
 269        {
 270            if (!_properties.TryGetValue(key, out PropertyValue? pv))
 271            {
 272                return value;
 273            }
 274
 275            pv.used = true;
 276
 277            string[] result = Ice.UtilInternal.StringUtil.splitString(pv.value, ", \t\r\n");
 278            if (result == null)
 279            {
 280                Util.getProcessLogger().warning(
 281                    $"mismatched quotes in property {key}'s value, returning default value");
 282                return value;
 283            }
 284            else
 285            {
 286                return result;
 287            }
 288        }
 289    }
 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    {
 300        lock (_mutex)
 301        {
 302            var result = new Dictionary<string, string>();
 303
 304            foreach (string s in _properties.Keys)
 305            {
 306                if (prefix.Length == 0 || s.StartsWith(prefix, StringComparison.Ordinal))
 307                {
 308                    PropertyValue pv = _properties[s];
 309                    pv.used = true;
 310                    result[s] = pv.value;
 311                }
 312            }
 313            return result;
 314        }
 315    }
 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        //
 328        if (key != null)
 329        {
 330            key = key.Trim();
 331        }
 332        if (key == null || key.Length == 0)
 333        {
 334            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.
 338        if (findIcePropertyArray(key) is PropertyArray propertyArray)
 339        {
 340            if (propertyArray.isOptIn && !_optInPrefixes.Contains(propertyArray.name))
 341            {
 342                throw new PropertyException(
 343                    $"Unable to set '{key}': property prefix '{propertyArray.name}' is opt-in and must be explicitly ena
 344            }
 345
 346            Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 347                throw new PropertyException($"unknown Ice property: {key}");
 348
 349            // If the property is deprecated, log a warning.
 350            if (prop.deprecated)
 351            {
 352                Util.getProcessLogger().warning($"setting deprecated property: {key}");
 353            }
 354        }
 355
 356        lock (_mutex)
 357        {
 358            // Set or clear the property.
 359            if (value is not null && value.Length > 0)
 360            {
 361                if (_properties.TryGetValue(key, out PropertyValue? pv))
 362                {
 363                    pv.value = value;
 364                }
 365                else
 366                {
 367                    pv = new PropertyValue(value, false);
 368                }
 369                _properties[key] = pv;
 370            }
 371            else
 372            {
 373                _properties.Remove(key);
 374            }
 375        }
 376    }
 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    {
 386        lock (_mutex)
 387        {
 388            string[] result = new string[_properties.Count];
 389            int i = 0;
 390            foreach (KeyValuePair<string, PropertyValue> entry in _properties)
 391            {
 392                result[i++] = "--" + entry.Key + "=" + entry.Value.value;
 393            }
 394            return result;
 395        }
 396    }
 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    {
 409        if (prefix.Length > 0 && prefix[^1] != '.')
 410        {
 411            prefix += '.';
 412        }
 413        prefix = "--" + prefix;
 414
 415        List<string> result = [];
 416        for (int i = 0; i < options.Length; i++)
 417        {
 418            string opt = options[i];
 419            if (opt.StartsWith(prefix, StringComparison.Ordinal))
 420            {
 421                if (!opt.Contains('=', StringComparison.Ordinal))
 422                {
 423                    opt += "=1";
 424                }
 425
 426                parseLine(opt[2..]);
 427            }
 428            else
 429            {
 430                result.Add(opt);
 431            }
 432        }
 433        string[] arr = new string[result.Count];
 434        if (arr.Length != 0)
 435        {
 436            result.CopyTo(arr);
 437        }
 438        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    {
 452        string[] args = options;
 453        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 454        {
 455            args = parseCommandLineOptions(name, args);
 456        }
 457        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        {
 468            using var sr = new StreamReader(file);
 469            parse(sr);
 470        }
 471        catch (IOException ex)
 472        {
 473            throw new FileException($"Cannot read '{file}'", ex);
 474        }
 475    }
 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    {
 483        var clonedProperties = new Properties();
 484        lock (_mutex)
 485        {
 486            foreach ((string key, PropertyValue value) in _properties)
 487            {
 488                clonedProperties._properties[key] = value.Clone();
 489            }
 490        }
 491        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.")]
 499    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    {
 507        lock (_mutex)
 508        {
 509            var unused = new List<string>();
 510            foreach ((string key, PropertyValue value) in _properties)
 511            {
 512                if (!value.used)
 513                {
 514                    unused.Add(key);
 515                }
 516            }
 517            return unused;
 518        }
 519    }
 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
 534        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 535        {
 536            if (prefix.StartsWith($"{name}.", StringComparison.Ordinal))
 537            {
 538                return;
 539            }
 540        }
 541
 542        var unknownProps = new List<string>();
 543        Dictionary<string, string> props = properties.getPropertiesForPrefix($"{prefix}.");
 544
 545        foreach (string p in props.Keys)
 546        {
 547            // Plus one to include the dot.
 548            if (findProperty(p[(prefix.Length + 1)..], propertyArray) is null)
 549            {
 550                unknownProps.Add(p);
 551            }
 552        }
 553
 554        if (unknownProps.Count > 0)
 555        {
 556            var message = new StringBuilder($"Found unknown properties for {propertyArray.name} : '{prefix}'");
 557            foreach (string s in unknownProps)
 558            {
 559                message.Append("\n    ");
 560                message.Append(s);
 561            }
 562
 563            throw new PropertyException(message.ToString());
 564        }
 565    }
 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    {
 574        int dotPos = key.IndexOf('.', StringComparison.Ordinal);
 575
 576        // If the key doesn't contain a dot, it's not a valid Ice property
 577        if (dotPos == -1)
 578        {
 579            return null;
 580        }
 581
 582        string prefix = key[..dotPos];
 583
 584        // Search for the property list that matches the prefix
 585        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    {
 596        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.
 600            if (key == prop.pattern)
 601            {
 602                if (prop.propertyArray is not null && prop.propertyArray.prefixOnly)
 603                {
 604                    return null;
 605                }
 606                return prop;
 607            }
 608            else if (prop.usesRegex && Regex.IsMatch(key, prop.pattern))
 609            {
 610                return prop;
 611            }
 612
 613            // If the property has a property class, check if the key is a prefix of the property.
 614            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
 621                if (key.Length > prop.pattern.Length && key.StartsWith($"{prop.pattern}.", StringComparison.Ordinal))
 622                {
 623                    // Plus one to skip the dot.
 624                    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.
 628                    if (findProperty(substring, prop.propertyArray) is Property subProp)
 629                    {
 630                        return subProp;
 631                    }
 632                }
 633            }
 634        }
 635        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.
 647        PropertyArray propertyArray = findIcePropertyArray(key) ??
 648            throw new PropertyException($"unknown Ice property: {key}");
 649
 650        // Find the property in the property array.
 651        Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 652            throw new PropertyException($"unknown Ice property: {key}");
 653
 654        return prop.defaultValue;
 655    }
 656
 657    private void parse(StreamReader input)
 658    {
 659        try
 660        {
 661            string? line;
 662            while ((line = input.ReadLine()) is not null)
 663            {
 664                parseLine(line);
 665            }
 666        }
 667        catch (IOException ex)
 668        {
 669            throw new SyscallException(ex);
 670        }
 671    }
 672
 673    private const int ParseStateKey = 0;
 674    private const int ParseStateValue = 1;
 675
 676    private void parseLine(string line)
 677    {
 678        string key = "";
 679        string val = "";
 680
 681        int state = ParseStateKey;
 682
 683        string whitespace = "";
 684        string escapedSpace = "";
 685        bool finished = false;
 686        for (int i = 0; i < line.Length; ++i)
 687        {
 688            char c = line[i];
 689            switch (state)
 690            {
 691                case ParseStateKey:
 692                {
 693                    switch (c)
 694                    {
 695                        case '\\':
 696                            if (i < line.Length - 1)
 697                            {
 698                                c = line[++i];
 699                                switch (c)
 700                                {
 701                                    case '\\':
 702                                    case '#':
 703                                    case '=':
 704                                        key += whitespace;
 705                                        whitespace = "";
 706                                        key += c;
 707                                        break;
 708
 709                                    case ' ':
 710                                        if (key.Length != 0)
 711                                        {
 712                                            whitespace += c;
 713                                        }
 714                                        break;
 715
 716                                    default:
 717                                        key += whitespace;
 718                                        whitespace = "";
 719                                        key += '\\';
 720                                        key += c;
 721                                        break;
 722                                }
 723                            }
 724                            else
 725                            {
 726                                key += whitespace;
 727                                key += c;
 728                            }
 729                            break;
 730
 731                        case ' ':
 732                        case '\t':
 733                        case '\r':
 734                        case '\n':
 735                            if (key.Length != 0)
 736                            {
 737                                whitespace += c;
 738                            }
 739                            break;
 740
 741                        case '=':
 742                            whitespace = "";
 743                            state = ParseStateValue;
 744                            break;
 745
 746                        case '#':
 747                            finished = true;
 748                            break;
 749
 750                        default:
 751                            key += whitespace;
 752                            whitespace = "";
 753                            key += c;
 754                            break;
 755                    }
 756                    break;
 757                }
 758
 759                case ParseStateValue:
 760                {
 761                    switch (c)
 762                    {
 763                        case '\\':
 764                            if (i < line.Length - 1)
 765                            {
 766                                c = line[++i];
 767                                switch (c)
 768                                {
 769                                    case '\\':
 770                                    case '#':
 771                                    case '=':
 772                                        val += val.Length == 0 ? escapedSpace : whitespace;
 773                                        whitespace = "";
 774                                        escapedSpace = "";
 775                                        val += c;
 776                                        break;
 777
 778                                    case ' ':
 779                                        whitespace += c;
 780                                        escapedSpace += c;
 781                                        break;
 782
 783                                    default:
 784                                        val += val.Length == 0 ? escapedSpace : whitespace;
 785                                        whitespace = "";
 786                                        escapedSpace = "";
 787                                        val += '\\';
 788                                        val += c;
 789                                        break;
 790                                }
 791                            }
 792                            else
 793                            {
 794                                val += val.Length == 0 ? escapedSpace : whitespace;
 795                                val += c;
 796                            }
 797                            break;
 798
 799                        case ' ':
 800                        case '\t':
 801                        case '\r':
 802                        case '\n':
 803                            if (val.Length != 0)
 804                            {
 805                                whitespace += c;
 806                            }
 807                            break;
 808
 809                        case '#':
 810                            finished = true;
 811                            break;
 812
 813                        default:
 814                            val += val.Length == 0 ? escapedSpace : whitespace;
 815                            whitespace = "";
 816                            escapedSpace = "";
 817                            val += c;
 818                            break;
 819                    }
 820                    break;
 821                }
 822            }
 823            if (finished)
 824            {
 825                break;
 826            }
 827        }
 828        val += escapedSpace;
 829
 830        if ((state == ParseStateKey && key.Length != 0) || (state == ParseStateValue && key.Length == 0))
 831        {
 832            Util.getProcessLogger().warning("invalid config file entry: \"" + line + "\"");
 833            return;
 834        }
 835        else if (key.Length == 0)
 836        {
 837            return;
 838        }
 839
 840        setProperty(key, val);
 841    }
 842
 843    private void loadConfig()
 844    {
 845        string val = getIceProperty("Ice.Config");
 846        if (val.Length == 0 || val == "1")
 847        {
 848            string? s = Environment.GetEnvironmentVariable("ICE_CONFIG");
 849            if (s != null && s.Length != 0)
 850            {
 851                val = s;
 852            }
 853        }
 854
 855        if (val.Length > 0)
 856        {
 857            char[] separator = { ',' };
 858            string[] files = val.Split(separator);
 859            for (int i = 0; i < files.Length; i++)
 860            {
 861                load(files[i].Trim());
 862            }
 863
 864            _properties["Ice.Config"] = new PropertyValue(val, true);
 865        }
 866    }
 867
 868    private readonly Dictionary<string, PropertyValue> _properties = [];
 869    private readonly List<string> _optInPrefixes = [];
 870    private readonly object _mutex = new();
 871}