// Copyright (C) Stichting Deltares 2021. All rights reserved. // // This file is part of the Dam Engine. // // The Dam Engine is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Reflection; namespace Deltares.DamEngine.Data.Standard { /// /// Static class ObjectExtensions /// public static class ObjectExtensions { public static readonly CultureInfo DefaultCulture = new CultureInfo("nl-NL"); private const double threshold = 0.0000001; /// /// Tests two double values for equality with a default precision of /// /// The first double value /// The other double value to compare with /// True when almost equal public static bool AlmostEquals(this double double1, double double2) { return AlmostEquals(double1, double2, threshold); } /// /// Tests two double values for equality with a given precision /// /// The first double value /// The other double value to compare with /// The precision value /// True when almost equal public static bool AlmostEquals(this double double1, double double2, double precision) { if (double.IsNaN(double1) && double.IsNaN(double2)) { return true; } if (double.IsNaN(double1) || double.IsNaN(double2)) { return false; } return (Math.Abs(double1 - double2) <= precision); } /// /// Converts an object to another type. Also works with nullable types. The method can't. /// /// /// Also works with nullable types and enums. The method can't do this. /// /// The type to converto to /// The object to convert /// The converted object /// When is null and name="type"/> is value type and not nullable. /// When an error occurs during conversion. public static T ToType(this object value) { if (value == null) { ThrowIfValueType(); return default(T); } return (T)value.ToType(typeof(T), DefaultCulture); } /// /// Converts an object to another type. /// /// /// Also works with nullable types and enums. The method can't do this. /// This method can also be used to downcast numeric values for example from string (double) value "12,0" to short 12 /// When trying to convert a string to double and the dutch culture is set, then this method tries to /// parse the string with custom logic. Because it occures that some string are not properly /// changed according to the current culture. For instance in dbf files where numeric values are stored as /// strings (for example "10,23") /// Use this method with care when high performance is required it is not yet tested with large data sets /// /// The value to convert /// The target type to convert to /// /// The converted type /// When is null and is value type and not nullable. /// When an error occurs during conversion. public static object ToType(this object value, Type type, CultureInfo culture) { if (value == null) { ThrowIfValueType(type); return null; } if (type.IsEnum) { return value.ToEnumType(type); } object result = null; if (IsNullable(type)) { try { var nullableConverter = new NullableConverter(type); type = nullableConverter.UnderlyingType; result = Convert.ChangeType(value, type, CultureInfo.InvariantCulture); } catch { result = null; } } else { try { if (type == typeof(double) && value is string) { if (Equals(culture, new CultureInfo("nl-NL"))) { // TODO: Needs to be tested !! // TODO: replace parsing with regular expression // TODO: to make this more flexible make parser injectable through lambda's var tmp = value as string; if (tmp.Contains(",") && !tmp.Contains(".")) // 1,5 -> 1.5 { value = tmp.Replace(",", "."); } else if (tmp.Contains(",") && tmp.Contains(".") && tmp.Length > 4 && !tmp.Contains(" ")) { // 100,000.00 -> 100000.00 value = tmp.Replace(",", ""); } //TODO: how to handle 100,000 english notation? Seems not to occur in most/mabye all situations } result = value.Equals("NaN") ? Double.NaN : Convert.ChangeType(value, type, CultureInfo.InvariantCulture); } else { // value needs to be downcasted switch (type.Name) { case "SByte": case "Byte": case "Int16": case "UInt16": case "Int32": case "UInt32": case "Int64": case "UInt64": var tmp = Convert.ChangeType(value, typeof(double), CultureInfo.InvariantCulture); result = Convert.ChangeType(tmp, type); break; default: result = Convert.ChangeType(value, type, CultureInfo.InvariantCulture); break; } } } catch (Exception e) { ThrowConversionException(value, type, e); } } return result; } /// /// Convert the value to the enum type /// /// The value to convert. /// The type to convert to. /// Ignores the case if set to true /// The enum public static object ToEnumType(this object value, Type type, bool ignoreCase = false) { if (value == null) { throw new ArgumentNullException("value"); } if (!ignoreCase && !Enum.IsDefined(type, value)) { ThrowConversionException(value, type, null); } string stringVal = value.ToString(); object result = null; try { result = Enum.Parse(type, stringVal, ignoreCase); } catch (Exception e) { ThrowConversionException(value, type, e); } return result; } /// /// Convert the value to the enum type /// /// The enum type to convert to /// The value to convert. /// A translation dictionary to handle items that do not match a given string representation /// The enum public static T ToEnumType(this object value, IDictionary lookup) { if (value == null) { throw new ArgumentNullException("value"); } string stringVal = value.ToString(); if (lookup != null && lookup.ContainsKey(stringVal)) { return lookup[stringVal]; } return (T)ToEnumType(value, typeof(T)); } /// /// Clone properties from an original object to a destination object. /// /// /// Copy matching properties from object to object /// http://goneale.wordpress.com/2009/02/16/cloning-object-properties-via-reflection/# /// /// /// /// /// /// public static void CloneProperties(this T1 origin, T2 destination, params string[] excludedProperties) { // Instantiate if necessary if (destination == null) { throw new ArgumentNullException("destination", "Destination object must first be instantiated."); } // Loop through each property in the destination foreach (var destinationProperty in destination.GetType().GetProperties()) { bool excluded = false; foreach (var excludedProperty in excludedProperties) { if (excludedProperty.ToLower().Equals(destinationProperty.Name.ToLower())) { excluded = true; } } if (excluded || IsIList(destinationProperty) || IsIDictionary(destinationProperty) || IsGenericIList(destinationProperty) || IsGenericIDictionary(destinationProperty)) { continue; } // find and set val if we can find a matching property name and matching type in the origin with the origin's value if (!origin.Equals(default(T1)) && destinationProperty.CanWrite) { PropertyInfo propertyInfo = destinationProperty; origin.GetType().GetProperties().Where( x => x.CanRead && (x.Name == propertyInfo.Name && x.PropertyType == propertyInfo.PropertyType)) .ToList() .ForEach(x => propertyInfo.SetValue(destination, x.GetValue(origin, null), null)); } } } private static bool IsGenericIDictionary(PropertyInfo destinationProperty) { return destinationProperty.PropertyType.IsGenericType && destinationProperty.PropertyType.GetGenericTypeDefinition() == typeof(IDictionary<,>); } private static bool IsIDictionary(PropertyInfo destinationProperty) { return typeof(IDictionary).IsAssignableFrom(destinationProperty.PropertyType); } private static bool IsGenericIList(PropertyInfo destinationProperty) { return destinationProperty.PropertyType.IsGenericType && destinationProperty.PropertyType.GetGenericTypeDefinition() == typeof(IList<>); } private static bool IsIList(PropertyInfo destinationProperty) { return typeof(IList).IsAssignableFrom(destinationProperty.PropertyType); } /// /// Throws an ArgumentNullException if target type to convert to is a ValueType. /// /// private static void ThrowIfValueType() { ThrowIfValueType(typeof(T)); } /// /// Throws an ArgumentNullException if target type to convert to is a ValueType. /// /// The type. private static void ThrowIfValueType(Type type) { if (type.IsValueType && !IsNullable(type)) { throw new ArgumentNullException( string.Format("The value can't be null because parameter type {0} is a value type", type)); } } /// /// Determines whether the specified type is nullable. /// /// The type. /// /// true if the specified type is nullable; otherwise, false. /// private static bool IsNullable(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } /// /// Throws conversion exception. /// /// The value. /// The type. /// The exception. private static void ThrowConversionException(object value, Type type, Exception e) { string v = ""; try { v = value.ToString().Replace("\0", " "); } catch { throw new ConversionException(type, value.ToString(), e); } throw new ConversionException(type, v, e); } /// /// Sets the object value. /// /// /// The property info. /// The destination object /// The value /// The translator public static void SetObjectValue(this PropertyInfo pInfo, T dest, object value, Func translator) { if (pInfo != null && pInfo.CanWrite) { if (translator != null) { pInfo.SetValue(dest, translator(value), null); } else { if (pInfo.PropertyType.IsEnum) { try { object v = Enum.Parse(pInfo.PropertyType, value.ToString(), true); pInfo.SetValue(dest, v, null); } catch (Exception e) { string msg = string.Format( "Couldn't parse value '{1}' to type {0}. Try to set a translation rule for this conversion.", pInfo.PropertyType, value); throw new ConversionException(msg, e); } } else { pInfo.SetValue(dest, ToType(value, pInfo.PropertyType), null); } } } } /// /// Converts an object to another type. /// /// The value. /// The type. /// /// is null and is value type and not nullable. /// When an error occurs during conversion. public static object ToType(this object value, Type type) { if (value == null) { ThrowIfValueType(type); return null; } return value.ToType(type, DefaultCulture); } } }