Beispiel #1
0
        /// <summary>
        /// Register a bunch of currencies, at once.
        /// </summary>
        /// <remarks>
        /// <para>For explanations on when and how to use this method, please see
        /// <see cref="RegisterCurrency(String, Int16?)"/>.</para>
        /// <para>This method is thread-safe and ensures that the registry is not modified
        /// if anything goes wrong.</para>
        /// </remarks>
        /// <param name="currencies">The <see cref="Dictionary{TKey, TValue}"/> that contains
        /// the codes and minor units for the new currencies.</param>
        /// <returns>true if ALL currencies has been added; otherwise, false.</returns>
        /// <exception cref="ArgumentException">Thrown if a candidate currency does not meet
        /// the requirements: its code must be of length 3 and made of ASCII
        /// uppercase letters, and its minor units, if not null, must be greater than
        /// or equal to zero and lower than or equal to 28.</exception>
        public static bool RegisterCurrencies(Dictionary <string, short?> currencies)
        {
            Require.NotNull(currencies, nameof(currencies));

            // Before any actual work, we check if the input looks fine.
            foreach (var pair in currencies)
            {
                string code = pair.Key;
                if (code == null)
                {
                    throw new ArgumentException(Strings_Money.Argument_CurrencyCodeIsNull, nameof(currencies));
                }
                if (!ValidateCode(code))
                {
                    throw new ArgumentException(
                              Format.Current(Strings_Money.Argument_InvalidCurrencyCode, code),
                              nameof(currencies));
                }

                short?minorUnits = pair.Value;
                if (!ValidateMinorUnits(minorUnits))
                {
                    throw new ArgumentException(
                              Format.Current(Strings_Money.Argument_InvalidCurrencyMinorUnits, minorUnits),
                              nameof(currencies));
                }

                if (Codes.ContainsKey(code) || WithdrawnCodes.Contains(code))
                {
                    return(false);
                }
            }

            lock (s_UserCodesLock)
            {
                // We work on a temporary copy of s_UserCodes. This is not very efficient but
                // ensures that s_UserCodes does not end up in a broken state if anything wrong
                // happens. Performance-wise, it is not as bad as it looks. First, we do not expect
                // this method to be called more than a couple of times during the lifetime of the
                // application, if ever. Second, s_UserCodes should be very small, therefore the
                // copy should be pretty fast and the operation should be rather inexpensive
                // from a memory perspective.
                // NB: This manip has nothing to do with thread-safety, it is only done for
                // correctness. If Add() fails, the method behaves as advertised - it does not
                // change the registry.
                var tmpCopy = s_UserCodes.ToDictionary(pair => pair.Key, pair => pair.Value);
                foreach (var pair in currencies)
                {
                    if (tmpCopy.ContainsKey(pair.Key))
                    {
                        return(false);
                    }
                    tmpCopy.Add(pair.Key, pair.Value);
                }

                SwapReferences(ref s_UserCodes, ref tmpCopy);
            }

            return(true);
        }
Beispiel #2
0
        public static Currency?TryCreate(string code, CurrencyTypes types)
        {
            Require.NotNull(code, nameof(code));

            if (types.Contains(CurrencyTypes.Active))
            {
                if (Codes.TryGetValue(code, out short?minorUnits))
                {
                    return(new Currency(code, minorUnits));
                }
            }

            if (types.Contains(CurrencyTypes.UserDefined))
            {
                if (UserCodes.TryGetValue(code, out short?minorUnits))
                {
                    return(new Currency(code, minorUnits));
                }
            }

            // At last, we look for a withdrawn currency.
            if (types.Contains(CurrencyTypes.Withdrawn) && WithdrawnCodes.Contains(code))
            {
                // For withdrawn currencies, ISO 4217 does not provide any information
                // concerning the minor units. See the property HasMinorCurrency for more info.
                return(new Currency(code, UnknownMinorUnits));
            }

            return(null);
        }
Beispiel #3
0
        /// <summary>
        /// Register a currency not part of ISO 4217.
        /// <para>It can also be useful when the library is not up-to-date with the ISO 4217 list
        /// of active currencies.</para>
        /// </summary>
        /// <remarks>
        /// <para>All currencies registered via this method have the <see cref="CurrencyTypes.UserDefined"/>
        /// type. In particular, they inherit the <see cref="CurrencyTypes.Circulating"/> type; as a
        /// consequence, you can not register a withdrawn currency.</para>
        /// <para>If you have more than one currency to register, you should use
        /// <see cref="RegisterCurrencies(Dictionary{String, Int16?})"/> instead.</para>
        /// <para>This method is thread-safe.</para>
        /// </remarks>
        /// <param name="code">The three letters code.</param>
        /// <param name="minorUnits">The number of minor units; null if the currency does not have
        /// a minor currency unit and <see cref="UnknownMinorUnits"/> if the status is unknown.</param>
        /// <returns>true if the operation succeeded; otherwise, false.</returns>
        /// <exception cref="ArgumentException">Thrown if the candidate currency does not meet
        /// the requirements: a <paramref name="code"/> must be of length 3 and made of ASCII
        /// uppercase letters, and <paramref name="minorUnits"/>, if not null, must be greater than
        /// or equal to zero and lower than or equal to 28.</exception>
        public static bool RegisterCurrency(string code, short?minorUnits)
        {
            // REVIEW: Should we relax the constraints we impose on the code value for user-defined
            // currencies? JavaMoney does it, but I am not very convinced that there are enough
            // good reasons for the complications it will imply. Nevertheless, if we decided
            // to do this, there will a problem with MinorCurrencyCode and we would have to review
            // all guards wherever we accept a code as input.
            Require.NotNull(code, nameof(code));
            // A currency code MUST be composed of exactly 3 letters.
            // A currency code MUST only contain uppercase ASCII letters.
            Require.True(
                ValidateCode(code),
                nameof(code),
                Format.Current(Strings_Money.Argument_InvalidCurrencyCode, code));
            Require.True(
                ValidateMinorUnits(minorUnits),
                nameof(minorUnits),
                Format.Current(Strings_Money.Argument_InvalidCurrencyMinorUnits, minorUnits));

            // Codes and WithdrawnCodes are immutable, so no concurrency problems here.
            if (Codes.ContainsKey(code) || WithdrawnCodes.Contains(code))
            {
                return(false);
            }

            lock (s_UserCodesLock)
            {
                if (!s_UserCodes.ContainsKey(code))
                {
                    s_UserCodes.Add(code, minorUnits);

                    return(true);
                }
            }

            return(false);
        }