Exemplo n.º 1
0
        /// <summary>Convert LocalMinute to ISO 8601 4 digit int hhmm format.</summary>
        public static int ToIsoInt(this LocalMinute value)
        {
            // Serialized to one minute precision in ISO 8601 4 digit int hhmm format
            int result = value.Hour * 100 + value.Minute;

            return(result);
        }
Exemplo n.º 2
0
        /// <summary>Convert LocalMinute to ISO 8601 string in hh:mm format.</summary>
        public static string ToIsoString(this LocalMinute value)
        {
            // LocalMinute is serialized to ISO 8601 string in hh:mm format
            string result = String.Join(":", value.Hour.ToString("00"), value.Minute.ToString("00"));

            return(result);
        }
Exemplo n.º 3
0
        /// <summary>Parse string using the specified value type and return the resulting variant.</summary>
        public static Variant Parse(VariantType valueType, string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                // Empty value
                return(new Variant());
            }
            else
            {
                // Switch on type of default value
                switch (valueType)
                {
                case VariantType.String:
                    return(new Variant(value));

                case VariantType.Double:
                    double doubleResult = double.Parse(value);
                    return(new Variant(doubleResult));

                case VariantType.Bool:
                    bool boolResult = bool.Parse(value);
                    return(new Variant(boolResult));

                case VariantType.Int:
                    int intResult = int.Parse(value);
                    return(new Variant(intResult));

                case VariantType.Long:
                    long longResult = long.Parse(value);
                    return(new Variant(longResult));

                case VariantType.LocalDate:
                    LocalDate dateResult = LocalDateUtil.Parse(value);
                    return(new Variant(dateResult));

                case VariantType.LocalTime:
                    LocalTime timeResult = LocalTimeUtil.Parse(value);
                    return(new Variant(timeResult));

                case VariantType.LocalMinute:
                    LocalMinute minuteResult = LocalMinuteUtil.Parse(value);
                    return(new Variant(minuteResult));

                case VariantType.LocalDateTime:
                    LocalDateTime dateTimeResult = LocalDateTimeUtil.Parse(value);
                    return(new Variant(dateTimeResult));

                case VariantType.Instant:
                    Instant instantResult = InstantUtil.Parse(value);
                    return(new Variant(instantResult));

                case VariantType.Enum:
                    throw new Exception("Variant cannot be created as enum without specifying enum typename.");

                default:
                    // Error message if any other type
                    throw new Exception("Unknown value type when parsing string into variant.");
                }
            }
        }
Exemplo n.º 4
0
        /// <summary>
        /// Convert LocalTime to LocalMinute.
        ///
        /// Error message unless the local time falls exactly on the minute to nanosecond precision
        /// </summary>
        public static LocalMinute ToLocalMinute(this LocalTime value)
        {
            // Check if milliseconds is zero (that will be visible when LocalTime is serialized)
            if (value.Second != 0)
            {
                throw new Exception(
                          $"LocalTime {value} cannot be converted to LocalMinute because it has non-zero seconds.");
            }
            if (value.Millisecond != 0)
            {
                throw new Exception(
                          $"LocalTime {value} cannot be converted to LocalMinute because it has non-zero milliseconds.");
            }

            // Check if nanoseconds are present
            if (value.NanosecondOfSecond != 0)
            {
                throw new Exception(
                          $"LocalTime {value} cannot be converted to LocalMinute because it has non-zero nanoseconds of {value.NanosecondOfSecond}.");
            }

            LocalMinute result = new LocalMinute(value.Hour, value.Minute);

            return(result);
        }
        /// <summary>Parse ISO 8601 4 digit int in hhmm format, throw if invalid format.</summary>
        public static LocalMinute FromIsoInt(int value)
        {
            // Extract
            int hour = value / 100;

            value -= hour * 100;
            int minute = value;

            // Create new LocalMinute object, validates values on input
            var result = new LocalMinute(hour, minute);

            return(result);
        }
Exemplo n.º 6
0
 /// <summary>Convert LocalMinute to variant.</summary>
 public static Variant ToVariant(this LocalMinute value)
 {
     return(new Variant(value));
 }
Exemplo n.º 7
0
 /// <summary>
 /// Unlike LocalDate and LocalDateTime, the LocalMinute class
 /// has no special value that can be treated as Empty.
 /// Its default constructed value is 00:00 (midnight).
 /// </summary>
 public static bool HasValue(this LocalMinute value)
 {
     return(true);
 }
Exemplo n.º 8
0
        /// <summary>Parse string using the specified value type and return the resulting variant.</summary>
        public static Variant Parse <T>(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                // Empty value
                return(new Variant());
            }
            else
            {
                // Switch on type of default value
                switch (default(T))
                {
                case string stringValue:
                    return(new Variant(value));

                case double doubleValue:
                    double doubleResult = double.Parse(value);
                    return(new Variant(doubleResult));

                case bool boolValue:
                    bool boolResult = bool.Parse(value);
                    return(new Variant(boolResult));

                case int intValue:
                    int intResult = int.Parse(value);
                    return(new Variant(intResult));

                case long longValue:
                    long longResult = long.Parse(value);
                    return(new Variant(longResult));

                case LocalDate dateValue:
                    LocalDate dateResult = LocalDateUtil.Parse(value);
                    return(new Variant(dateResult));

                case LocalTime timeValue:
                    LocalTime timeResult = LocalTimeUtil.Parse(value);
                    return(new Variant(timeResult));

                case LocalMinute minuteValue:
                    LocalMinute minuteResult = LocalMinuteUtil.Parse(value);
                    return(new Variant(minuteResult));

                case LocalDateTime dateTimeValue:
                    LocalDateTime dateTimeResult = LocalDateTimeUtil.Parse(value);
                    return(new Variant(dateTimeResult));

                case Instant instantValue:
                    Instant instantResult = InstantUtil.Parse(value);
                    return(new Variant(instantResult));

                case Enum enumValue:
                    object enumResult = Enum.Parse(typeof(T), value);
                    return(new Variant(enumResult));

                default:
                    // Error message if any other type
                    throw new Exception(GetWrongTypeErrorMessage(default(T)));
                }
            }
        }
Exemplo n.º 9
0
        //--- PRIVATE

        /// <summary>
        /// Populate key elements from an array of tokens starting
        /// at the specified token index. Elements that are themselves
        /// keys may use more than one token.
        ///
        /// This method returns the index of the first unused token.
        /// The returned value is the same as the length of the tokens
        /// array if all tokens are used.
        ///
        /// If key AKey has two elements, B and C, where
        ///
        /// * B has type BKey which has two string elements, and
        /// * C has type string,
        ///
        /// the semicolon delimited key has the following format:
        ///
        /// BToken1;BToken2;CToken
        ///
        /// To avoid serialization format uncertainty, key elements
        /// can have any atomic type except Double.
        /// </summary>
        private int PopulateFrom(string[] tokens, int tokenIndex)
        {
            // Get key elements using reflection
            var elementInfoArray = DataTypeInfo.GetOrCreate(this).DataElements;

            // If singleton is detected process it separately, then exit
            if (elementInfoArray.Length == 0)
            {
                // Check that string key is empty
                if (tokens.Length != 1 || tokens[0] != String.Empty)
                {
                    throw new Exception($"Type {GetType()} has key {string.Join(";", tokens)} while " +
                                        $"for a singleton the key must be an empty string (String.Empty). " +
                                        $"Singleton key is a key that has no key elements.");
                }

                // Return the length of empty key which consists of one (empty) token
                return(1);
            }

            // Check that there are enough remaining tokens in the key for each key element
            if (tokens.Length - tokenIndex < elementInfoArray.Length)
            {
                throw new Exception(
                          $"Key of type {GetType().Name} requires at least {elementInfoArray.Length} elements " +
                          $"{String.Join(";", elementInfoArray.Select(p => p.Name).ToArray())} while there are " +
                          $"only {tokens.Length - tokenIndex} remaining key tokens: {string.Join(";", tokens)}.");
            }

            // Iterate over element info elements, advancing tokenIndex by the required
            // number of tokens for each element. In case of embedded keys, the value of
            // tokenIndex is advanced by the recursive call to InitFromTokens method
            // of the embedded key.
            foreach (var elementInfo in elementInfoArray)
            {
                // Get element type
                Type elementType = elementInfo.PropertyType;

                // Convert string token to value depending on elementType
                if (elementType == typeof(string))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token = tokens[tokenIndex++];
                    elementInfo.SetValue(this, token);
                }
                else if (elementType == typeof(double) || elementType == typeof(double?))
                {
                    throw new Exception(
                              $"Key element {elementInfo.Name} has type Double. Elements of this type " +
                              $"cannot be part of key due to serialization format uncertainty.");
                }
                else if (elementType == typeof(bool) || elementType == typeof(bool?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    bool   tokenValue = bool.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(int) || elementType == typeof(int?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    int    tokenValue = int.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(long) || elementType == typeof(long?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    long   tokenValue = long.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalDate) || elementType == typeof(LocalDate?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalDate is represented as readable int in
                    // non-delimited yyyymmdd format, not as delimited ISO string.
                    //
                    // First parse the string to int, then convert int to LocalDate.
                    string token = tokens[tokenIndex++];
                    if (!Int32.TryParse(token, out int isoInt))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalDate and value {token} " +
                                  $"that cannot be converted to readable int in non-delimited yyyymmdd format.");
                    }

                    LocalDate tokenValue = LocalDateUtil.FromIsoInt(isoInt);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalTime) || elementType == typeof(LocalTime?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalTime is represented as readable int in
                    // non-delimited hhmmssfff format, not as delimited ISO string.
                    //
                    // First parse the string to int, then convert int to LocalTime.
                    string token = tokens[tokenIndex++];
                    if (!Int32.TryParse(token, out int isoInt))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalTime and value {token} " +
                                  $"that cannot be converted to readable int in non-delimited hhmmssfff format.");
                    }

                    LocalTime tokenValue = LocalTimeUtil.FromIsoInt(isoInt);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalMinute) || elementType == typeof(LocalMinute?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalMinute is represented as readable int in
                    // non-delimited hhmm format, not as delimited ISO string.
                    //
                    // First parse the string to int, then convert int to LocalTime.
                    string token = tokens[tokenIndex++];
                    if (!Int32.TryParse(token, out int isoInt))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalMinute and value {token} " +
                                  $"that cannot be converted to readable int in non-delimited hhmm format.");
                    }

                    LocalMinute tokenValue = LocalMinuteUtil.FromIsoInt(isoInt);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(LocalDateTime) || elementType == typeof(LocalDateTime?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, LocalDateTime is represented as readable long in
                    // non-delimited yyyymmddhhmmssfff format, not as delimited ISO string.
                    //
                    // First parse the string to long, then convert int to LocalDateTime.
                    string token = tokens[tokenIndex++];
                    if (!Int64.TryParse(token, out long isoLong))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type LocalDateTime and value {token} " +
                                  $"that cannot be converted to readable long in non-delimited yyyymmddhhmmssfff format.");
                    }

                    LocalDateTime tokenValue = LocalDateTimeUtil.FromIsoLong(isoLong);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(Instant) || elementType == typeof(Instant?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    // Inside the key, Instant is represented as readable long in
                    // non-delimited yyyymmddhhmmssfff format, not as delimited ISO string.
                    //
                    // First parse the string to long, then convert int to Instant.
                    string token = tokens[tokenIndex++];
                    if (!Int64.TryParse(token, out long isoLong))
                    {
                        throw new Exception(
                                  $"Element {elementInfo.Name} of key type {GetType().Name} has type Instant and value {token} " +
                                  $"that cannot be converted to readable long in non-delimited yyyymmddhhmmssfff format.");
                    }

                    Instant tokenValue = InstantUtil.FromIsoLong(isoLong);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType == typeof(TemporalId) || elementType == typeof(TemporalId?))
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string     token      = tokens[tokenIndex++];
                    TemporalId tokenValue = TemporalId.Parse(token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (elementType.BaseType == typeof(Enum)) // TODO Support nullable Enum in key
                {
                    CheckTokenNotEmpty(tokens, tokenIndex);

                    string token      = tokens[tokenIndex++];
                    object tokenValue = Enum.Parse(elementType, token);
                    elementInfo.SetValue(this, tokenValue);
                }
                else if (typeof(Key).IsAssignableFrom(elementType))
                {
                    Key keyElement = (Key)Activator.CreateInstance(elementType);
                    tokenIndex = keyElement.PopulateFrom(tokens, tokenIndex);
                    elementInfo.SetValue(this, keyElement);
                }
                else
                {
                    // Field type is unsupported for a key, error message
                    throw new Exception(
                              $"Element {elementInfo.Name} of key type {GetType().Name} has type {elementType} that " +
                              $"is not one of the supported key element types. Available key element types are " +
                              $"string, bool, int, long, LocalDate, LocalTime, LocalMinute, LocalDateTime, Instant, Enum, or Key.");
                }
            }

            return(tokenIndex);
        }
Exemplo n.º 10
0
        /// <summary>
        /// Write atomic value. Value type
        /// will be inferred from object.GetType().
        /// </summary>
        public void WriteValue(object value)
        {
            // Check state transition matrix
            if (currentState_ == TreeWriterState.ValueStarted)
            {
                currentState_ = TreeWriterState.ValueWritten;
            }
            else if (currentState_ == TreeWriterState.ValueArrayItemStarted)
            {
                currentState_ = TreeWriterState.ValueArrayItemWritten;
            }
            else
            {
                throw new Exception(
                          $"A call to WriteEndValue(...) does not follow a matching WriteValue(...) at the same indent level.");
            }

            // Check that we are either inside dictionary or array
            Type elementType = null;

            if (currentArray_ != null)
            {
                elementType = currentArrayItemType_;
            }
            else if (currentDict_ != null)
            {
                elementType = currentElementInfo_.PropertyType;
            }
            else
            {
                throw new Exception($"Cannot WriteValue(...)for element {currentElementName_} " +
                                    $"is called outside dictionary or array.");
            }

            if (value.IsEmpty())
            {
                // Do not record null or empty value into dictionary, but add it to an array
                // Add to dictionary or array, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = null;
                }
                return;
            }

            // Write based on element type
            Type valueType = value.GetType();

            if (elementType == typeof(string) ||
                elementType == typeof(double) || elementType == typeof(double?) ||
                elementType == typeof(bool) || elementType == typeof(bool?) ||
                elementType == typeof(int) || elementType == typeof(int?) ||
                elementType == typeof(long) || elementType == typeof(long?))
            {
                // Check type match
                if (!elementType.IsAssignableFrom(valueType))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into element of type {elementType.Name}.");
                }

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = value;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, value);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(LocalDate) || elementType == typeof(LocalDate?))
            {
                // Check type match
                if (valueType != typeof(int))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into LocalDate; type should be int32.");
                }

                // Deserialize LocalDate as ISO int in yyyymmdd format
                LocalDate dateValue = LocalDateUtil.FromIsoInt((int)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = dateValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, dateValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(LocalTime) || elementType == typeof(LocalTime?))
            {
                // Check type match
                if (valueType != typeof(int))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into LocalTime; type should be int32.");
                }

                // Deserialize LocalTime as ISO int in hhmmssfff format
                LocalTime timeValue = LocalTimeUtil.FromIsoInt((int)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = timeValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, timeValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(LocalMinute) || elementType == typeof(LocalMinute?))
            {
                // Check type match
                if (valueType != typeof(int))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into LocalMinute; type should be int32.");
                }

                // Deserialize LocalTime as ISO int in hhmmssfff format
                LocalMinute minuteValue = LocalMinuteUtil.FromIsoInt((int)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = minuteValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, minuteValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(LocalDateTime) || elementType == typeof(LocalDateTime?))
            {
                // Check type match
                if (valueType != typeof(long))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into LocalDateTime; type should be int64.");
                }

                // Deserialize LocalDateTime as ISO long in yyyymmddhhmmssfff format
                LocalDateTime dateTimeValue = LocalDateTimeUtil.FromIsoLong((long)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = dateTimeValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, dateTimeValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType == typeof(Instant) || elementType == typeof(Instant?))
            {
                // Check type match
                if (valueType != typeof(long))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into Instant; type should be int64.");
                }

                // Deserialize Instant as ISO long in yyyymmddhhmmssfff format
                Instant instantValue = InstantUtil.FromIsoLong((long)value);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = instantValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, instantValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else if (elementType.IsEnum)
            {
                // Check type match
                if (valueType != typeof(string))
                {
                    throw new Exception(
                              $"Attempting to deserialize value of type {valueType.Name} " +
                              $"into enum {elementType.Name}; type should be string.");
                }

                string stringValue = (string)value;

                // Deserialize enum as string
                string enumString = (string)value;
                object enumValue  = Enum.Parse(elementType, enumString);

                // Add to array or dictionary, depending on what we are inside of
                if (currentArray_ != null)
                {
                    currentArray_[currentArray_.Count - 1] = enumValue;
                }
                else if (currentDict_ != null)
                {
                    currentElementInfo_.SetValue(currentDict_, enumValue);
                }
                else
                {
                    throw new Exception($"Value can only be added to a dictionary or array.");
                }
            }
            else
            {
                // We run out of value types at this point, now we can create
                // a reference type and check that it implements Key
                object keyObj = (Key)Activator.CreateInstance(elementType);
                if (keyObj is Key)
                {
                    Key key = (Key)keyObj;

                    // Check type match
                    if (valueType != typeof(string) && valueType != elementType)
                    {
                        throw new Exception(
                                  $"Attempting to deserialize value of type {valueType.Name} " +
                                  $"into key type {elementType.Name}; keys should be serialized into semicolon delimited string.");
                    }

                    // Populate by parsing semicolon delimited string
                    string stringValue = value.AsString();
                    key.PopulateFrom(stringValue);

                    // Add to array or dictionary, depending on what we are inside of
                    if (currentArray_ != null)
                    {
                        currentArray_[currentArray_.Count - 1] = key;
                    }
                    else if (currentDict_ != null)
                    {
                        currentElementInfo_.SetValue(currentDict_, key);
                    }
                    else
                    {
                        throw new Exception($"Value can only be added to a dictionary or array.");
                    }
                }
                else
                {
                    // Argument type is unsupported, error message
                    throw new Exception($"Element type {value.GetType()} is not supported for serialization.");
                }
            }
        }