// 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