< Summary

Information
Class: Ice.Properties.PropertyValue
Assembly: Ice
File(s): /_/csharp/src/Ice/Properties.cs
Tag: 91_21789722663
Line coverage
100%
Covered lines: 7
Uncovered lines: 0
Coverable lines: 7
Total lines: 887
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)

/_/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{
 22    private readonly Dictionary<string, PropertyValue> _propertySet = [];
 23    private readonly ImmutableList<string> _optInPrefixes = [];
 24    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>
 29    public Properties()
 30    {
 31    }
 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)
 48        : this(defaults) =>
 49        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#
 65    public Properties(ref string[] args, ImmutableList<string> optInPrefixes)
 66    {
 67        _optInPrefixes = optInPrefixes;
 68        loadArgs(ref args);
 69    }
 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    {
 79        lock (_mutex)
 80        {
 81            string result = "";
 82            if (_propertySet.TryGetValue(key, out PropertyValue? pv))
 83            {
 84                pv.used = true;
 85                result = pv.value;
 86            }
 87            return result;
 88        }
 89    }
 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>
 98    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    {
 109        lock (_mutex)
 110        {
 111            string result = value;
 112            if (_propertySet.TryGetValue(key, out PropertyValue? pv))
 113            {
 114                pv.used = true;
 115                result = pv.value;
 116            }
 117            return result;
 118        }
 119    }
 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>
 128    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    {
 140        string defaultValueString = getDefaultProperty(key);
 141        int defaultValue = 0;
 142        if (defaultValueString.Length > 0)
 143        {
 144            defaultValue = int.Parse(defaultValueString, CultureInfo.InvariantCulture);
 145        }
 146
 147        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    {
 160        lock (_mutex)
 161        {
 162            if (!_propertySet.TryGetValue(key, out PropertyValue? pv))
 163            {
 164                return value;
 165            }
 166            pv.used = true;
 167            try
 168            {
 169                return int.Parse(pv.value, CultureInfo.InvariantCulture);
 170            }
 171            catch (FormatException)
 172            {
 173                throw new PropertyException($"property '{key}' has an invalid integer value: '{pv.value}'");
 174            }
 175        }
 176    }
 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>
 188    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    {
 203        string[] defaultList = UtilInternal.StringUtil.splitString(getDefaultProperty(key), ", \t\r\n");
 204        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    {
 220        value ??= [];
 221
 222        lock (_mutex)
 223        {
 224            if (!_propertySet.TryGetValue(key, out PropertyValue? pv))
 225            {
 226                return value;
 227            }
 228
 229            pv.used = true;
 230
 231            string[] result = Ice.UtilInternal.StringUtil.splitString(pv.value, ", \t\r\n");
 232            if (result == null)
 233            {
 234                Util.getProcessLogger().warning(
 235                    $"mismatched quotes in property {key}'s value, returning default value");
 236                return value;
 237            }
 238            else
 239            {
 240                return result;
 241            }
 242        }
 243    }
 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    {
 254        lock (_mutex)
 255        {
 256            var result = new Dictionary<string, string>();
 257
 258            foreach (string s in _propertySet.Keys)
 259            {
 260                if (prefix.Length == 0 || s.StartsWith(prefix, StringComparison.Ordinal))
 261                {
 262                    PropertyValue pv = _propertySet[s];
 263                    pv.used = true;
 264                    result[s] = pv.value;
 265                }
 266            }
 267            return result;
 268        }
 269    }
 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        //
 282        if (key != null)
 283        {
 284            key = key.Trim();
 285        }
 286        if (key == null || key.Length == 0)
 287        {
 288            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.
 292        if (findIcePropertyArray(key) is PropertyArray propertyArray)
 293        {
 294            if (propertyArray.isOptIn && !_optInPrefixes.Contains(propertyArray.name))
 295            {
 296                throw new PropertyException(
 297                    $"Unable to set '{key}': property prefix '{propertyArray.name}' is opt-in and must be explicitly ena
 298            }
 299
 300            Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 301                throw new PropertyException($"unknown Ice property: {key}");
 302
 303            // If the property is deprecated, log a warning.
 304            if (prop.deprecated)
 305            {
 306                Util.getProcessLogger().warning($"setting deprecated property: {key}");
 307            }
 308        }
 309
 310        lock (_mutex)
 311        {
 312            // Set or clear the property.
 313            if (value is not null && value.Length > 0)
 314            {
 315                if (_propertySet.TryGetValue(key, out PropertyValue? pv))
 316                {
 317                    pv.value = value;
 318                }
 319                else
 320                {
 321                    pv = new PropertyValue(value, false);
 322                }
 323                _propertySet[key] = pv;
 324            }
 325            else
 326            {
 327                _propertySet.Remove(key);
 328            }
 329        }
 330    }
 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    {
 340        lock (_mutex)
 341        {
 342            string[] result = new string[_propertySet.Count];
 343            int i = 0;
 344            foreach (KeyValuePair<string, PropertyValue> entry in _propertySet)
 345            {
 346                result[i++] = "--" + entry.Key + "=" + entry.Value.value;
 347            }
 348            return result;
 349        }
 350    }
 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    {
 363        if (prefix.Length > 0 && prefix[^1] != '.')
 364        {
 365            prefix += '.';
 366        }
 367        prefix = "--" + prefix;
 368
 369        List<string> result = [];
 370        for (int i = 0; i < options.Length; i++)
 371        {
 372            string opt = options[i];
 373            if (opt.StartsWith(prefix, StringComparison.Ordinal))
 374            {
 375                if (!opt.Contains('=', StringComparison.Ordinal))
 376                {
 377                    opt += "=1";
 378                }
 379
 380                parseLine(opt[2..]);
 381            }
 382            else
 383            {
 384                result.Add(opt);
 385            }
 386        }
 387        string[] arr = new string[result.Count];
 388        if (arr.Length != 0)
 389        {
 390            result.CopyTo(arr);
 391        }
 392        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    {
 406        string[] args = options;
 407        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 408        {
 409            args = parseCommandLineOptions(name, args);
 410        }
 411        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        {
 422            using var sr = new StreamReader(file);
 423            parse(sr);
 424        }
 425        catch (IOException ex)
 426        {
 427            throw new FileException($"Cannot read '{file}'", ex);
 428        }
 429    }
 430
 431    /// <summary>
 432    /// Create a copy of this property set.
 433    /// </summary>
 434    /// <returns>A copy of this property set.</returns>
 435    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.")]
 442    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    {
 450        lock (_mutex)
 451        {
 452            var unused = new List<string>();
 453            foreach ((string key, PropertyValue value) in _propertySet)
 454            {
 455                if (!value.used)
 456                {
 457                    unused.Add(key);
 458                }
 459            }
 460            return unused;
 461        }
 462    }
 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
 477        foreach (string? name in PropertyNames.validProps.Select(p => p.name))
 478        {
 479            if (prefix.StartsWith($"{name}.", StringComparison.Ordinal))
 480            {
 481                return;
 482            }
 483        }
 484
 485        var unknownProps = new List<string>();
 486        Dictionary<string, string> props = properties.getPropertiesForPrefix($"{prefix}.");
 487
 488        foreach (string p in props.Keys)
 489        {
 490            // Plus one to include the dot.
 491            if (findProperty(p[(prefix.Length + 1)..], propertyArray) is null)
 492            {
 493                unknownProps.Add(p);
 494            }
 495        }
 496
 497        if (unknownProps.Count > 0)
 498        {
 499            var message = new StringBuilder($"Found unknown properties for {propertyArray.name} : '{prefix}'");
 500            foreach (string s in unknownProps)
 501            {
 502                message.Append("\n    ");
 503                message.Append(s);
 504            }
 505
 506            throw new PropertyException(message.ToString());
 507        }
 508    }
 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    {
 517        int dotPos = key.IndexOf('.', StringComparison.Ordinal);
 518
 519        // If the key doesn't contain a dot, it's not a valid Ice property
 520        if (dotPos == -1)
 521        {
 522            return null;
 523        }
 524
 525        string prefix = key[..dotPos];
 526
 527        // Search for the property list that matches the prefix
 528        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    {
 539        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.
 543            if (key == prop.pattern)
 544            {
 545                if (prop.propertyArray is not null && prop.propertyArray.prefixOnly)
 546                {
 547                    return null;
 548                }
 549                return prop;
 550            }
 551            else if (prop.usesRegex && Regex.IsMatch(key, prop.pattern))
 552            {
 553                return prop;
 554            }
 555
 556            // If the property has a property class, check if the key is a prefix of the property.
 557            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
 564                if (key.Length > prop.pattern.Length && key.StartsWith($"{prop.pattern}.", StringComparison.Ordinal))
 565                {
 566                    // Plus one to skip the dot.
 567                    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.
 571                    if (findProperty(substring, prop.propertyArray) is Property subProp)
 572                    {
 573                        return subProp;
 574                    }
 575                }
 576            }
 577        }
 578        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.
 590        PropertyArray propertyArray = findIcePropertyArray(key) ??
 591            throw new PropertyException($"unknown Ice property: {key}");
 592
 593        // Find the property in the property array.
 594        Property prop = findProperty(key[(propertyArray.name.Length + 1)..], propertyArray) ??
 595            throw new PropertyException($"unknown Ice property: {key}");
 596
 597        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>
 604    private Properties(Properties? defaults)
 605    {
 606        if (defaults is not null)
 607        {
 608            lock (defaults._mutex)
 609            {
 610                foreach (KeyValuePair<string, PropertyValue> entry in defaults._propertySet)
 611                {
 612                    _propertySet[entry.Key] = entry.Value.Clone();
 613                }
 614            }
 615
 616            _optInPrefixes = defaults._optInPrefixes;
 617        }
 618        // else remains empty
 619    }
 620
 621    // Helper method called exclusively by constructors.
 622    private void loadArgs(ref string[] args)
 623    {
 624        bool loadConfigFiles = false;
 625
 626        for (int i = 0; i < args.Length; i++)
 627        {
 628            if (args[i].StartsWith("--Ice.Config", StringComparison.Ordinal))
 629            {
 630                string line = args[i];
 631                if (!line.Contains('=', StringComparison.Ordinal))
 632                {
 633                    line += "=1";
 634                }
 635                parseLine(line[2..]);
 636                loadConfigFiles = true;
 637
 638                string[] arr = new string[args.Length - 1];
 639                Array.Copy(args, 0, arr, 0, i);
 640                if (i < args.Length - 1)
 641                {
 642                    Array.Copy(args, i + 1, arr, i, args.Length - i - 1);
 643                }
 644                args = arr;
 645            }
 646        }
 647
 648        if (!loadConfigFiles)
 649        {
 650            // If Ice.Config is not set, load from ICE_CONFIG (if set).
 651            loadConfigFiles = !_propertySet.ContainsKey("Ice.Config");
 652        }
 653
 654        if (loadConfigFiles)
 655        {
 656            loadConfig();
 657        }
 658
 659        args = parseIceCommandLineOptions(args);
 660    }
 661
 662    private void parse(StreamReader input)
 663    {
 664        try
 665        {
 666            string? line;
 667            while ((line = input.ReadLine()) is not null)
 668            {
 669                parseLine(line);
 670            }
 671        }
 672        catch (IOException ex)
 673        {
 674            throw new SyscallException(ex);
 675        }
 676    }
 677
 678    private const int ParseStateKey = 0;
 679    private const int ParseStateValue = 1;
 680
 681    private void parseLine(string line)
 682    {
 683        string key = "";
 684        string val = "";
 685
 686        int state = ParseStateKey;
 687
 688        string whitespace = "";
 689        string escapedSpace = "";
 690        bool finished = false;
 691        for (int i = 0; i < line.Length; ++i)
 692        {
 693            char c = line[i];
 694            switch (state)
 695            {
 696                case ParseStateKey:
 697                {
 698                    switch (c)
 699                    {
 700                        case '\\':
 701                            if (i < line.Length - 1)
 702                            {
 703                                c = line[++i];
 704                                switch (c)
 705                                {
 706                                    case '\\':
 707                                    case '#':
 708                                    case '=':
 709                                        key += whitespace;
 710                                        whitespace = "";
 711                                        key += c;
 712                                        break;
 713
 714                                    case ' ':
 715                                        if (key.Length != 0)
 716                                        {
 717                                            whitespace += c;
 718                                        }
 719                                        break;
 720
 721                                    default:
 722                                        key += whitespace;
 723                                        whitespace = "";
 724                                        key += '\\';
 725                                        key += c;
 726                                        break;
 727                                }
 728                            }
 729                            else
 730                            {
 731                                key += whitespace;
 732                                key += c;
 733                            }
 734                            break;
 735
 736                        case ' ':
 737                        case '\t':
 738                        case '\r':
 739                        case '\n':
 740                            if (key.Length != 0)
 741                            {
 742                                whitespace += c;
 743                            }
 744                            break;
 745
 746                        case '=':
 747                            whitespace = "";
 748                            state = ParseStateValue;
 749                            break;
 750
 751                        case '#':
 752                            finished = true;
 753                            break;
 754
 755                        default:
 756                            key += whitespace;
 757                            whitespace = "";
 758                            key += c;
 759                            break;
 760                    }
 761                    break;
 762                }
 763
 764                case ParseStateValue:
 765                {
 766                    switch (c)
 767                    {
 768                        case '\\':
 769                            if (i < line.Length - 1)
 770                            {
 771                                c = line[++i];
 772                                switch (c)
 773                                {
 774                                    case '\\':
 775                                    case '#':
 776                                    case '=':
 777                                        val += val.Length == 0 ? escapedSpace : whitespace;
 778                                        whitespace = "";
 779                                        escapedSpace = "";
 780                                        val += c;
 781                                        break;
 782
 783                                    case ' ':
 784                                        whitespace += c;
 785                                        escapedSpace += c;
 786                                        break;
 787
 788                                    default:
 789                                        val += val.Length == 0 ? escapedSpace : whitespace;
 790                                        whitespace = "";
 791                                        escapedSpace = "";
 792                                        val += '\\';
 793                                        val += c;
 794                                        break;
 795                                }
 796                            }
 797                            else
 798                            {
 799                                val += val.Length == 0 ? escapedSpace : whitespace;
 800                                val += c;
 801                            }
 802                            break;
 803
 804                        case ' ':
 805                        case '\t':
 806                        case '\r':
 807                        case '\n':
 808                            if (val.Length != 0)
 809                            {
 810                                whitespace += c;
 811                            }
 812                            break;
 813
 814                        case '#':
 815                            finished = true;
 816                            break;
 817
 818                        default:
 819                            val += val.Length == 0 ? escapedSpace : whitespace;
 820                            whitespace = "";
 821                            escapedSpace = "";
 822                            val += c;
 823                            break;
 824                    }
 825                    break;
 826                }
 827            }
 828            if (finished)
 829            {
 830                break;
 831            }
 832        }
 833        val += escapedSpace;
 834
 835        if ((state == ParseStateKey && key.Length != 0) || (state == ParseStateValue && key.Length == 0))
 836        {
 837            Util.getProcessLogger().warning("invalid config file entry: \"" + line + "\"");
 838            return;
 839        }
 840        else if (key.Length == 0)
 841        {
 842            return;
 843        }
 844
 845        setProperty(key, val);
 846    }
 847
 848    private void loadConfig()
 849    {
 850        string val = getIceProperty("Ice.Config");
 851        if (val.Length == 0 || val == "1")
 852        {
 853            string? s = Environment.GetEnvironmentVariable("ICE_CONFIG");
 854            if (s != null && s.Length != 0)
 855            {
 856                val = s;
 857            }
 858        }
 859
 860        if (val.Length > 0)
 861        {
 862            char[] separator = { ',' };
 863            string[] files = val.Split(separator);
 864            for (int i = 0; i < files.Length; i++)
 865            {
 866                load(files[i].Trim());
 867            }
 868
 869            _propertySet["Ice.Config"] = new PropertyValue(val, true);
 870        }
 871    }
 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}