Skip to content
This repository has been archived by the owner on Jul 5, 2023. It is now read-only.

Commit

Permalink
Merge pull request #128 from NoxOrg/feature/volume-type
Browse files Browse the repository at this point in the history
Implemented Volume type #115
  • Loading branch information
rochar committed Jul 4, 2023
2 parents 32020d5 + fe70f4c commit 09bc632
Show file tree
Hide file tree
Showing 13 changed files with 552 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/Nox.Types/Types/Area/Area.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ protected override IEnumerable<KeyValuePair<string, object>> GetEqualityComponen

private QuantityValue GetAreaIn(AreaTypeUnit targetUnit)
{
var factor = new MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value;
var factor = new Common.MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value;
return Round(Value * factor);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Nox.Types/Types/Distance/Distance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public string ToString(IFormatProvider formatProvider)

private QuantityValue GetDistanceIn(DistanceTypeUnit targetUnit)
{
var factor = new MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value;
var factor = new Common.MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value;
return Round(Value * factor);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Nox.Types/Types/Length/Length.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public QuantityValue ToFeet()

private QuantityValue GetLengthIn(LengthTypeUnit targetUnit)
{
var factor = new MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value;
var factor = new Common.MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value;
return Round(Value * factor);
}

Expand Down
78 changes: 78 additions & 0 deletions src/Nox.Types/Types/Volume/Measurement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Globalization;

namespace Nox.Types;

public abstract class Measurement<TValueObject, TUnitType> : ValueObject<QuantityValue, TValueObject>
where TValueObject : Measurement<TValueObject, TUnitType>, new()
where TUnitType : MeasurementUnit
{
private const int QuantityValueDecimalPrecision = 6;

public TUnitType Unit { get; private set; } = default!;

protected Measurement() : base() { Value = 0; }

/// <summary>
/// Creates a new instance of <see cref="TValueObject"/> object with the specified <see cref="TUnitType"/>.
/// </summary>
/// <param name="value">The value to create the <see cref="TValueObject"/> with</param>
/// <param name="unit">The <see cref="TUnitType"/> to create the <see cref="TValueObject"/> with</param>
/// <returns></returns>
/// <exception cref="TypeValidationException"></exception>
public static TValueObject From(QuantityValue value, TUnitType unit)
{
var newObject = new TValueObject
{
Value = Round(value),
Unit = unit,
};

var validationResult = newObject.Validate();

if (!validationResult.IsValid)
{
throw new TypeValidationException(validationResult.Errors);
}

return newObject;
}

/// <summary>
/// Validates a <see cref="TValueObject"/> object.
/// </summary>
/// <returns>true if the <see cref="TValueObject"/> value is valid.</returns>
internal override ValidationResult Validate()
{
var result = Value.Validate();

if (Value < 0 && !double.IsNaN((double)Value) && !double.IsInfinity((double)Value))
{
result.Errors.Add(new ValidationFailure(nameof(Value), $"Could not create a Nox {typeof(TValueObject).Name} type as negative {typeof(TValueObject).Name.ToLower()} value {Value} is not allowed."));
}

return result;
}

public override string ToString()
=> $"{Value.ToString($"0.{new string('#', QuantityValueDecimalPrecision)}", CultureInfo.InvariantCulture)} {Unit}";

/// <summary>
/// Returns a string representation of the <see cref="TValueObject"/> object using the specified <see cref="IFormatProvider"/>.
/// </summary>
/// <param name="formatProvider">The format provider for the measurement value.</param>
/// <returns>A string representation of the <see cref="TValueObject"/> object with the value formatted using the specified <see cref="IFormatProvider"/>.</returns>
public string ToString(IFormatProvider formatProvider)
=> $"{Value.ToString(formatProvider)} {Unit}";

protected QuantityValue GetMeasurementIn(TUnitType targetUnit)
{
var factor = ResolveUnitConversionFactor(Unit, targetUnit);
return Round(Value * factor);
}

private static QuantityValue Round(QuantityValue value)
=> Math.Round((double)value, QuantityValueDecimalPrecision);

protected abstract MeasurementConversionFactor<TUnitType> ResolveUnitConversionFactor(TUnitType sourceUnit, TUnitType targetUnit);
}
36 changes: 36 additions & 0 deletions src/Nox.Types/Types/Volume/MeasurementConversionFactor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;

namespace Nox.Types;

public abstract class MeasurementConversionFactor<TUnitType> where TUnitType : MeasurementUnit
{
protected abstract Dictionary<(TUnitType, TUnitType), double> DefinedConversionFactors { get; }

public double Value { get; }

protected MeasurementConversionFactor(TUnitType sourceUnit, TUnitType targetUnit)
{
Value = ResolveConversionFactor(sourceUnit, targetUnit);
}

private double ResolveConversionFactor(TUnitType sourceUnit, TUnitType targetUnit)
{
var conversion = (sourceUnit, targetUnit);

if (DefinedConversionFactors.ContainsKey(conversion))
return DefinedConversionFactors[conversion];


if (sourceUnit == targetUnit)
return 1;

throw new NotImplementedException($"No conversion defined from {sourceUnit?.Name} to {targetUnit?.Name}.");
}

public static QuantityValue operator *(QuantityValue value, MeasurementConversionFactor<TUnitType> factor)
=> value * factor.Value;

public static QuantityValue operator *(MeasurementConversionFactor<TUnitType> factor, QuantityValue value)
=> value * factor;
}
53 changes: 53 additions & 0 deletions src/Nox.Types/Types/Volume/MeasurementUnit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;

namespace Nox.Types;

public abstract class MeasurementUnit : IComparable
{
public int Id { get; }
public string Name { get; }
public string Symbol { get; }

protected MeasurementUnit(int id, string name, string symbol)
{
Id = id;
Name = name;
Symbol = symbol;
}

public int CompareTo(object obj)
=> Id.CompareTo(((MeasurementUnit)obj).Id);

public override bool Equals(object obj)
{
if (obj is not MeasurementUnit otherValue)
{
return false;
}

var isEqualType = GetType().Equals(obj.GetType());
var isEqualId = Id.Equals(otherValue.Id);

return isEqualType && isEqualId;
}

public override int GetHashCode() => Id.GetHashCode();

public override string ToString() => Symbol;

public static bool operator ==(MeasurementUnit? a, MeasurementUnit? b)
{
if (a is null && b is null)
return true;

if (a is null || b is null)
return false;

return a.Equals(b);
}

public static bool operator !=(MeasurementUnit? a, MeasurementUnit? b)
{
return !(a == b);
}
}
49 changes: 46 additions & 3 deletions src/Nox.Types/Types/Volume/Volume.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,52 @@
using System.Collections.Generic;

namespace Nox.Types;

/// <summary>
/// Represents a Nox <see cref="Volume"/> type and value object.
/// </summary>
public sealed class Volume : Measurement<Volume, VolumeUnit>
{
/// <summary>
/// Creates a new instance of <see cref="Volume"/> object in cubic feet.
/// </summary>
/// <param name="value">The value to create the <see cref="Volume"/> with</param>
/// <returns></returns>
/// <exception cref="TypeValidationException"></exception>
public static Volume FromCubicFeet(QuantityValue value)
=> From(value, VolumeUnit.CubicFoot);

/// <summary>
/// Creates a new instance of <see cref="Volume"/> object in cubic meters.
/// </summary>
/// <param name="value">The origin value to create the <see cref="Volume"/> with</param>
/// <returns></returns>
/// <exception cref="TypeValidationException"></exception>
public static Volume FromCubicMeters(QuantityValue value)
=> From(value, VolumeUnit.CubicMeter);

/// <summary>
/// Represents a Nox <see cref="Volume"/> type and value object.
/// Creates a new instance of <see cref="Volume"/> object in cubic meters.
/// </summary>
/// <remarks>Placeholder, needs to be implemented</remarks>
public sealed class Volume : ValueObject<QuantityValue, Volume>
/// <param name="value">The origin value to create the <see cref="Volume"/> with</param>
/// <returns></returns>
/// <exception cref="TypeValidationException"></exception>
public new static Volume From(QuantityValue value)
=> From(value, VolumeUnit.CubicMeter);

private QuantityValue? _cubicFeet;

public QuantityValue ToCubicFeet() => _cubicFeet ??= GetMeasurementIn(VolumeUnit.CubicFoot);

private QuantityValue? _cubicMeters;

public QuantityValue ToCubicMeters() => _cubicMeters ??= GetMeasurementIn(VolumeUnit.CubicMeter);

protected override IEnumerable<KeyValuePair<string, object>> GetEqualityComponents()
{
yield return new KeyValuePair<string, object>(nameof(Value), ToCubicMeters());
}

protected override MeasurementConversionFactor<VolumeUnit> ResolveUnitConversionFactor(VolumeUnit sourceUnit, VolumeUnit targetUnit)
=> new VolumeConversionFactor(sourceUnit, targetUnit);
}
19 changes: 19 additions & 0 deletions src/Nox.Types/Types/Volume/VolumeConversionFactor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;

namespace Nox.Types;

public class VolumeConversionFactor : MeasurementConversionFactor<VolumeUnit>
{
private static readonly Dictionary<(VolumeUnit, VolumeUnit), double> _definedVolumeConversionFactors = new()
{
{ (VolumeUnit.CubicFoot, VolumeUnit.CubicMeter), 0.0283168466 },
{ (VolumeUnit.CubicMeter, VolumeUnit.CubicFoot), 35.3146667 },
};

protected override Dictionary<(VolumeUnit, VolumeUnit), double> DefinedConversionFactors => _definedVolumeConversionFactors;

public VolumeConversionFactor(VolumeUnit sourceUnit, VolumeUnit targetUnit)
: base(sourceUnit, targetUnit)
{
}
}
29 changes: 29 additions & 0 deletions src/Nox.Types/Types/Volume/VolumeUnit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Nox.Types;

public class VolumeUnit : MeasurementUnit
{
public static VolumeUnit CubicMeter { get; } = new VolumeUnit(1, "CubicMeter", "");
public static VolumeUnit CubicFoot { get; } = new VolumeUnit(2, "CubicFoot", "ft³");

private VolumeUnit(int id, string name, string symbol) : base(id, name, symbol)
{
}
}


public class LengthUnit : MeasurementUnit
{
public static LengthUnit Meter { get; } = new LengthUnit(1, "Meter", "m");
public static LengthUnit Foot { get; } = new LengthUnit(2, "Foot", "ft");

protected LengthUnit(int id, string name, string symbol) : base(id, name, symbol)
{
}
}

public class DistanceUnit : LengthUnit
{
protected DistanceUnit(int id, string name, string symbol) : base(id, name, symbol)
{
}
}
16 changes: 8 additions & 8 deletions tests/Nox.Types.Tests/Common/MeasurementConversionFactorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,63 +8,63 @@ public class MeasurementConversionFactorTests
[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromFootToMeter_ReturnsValue()
{
var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Foot, MeasurementTypeUnit.Meter);
var factor = new Nox.Common.MeasurementConversionFactor(MeasurementTypeUnit.Foot, MeasurementTypeUnit.Meter);

factor.Value.Should().Be(0.30480000033);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromMeterToFoot_ReturnsValue()
{
var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Meter, MeasurementTypeUnit.Foot);
var factor = new Nox.Common.MeasurementConversionFactor(MeasurementTypeUnit.Meter, MeasurementTypeUnit.Foot);

factor.Value.Should().Be(3.28083989142);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromKilometerToMile_ReturnsValue()
{
var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Kilometer, MeasurementTypeUnit.Mile);
var factor = new Nox.Common.MeasurementConversionFactor(MeasurementTypeUnit.Kilometer, MeasurementTypeUnit.Mile);

factor.Value.Should().Be(0.62137119102);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromMileToKilometer_ReturnsValue()
{
var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Mile, MeasurementTypeUnit.Kilometer);
var factor = new Nox.Common.MeasurementConversionFactor(MeasurementTypeUnit.Mile, MeasurementTypeUnit.Kilometer);

factor.Value.Should().Be(1.60934400315);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromSquareFootToSquareMeter_ReturnsValue()
{
var factor = new MeasurementConversionFactor(MeasurementTypeUnit.SquareFoot, MeasurementTypeUnit.SquareMeter);
var factor = new Nox.Common.MeasurementConversionFactor(MeasurementTypeUnit.SquareFoot, MeasurementTypeUnit.SquareMeter);

factor.Value.Should().Be(0.09290304);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_FromSquareMeterToSquareFoot_ReturnsValue()
{
var factor = new MeasurementConversionFactor(MeasurementTypeUnit.SquareMeter, MeasurementTypeUnit.SquareFoot);
var factor = new Nox.Common.MeasurementConversionFactor(MeasurementTypeUnit.SquareMeter, MeasurementTypeUnit.SquareFoot);

factor.Value.Should().Be(10.76391042);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_WithSameSourceAndTargetUnit_ReturnsValue()
{
var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Foot, MeasurementTypeUnit.Foot);
var factor = new Nox.Common.MeasurementConversionFactor(MeasurementTypeUnit.Foot, MeasurementTypeUnit.Foot);

factor.Value.Should().Be(1);
}

[Fact]
public void MeasurementUnitConverter_GetConversionFactor_WithUnsupportedConversion_ThrowsException()
{
var action = () => new MeasurementConversionFactor(MeasurementTypeUnit.SquareMeter, MeasurementTypeUnit.Meter);
var action = () => new Nox.Common.MeasurementConversionFactor(MeasurementTypeUnit.SquareMeter, MeasurementTypeUnit.Meter);

action.Should().Throw<NotImplementedException>()
.WithMessage("No conversion defined from SquareMeter to Meter.");
Expand Down
Loading

0 comments on commit 09bc632

Please sign in to comment.