// Copyright (C) Stichting Deltares 2017. All rights reserved.
//
// This file is part of Ringtoets.
//
// Ringtoets is free software: you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU 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.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Core.Common.Base;
using Core.Common.Base.Data;
using Core.Common.Base.Geometry;
using Core.Common.Base.IO;
using Core.Common.Utils;
using Core.Common.Utils.Builders;
using Ringtoets.Common.Data.DikeProfiles;
using Ringtoets.Common.IO.Exceptions;
using Ringtoets.Common.IO.Properties;
using CoreCommonUtilsResources = Core.Common.Utils.Properties.Resources;
using UtilsResources = Core.Common.Utils.Properties.Resources;
namespace Ringtoets.Common.IO.DikeProfiles
{
///
/// Reader responsible for reading the data for a dike profile from a .prfl file.
///
public class DikeProfileDataReader
{
[Flags]
private enum Keywords
{
None = 0,
VERSIE = 1,
ID = 2,
RICHTING = 4,
DAM = 8,
DAMHOOGTE = 16,
VOORLAND = 32,
DAMWAND = 64,
KRUINHOOGTE = 128,
DIJK = 256,
MEMO = 512
}
private static readonly Range orientationValidityRange = new Range(0, 360);
private static readonly Range roughnessValidityRange = new Range(0.5, 1.0);
private readonly string[] acceptedIds;
private string fileBeingRead;
private Keywords readKeywords;
///
/// Initializes a new instance of .
///
/// The accepted ids the file id must have to pass validation.
/// Thrown when is null.
public DikeProfileDataReader(string[] acceptedIds)
{
if (acceptedIds == null)
{
throw new ArgumentNullException(nameof(acceptedIds));
}
this.acceptedIds = acceptedIds;
}
///
/// Reads a *.prfl file containing dike profile data.
///
/// The file path.
/// The read .
/// Thrown when is invalid.
/// Thrown when:
///
/// - refers to a file that doesn't exist.
/// - A piece of text from the file cannot be converted into the expected variable type.
/// - A critical I/O exception occurred while attempting to read a line in the file.
/// - A converted value is invalid.
/// - The file is incomplete.
/// - A keyword is defined more then once.
/// - The geometry points for either the dike or foreshore do not have monotonically
/// increasing X-coordinates.
/// - An unexpected piece of text has been encountered in the file.
///
/// Thrown when the read id is not an accepted id.
public DikeProfileData ReadDikeProfileData(string filePath)
{
IOUtils.ValidateFilePath(filePath);
if (!File.Exists(filePath))
{
string message = new FileReaderErrorMessageBuilder(filePath)
.Build(CoreCommonUtilsResources.Error_File_does_not_exist);
throw new CriticalFileReadException(message);
}
var data = new DikeProfileData();
readKeywords = Keywords.None;
using (var reader = new StreamReader(filePath))
{
fileBeingRead = filePath;
string text;
var lineNumber = 0;
while ((text = ReadLineAndHandleIOExceptions(reader, ++lineNumber)) != null)
{
if (string.IsNullOrWhiteSpace(text))
{
continue;
}
if (TryReadVersion(text, lineNumber))
{
continue;
}
if (TryReadId(text, data, lineNumber))
{
continue;
}
if (TryReadOrientation(text, data, lineNumber))
{
continue;
}
if (TryReadDamType(text, data, lineNumber))
{
continue;
}
if (TryReadSheetPileType(text, data, lineNumber))
{
continue;
}
if (TryReadDamHeight(text, data, lineNumber))
{
continue;
}
if (TryReadDikeHeight(text, data, lineNumber))
{
continue;
}
if (TryReadDikeRoughnessPoints(text, data, reader, ref lineNumber))
{
continue;
}
if (TryReadForeshoreRoughnessPoints(text, data, reader, ref lineNumber))
{
continue;
}
if (TryReadMemo(text, data, reader))
{
continue;
}
HandleUnexpectedText(text, lineNumber);
}
}
ValidateNoMissingKeywords();
return data;
}
///
/// Attempts to match the given text to a VERSIE key-value pair. If a match is found,
/// the value is parsed and validated.
///
/// The text.
/// The line number.
/// True if the text matches a VERSIE key-value pair and has been
/// validated successfully; false otherwise.
/// The value after the VERSIE key does
/// not represent a valid version code or has already been defined or the version
/// is not supported by this reader.
private bool TryReadVersion(string text, int lineNumber)
{
Match versionMatch = new Regex(@"^VERSIE(\s+(?.+?)?)?\s*$").Match(text);
if (versionMatch.Success)
{
ValidateNoPriorParameterDefinition(Keywords.VERSIE, lineNumber);
string readVersionText = versionMatch.Groups["version"].Value;
Version fileVersion = ParseVersion(lineNumber, readVersionText);
ValidateVersion(fileVersion, lineNumber);
readKeywords |= Keywords.VERSIE;
return true;
}
return false;
}
///
/// Parses the version code from a piece of text.
///
/// The line number.
/// The text to parse.
/// The read version code.
/// Thrown when
/// does not represent a valid version code.
private Version ParseVersion(int lineNumber, string readVersionText)
{
try
{
return new Version(readVersionText);
}
catch (FormatException e)
{
string message = Resources.DikeProfileDataReader_ValidateVersion_Only_version_four_zero_supported;
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (OverflowException e)
{
string message = Resources.DikeProfileDataReader_ValidateVersion_Only_version_four_zero_supported;
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (ArgumentException e)
{
string message = Resources.DikeProfileDataReader_ValidateVersion_Only_version_four_zero_supported;
throw CreateCriticalFileReadException(lineNumber, message, e);
}
}
///
/// Validates the version.
///
/// The file version.
/// The line number.
/// Thrown when
/// is not supported by this reader.
private void ValidateVersion(Version fileVersion, int lineNumber)
{
if (!fileVersion.Equals(new Version(4, 0)))
{
string message = Resources.DikeProfileDataReader_ValidateVersion_Only_version_four_zero_supported;
throw CreateCriticalFileReadException(lineNumber, message);
}
}
///
/// Attempts to match the given text to an ID key-value pair. If a match is found,
/// the value is parsed and validated.
///
/// The text.
/// The data to be updated.
/// The line number.
/// True if the text matches an ID key-value pair and has been
/// validated successfully; false otherwise.
/// The value after the ID key does
/// not represent an id or has already been defined.
private bool TryReadId(string text, DikeProfileData data, int lineNumber)
{
Match idMatch = new Regex(@"^ID(\s+(?.+?)?)?\s*$").Match(text);
if (idMatch.Success)
{
ValidateNoPriorParameterDefinition(Keywords.ID, lineNumber);
string readIdText = idMatch.Groups["id"].Value;
ValidateId(readIdText, lineNumber);
data.Id = readIdText;
readKeywords |= Keywords.ID;
return true;
}
return false;
}
///
/// Validates the identifier.
///
/// The id.
/// The line number.
/// Thrown when is not
/// present or has a whitespace.
/// Thrown when is not an accepted id.
private void ValidateId(string id, int lineNumber)
{
if (string.IsNullOrWhiteSpace(id))
{
string message = string.Format(Resources.DikeProfileDataReader_ValidateId_Id_0_not_valid,
id);
throw CreateCriticalFileReadException(lineNumber, message);
}
if (id.Any(char.IsWhiteSpace))
{
string message = string.Format(Resources.DikeProfileDataReader_ValidateId_Id_0_has_unsupported_white_space,
id);
throw CreateCriticalFileReadException(lineNumber, message);
}
if (!acceptedIds.Contains(id))
{
string message = string.Format(Resources.DikeProfileDataReader_ValidateId_Id_0_not_valid,
id);
throw new CriticalFileValidationException(message);
}
}
///
/// Attempts to match the given text to a RICHTING key-value pair. If a match is
/// found, the value is parsed and validated. If valid, the value is stored.
///
/// The text.
/// The data to be updated.
/// The line number.
/// True if the text matches a RICHTING key-value pair and has been
/// validated successfully; false otherwise.
/// The value after the RICHTING key does
/// not represent a valid number or has already been defined or the orientation value
/// is not in the range [0, 360].
private bool TryReadOrientation(string text, DikeProfileData data, int lineNumber)
{
Match orientationMatch = new Regex(@"^RICHTING(\s+(?.+?)?)?\s*$").Match(text);
if (orientationMatch.Success)
{
ValidateNoPriorParameterDefinition(Keywords.RICHTING, lineNumber);
string readOrientationText = orientationMatch.Groups["orientation"].Value;
double orientation = ParseOrientation(lineNumber, readOrientationText);
ValidateOrientation(orientation, lineNumber);
data.Orientation = orientation;
readKeywords |= Keywords.RICHTING;
return true;
}
return false;
}
///
/// Parses the orientation from a piece of text.
///
/// The line number.
/// The text.
/// The value corresponding to the RICHTING key.
/// Thrown when
/// does not represent a valid number.
private double ParseOrientation(int lineNumber, string readOrientationText)
{
try
{
return double.Parse(readOrientationText, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseOrientation_Orientation_0_not_double,
readOrientationText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (OverflowException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseOrientation_Orientation_0_overflows,
readOrientationText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
}
///
/// Validates the orientation.
///
/// The orientation.
/// The line number.
/// Thrown when
/// is outside the [0, 360] range.
private void ValidateOrientation(double orientation, int lineNumber)
{
if (!orientationValidityRange.InRange(orientation))
{
string rangeText = orientationValidityRange.ToString(FormattableConstants.ShowAtLeastOneDecimal, CultureInfo.CurrentCulture);
string message = string.Format(Resources.DikeProfileDataReader_ValidateOrientation_Orientation_0_must_be_in_Range_1_,
orientation, rangeText);
throw CreateCriticalFileReadException(lineNumber, message);
}
}
///
/// Attempts to match the given text to a DAM key-value pair. If a match is found,
/// the value is parsed and validated. If valid, the value is stored.
///
/// The text.
/// The data to be updated.
/// The line number.
/// True if the text matches a DAM key-value pair and has been
/// validated successfully; false otherwise.
/// The value after the DAM key does
/// not represent a valid value or has already been defined.
private bool TryReadDamType(string text, DikeProfileData data, int lineNumber)
{
Match damTypeMatch = new Regex(@"^DAM(\s+(?.+?)?)?\s*$").Match(text);
if (damTypeMatch.Success)
{
ValidateNoPriorParameterDefinition(Keywords.DAM, lineNumber);
string readDamTypeText = damTypeMatch.Groups["damtype"].Value;
DamType damType = ParseDamType(lineNumber, readDamTypeText);
data.DamType = damType;
readKeywords |= Keywords.DAM;
return true;
}
return false;
}
///
/// Parses the dam-type from a piece of text.
///
/// The line number.
/// The text.
/// The read dam-type.
/// Thrown when
/// does not represent a .
private DamType ParseDamType(int lineNumber, string readDamTypeText)
{
int damTypeValue;
try
{
damTypeValue = int.Parse(readDamTypeText, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseDamType_DamType_0_must_be_in_range,
readDamTypeText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (OverflowException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseDamType_DamType_0_must_be_in_range,
readDamTypeText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
if (!CanSafelyCastToEnum(damTypeValue))
{
string message = string.Format(Resources.DikeProfileDataReader_ParseDamType_DamType_0_must_be_in_range,
damTypeValue);
throw CreateCriticalFileReadException(lineNumber, message);
}
return (DamType) damTypeValue;
}
///
/// Attempts to match the given text to a DAMWAND key-value pair. If a match is
/// found, the value is parsed and validated. If valid, the value is stored.
///
/// The text.
/// The data to be updated.
/// The line number.
/// True if the text matches a DAMWAND key-value pair and has been
/// validated successfully; false otherwise.
/// The value after the DAMWAND key
/// does not represent a valid value or has already been defined.
private bool TryReadSheetPileType(string text, DikeProfileData data, int lineNumber)
{
Match profileTypeMatch = new Regex(@"^DAMWAND(\s+(?.+?)?)?\s*$").Match(text);
if (profileTypeMatch.Success)
{
ValidateNoPriorParameterDefinition(Keywords.DAMWAND, lineNumber);
string readSheetPileTypeText = profileTypeMatch.Groups["sheetpiletype"].Value;
SheetPileType sheetPileType = ParseSheetPileType(lineNumber, readSheetPileTypeText);
data.SheetPileType = sheetPileType;
readKeywords |= Keywords.DAMWAND;
return true;
}
return false;
}
///
/// Parses the sheet piling type from a piece of text.
///
/// The line number.
/// The text.
/// The read sheet piling type.
/// Thrown when
/// does not represent a .
private SheetPileType ParseSheetPileType(int lineNumber, string readSheetPileTypeText)
{
int sheetPileTypeValue;
try
{
sheetPileTypeValue = int.Parse(readSheetPileTypeText, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseSheetPileType_SheetPileType_0_must_be_in_range,
readSheetPileTypeText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (OverflowException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseSheetPileType_SheetPileType_0_must_be_in_range,
readSheetPileTypeText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
if (!CanSafelyCastToEnum(sheetPileTypeValue))
{
string message = string.Format(Resources.DikeProfileDataReader_ParseSheetPileType_SheetPileType_0_must_be_in_range,
sheetPileTypeValue);
throw CreateCriticalFileReadException(lineNumber, message);
}
return (SheetPileType) sheetPileTypeValue;
}
///
/// Attempts to match the given text to a DAMHOOGTE key-value pair. If a match is
/// found, the value is parsed.
///
/// The text.
/// The data to be updated.
/// The line number.
/// True if the text matches a DAMHOOGTE key-value pair and has been
/// validated successfully; false otherwise.
/// The value after the DAMHOOGTE key
/// does not represent a valid number or has already been defined.
private bool TryReadDamHeight(string text, DikeProfileData data, int lineNumber)
{
Match damHeightMatch = new Regex(@"^DAMHOOGTE(\s+(?.+?)?)?\s*$").Match(text);
if (damHeightMatch.Success)
{
ValidateNoPriorParameterDefinition(Keywords.DAMHOOGTE, lineNumber);
string readDamHeightText = damHeightMatch.Groups["damheight"].Value;
double damHeight = ParseDamHeight(readDamHeightText, lineNumber);
data.DamHeight = damHeight;
readKeywords |= Keywords.DAMHOOGTE;
return true;
}
return false;
}
///
/// Parses the height of the dam from a piece of text.
///
/// The line number.
/// The text.
/// The height of the dam.
/// Thrown when
/// does not represent a number.
private double ParseDamHeight(string readDamHeightText, int lineNumber)
{
try
{
return double.Parse(readDamHeightText, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseDamHeight_DamHeight_0_not_number,
readDamHeightText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (OverflowException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseDamHeight_DamHeight_0_overflows,
readDamHeightText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
}
///
/// Attempts to match the given text to a KRUINHOOGTE key-value pair. If a match is
/// found, the value is parsed.
///
/// The text.
/// The data to be updated.
/// The line number.
/// True if the text matches a KRUINHOOGTE key-value pair and has been
/// validated successfully; false otherwise.
/// The value after the KRUINHOOGTE key
/// does not represent a valid number or has already been defined.
private bool TryReadDikeHeight(string text, DikeProfileData data, int lineNumber)
{
Match crestLevelMatch = new Regex(@"^KRUINHOOGTE(\s+(?.+?)?)?\s*$").Match(text);
if (crestLevelMatch.Success)
{
ValidateNoPriorParameterDefinition(Keywords.KRUINHOOGTE, lineNumber);
string readDikeHeightText = crestLevelMatch.Groups["dikeheight"].Value;
double crestLevel = ParseDikeHeight(lineNumber, readDikeHeightText);
data.DikeHeight = crestLevel;
readKeywords |= Keywords.KRUINHOOGTE;
return true;
}
return false;
}
///
/// Parses the height of the dike from a piece of text.
///
/// The line number.
/// The text.
/// The height of the dike.
/// Thrown when
/// does not represent a number.
private double ParseDikeHeight(int lineNumber, string readDikeHeightText)
{
try
{
return double.Parse(readDikeHeightText, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseDikeHeight_DikeHeight_0_not_number,
readDikeHeightText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (OverflowException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseDikeHeight_DikeHeight_0_overflows,
readDikeHeightText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
}
///
/// Attempts to match the given text to a DIJK key-value pair. If a match is
/// found, the data block is being read. If valid, the value is stored.
///
/// The text of the DIJK key-value pair.
/// The data to be updated.
/// The reader of the file.
/// The line number.
/// True if the text matches a DIJK key-value pair and has been
/// validated successfully; false otherwise.
/// Thrown when
///
/// - The value after the DIJK key does not represent a valid number.
/// - Any of read the parameters in the following data block is invalid.
/// - The keyword has already been defined.
/// - The X-coordinates of the dike are not monotonically increasing.
///
private bool TryReadDikeRoughnessPoints(string text, DikeProfileData data, TextReader reader, ref int lineNumber)
{
Match dikeGeometryMatch = new Regex(@"^DIJK(\s+(?.+?)?)?\s*$").Match(text);
if (dikeGeometryMatch.Success)
{
ValidateNoPriorParameterDefinition(Keywords.DIJK, lineNumber);
string readDikeGeometryCountText = dikeGeometryMatch.Groups["dikegeometry"].Value;
int numberOfPoints = ParseNumberOfDikePoints(lineNumber, readDikeGeometryCountText);
ValidateDikePointCount(numberOfPoints, lineNumber);
if (numberOfPoints == 0)
{
readKeywords |= Keywords.DIJK;
return true;
}
data.DikeGeometry = new RoughnessPoint[numberOfPoints];
for (var i = 0; i < numberOfPoints; i++)
{
lineNumber++;
text = ReadLineAndHandleIOExceptions(reader, lineNumber);
if (string.IsNullOrWhiteSpace(text))
{
string message = string.Format(Resources.DikeProfileDataReader_TryReadDikeRoughnessPoints_DikeCount_0_does_not_correspond_ExpectedCount_1_,
i, numberOfPoints);
throw CreateCriticalFileReadException(lineNumber, message);
}
RoughnessPoint roughnessPoint = ReadRoughnessPoint(text, lineNumber);
data.DikeGeometry[i] = roughnessPoint;
if (i > 0)
{
ValidateDikePointsAreMonotonicallyIncreasing(roughnessPoint.Point,
data.DikeGeometry[i - 1].Point,
lineNumber);
}
}
readKeywords |= Keywords.DIJK;
return true;
}
return false;
}
///
/// Parses the number of dike roughness points from a piece of text representing
/// a DIJK key-value pair.
///
/// The line number.
/// The DIJK key-value pair text.
/// The number of dike roughness points.
/// Thrown when
/// does not represent a number.
private int ParseNumberOfDikePoints(int lineNumber, string readDikeGeometryCountText)
{
try
{
return int.Parse(readDikeGeometryCountText, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseNumberOfDikeElements_DijkCount_0_not_integer,
readDikeGeometryCountText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (OverflowException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseNumberOfDikeElements_DikeCount_0_overflows,
readDikeGeometryCountText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
}
///
/// Validates the number of dike points to be read from file.
///
/// The number of elements.
/// The line number.
/// Thrown when
/// is negative.
private void ValidateDikePointCount(int numberOfElements, int lineNumber)
{
if (numberOfElements < 0)
{
string message = string.Format(Resources.DikeProfileDataReader_ReadDikeProfileData_DikeCount_cannot_be_negative,
numberOfElements);
throw CreateCriticalFileReadException(lineNumber, message);
}
}
///
/// Matches the given text to a value triplet representing a roughness point. If
/// a match is found, the values are parsed and validated. If valid, the resulting
/// based on the values will be returned.
///
/// The text.
/// The line number.
/// A instance built using data in .
/// Any parameter value in the roughness
/// point triplet does not represent a valid number or the roughness parameter is
/// not valid.
private RoughnessPoint ReadRoughnessPoint(string text, int lineNumber)
{
Match roughnessSectionDataMatch = new Regex(@"^(\s*)?(?\S+)\s+(?\S+)\s+(?\S+)\s*$").Match(text);
if (!roughnessSectionDataMatch.Success)
{
string message = string.Format(Resources.DikeProfileDataReader_ReadRoughnessPoint_Line_0_not_x_y_roughness_definition,
text);
throw CreateCriticalFileReadException(lineNumber, message);
}
string readLocalXText = roughnessSectionDataMatch.Groups["localx"].Value;
double localX = ParseRoughnessPointParameter(readLocalXText,
Resources.DikeProfileDataReader_ReadRoughnessPoint_X_DisplayName,
lineNumber);
string readLocalZText = roughnessSectionDataMatch.Groups["localz"].Value;
double localZ = ParseRoughnessPointParameter(readLocalZText,
Resources.DikeProfileDataReader_ReadRoughnessPoint_Z_DisplayName,
lineNumber);
string readRoughnessText = roughnessSectionDataMatch.Groups["roughness"].Value;
double roughness = ParseRoughnessPointParameter(readRoughnessText,
Resources.DikeProfileDataReader_ReadRoughnessPoint_Roughness_DisplayName,
lineNumber);
ValidateRoughness(roughness, lineNumber);
return new RoughnessPoint(new Point2D(localX, localZ), roughness);
}
///
/// Parses some double parameter from a piece of text.
///
/// The name of the parameter as shown to the user.
/// The line number.
/// The value text to be parsed.
/// The parameter value.
/// Thrown when
/// does not represent a number.
private double ParseRoughnessPointParameter(string readParameterText, string parameterName, int lineNumber)
{
try
{
return double.Parse(readParameterText, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseRoughnessPointParameter_ParameterName_0_Value_1_not_number,
parameterName, readParameterText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (OverflowException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseRoughnessPointParameter_ParameterName_0_Value_1_overflows,
parameterName, readParameterText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
}
///
/// Validates the roughness.
///
/// The roughness.
/// The line number.
/// Thrown when
/// is outside the range [0.5, 1].
private void ValidateRoughness(double roughness, int lineNumber)
{
if (!roughnessValidityRange.InRange(roughness))
{
string rangeText = roughnessValidityRange.ToString(FormattableConstants.ShowAtLeastOneDecimal, CultureInfo.CurrentCulture);
string message = string.Format(Resources.DikeProfileDataReader_ReadRoughnessPoint_Roughness_0_must_be_Range_1_,
roughness, rangeText);
throw CreateCriticalFileReadException(lineNumber, message);
}
}
///
/// Validates that two dike points are monotonically increasing.
///
/// The current point.
/// The previous point.
/// The line number.
/// Thrown when
/// has an X-coordinate before or equal to that of .
private void ValidateDikePointsAreMonotonicallyIncreasing(Point2D currentPoint, Point2D previousPoint, int lineNumber)
{
if (currentPoint.X <= previousPoint.X)
{
throw CreateCriticalFileReadException(lineNumber, Resources.DikeProfileDataReader_ValidateDikePointsAreMonotonicallyIncreasing_Error_message);
}
}
///
/// Attempts to match the given text to a VOORLAND key-value pair. If a match is
/// found, the data block is being read. If valid, the value is stored.
///
/// The text of the VOORLAND key-value pair.
/// The data to be updated.
/// The reader of the file.
/// The line number.
/// Thrown when
///
/// - The value after the VOORLAND key does not represent a valid number.
/// - Any of read the parameters in the following data block is invalid.
/// - The keyword has already been defined.
/// - The X-coordinates of the foreshore are not monotonically increasing.
///
private bool TryReadForeshoreRoughnessPoints(string text, DikeProfileData data, TextReader reader, ref int lineNumber)
{
Match foreshoreGeometryMatch = new Regex(@"^VOORLAND(\s+(?.+?)?)?\s*$").Match(text);
if (foreshoreGeometryMatch.Success)
{
ValidateNoPriorParameterDefinition(Keywords.VOORLAND, lineNumber);
string readForeshoreCountText = foreshoreGeometryMatch.Groups["foreshoregeometry"].Value;
int numberOfElements = ParseNumberOfForeshoreElements(readForeshoreCountText, lineNumber);
ValidateForeshorePointCount(numberOfElements, lineNumber);
if (numberOfElements == 0)
{
readKeywords |= Keywords.VOORLAND;
return true;
}
data.ForeshoreGeometry = new RoughnessPoint[numberOfElements];
for (var i = 0; i < numberOfElements; i++)
{
lineNumber++;
text = ReadLineAndHandleIOExceptions(reader, lineNumber);
if (text == null)
{
string message = string.Format(Resources.DikeProfileDataReader_TryReadForeshoreRoughnessPoints_ForeshoreCount_0_does_not_correspond_ExpectedCount_1_,
i, numberOfElements);
throw CreateCriticalFileReadException(lineNumber, message);
}
RoughnessPoint roughnessPoint = ReadRoughnessPoint(text, lineNumber);
data.ForeshoreGeometry[i] = roughnessPoint;
if (i > 0)
{
ValidateForeshorePointsAreMonotonicallyIncreasing(roughnessPoint.Point,
data.ForeshoreGeometry[i - 1].Point,
lineNumber);
}
}
readKeywords |= Keywords.VOORLAND;
return true;
}
return false;
}
///
/// Parses the number of foreshore roughness points from a piece of text.
///
/// The line number.
/// The text.
/// The number of foreshore roughness points.
/// Thrown when
/// does not represent a number.
private int ParseNumberOfForeshoreElements(string readForeshoreCountText, int lineNumber)
{
try
{
return int.Parse(readForeshoreCountText, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseNumberOfForeshoreElements_ForeshoreCount_0_not_integer,
readForeshoreCountText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
catch (OverflowException e)
{
string message = string.Format(Resources.DikeProfileDataReader_ParseNumberOfForeshoreElements_ForeshoreCount_0_overflows,
readForeshoreCountText);
throw CreateCriticalFileReadException(lineNumber, message, e);
}
}
///
/// Validates the number of foreshore points.
///
/// The number of elements.
/// The line number.
/// Thrown when
/// is negative.
private void ValidateForeshorePointCount(int numberOfElements, int lineNumber)
{
if (numberOfElements < 0)
{
string message = string.Format(Resources.DikeProfileDataReader_ReadDikeProfileData_ForeshoreCount_0_cannot_be_negative,
numberOfElements);
throw CreateCriticalFileReadException(lineNumber, message);
}
}
///
/// Validates that two foreshore points are monotonically increasing.
///
/// The current point.
/// The previous point.
/// The line number.
/// Thrown when
/// has an X-coordinate before or equal to that of .
private void ValidateForeshorePointsAreMonotonicallyIncreasing(Point2D currentPoint, Point2D previousPoint, int lineNumber)
{
if (currentPoint.X <= previousPoint.X)
{
throw CreateCriticalFileReadException(lineNumber, Resources.DikeProfileDataReader_ValidateForeshorePointsAreMonotonicallyIncreasing_Error_message);
}
}
private bool TryReadMemo(string text, DikeProfileData data, StreamReader reader)
{
Match memoMatch = new Regex(@"^MEMO\s*$").Match(text);
if (memoMatch.Success)
{
data.Memo = reader.ReadToEnd();
readKeywords |= Keywords.MEMO;
return true;
}
return false;
}
///
/// Validates that there is no prior parameter definition of a given parameter.
///
/// The parameter.
/// The line number.
/// Thrown when
/// already has been defined in the file at a line prior to .
private void ValidateNoPriorParameterDefinition(Keywords parameter, int lineNumber)
{
if (readKeywords.HasFlag(parameter))
{
string message = string.Format(Resources.DikeProfileDataReader_ValidateNoPriorParameterDefinition_Parameter_0_already_defined,
parameter);
throw CreateCriticalFileReadException(lineNumber, message);
}
}
private static bool CanSafelyCastToEnum(int parameterValue) where TEnum : struct, IConvertible, IComparable, IFormattable
{
return Enum.GetValues(typeof(TEnum))
.OfType()
.Select(dt => Convert.ToInt32(dt))
.Any(i => i.Equals(parameterValue));
}
///
/// Reads the next line and handles I/O exceptions.
///
/// The opened text file reader.
/// Row number for error messaging.
/// The read line, or null when at the end of the file.
/// An critical I/O exception occurred.
private string ReadLineAndHandleIOExceptions(TextReader reader, int currentLine)
{
try
{
return reader.ReadLine();
}
catch (OutOfMemoryException e)
{
throw CreateCriticalFileReadException(currentLine, UtilsResources.Error_Line_too_big_for_RAM, e);
}
catch (IOException e)
{
string message = new FileReaderErrorMessageBuilder(fileBeingRead).Build(string.Format(UtilsResources.Error_General_IO_ErrorMessage_0_, e.Message));
throw new CriticalFileReadException(message, e);
}
}
///
/// Returns a configured instance of .
///
/// The line number being read.
/// The critical error message.
/// Optional: exception that caused this exception to be thrown.
/// New with message and inner exception set.
private CriticalFileReadException CreateCriticalFileReadException(int currentLine, string criticalErrorMessage, Exception innerException = null)
{
string locationDescription = string.Format(UtilsResources.TextFile_On_LineNumber_0_, currentLine);
string message = new FileReaderErrorMessageBuilder(fileBeingRead).WithLocation(locationDescription)
.Build(criticalErrorMessage);
return new CriticalFileReadException(message, innerException);
}
///
/// Handles the error-case that the file has unexpected text.
///
/// The unexpected text.
/// The line number.
/// Thrown when calling this method, due to
/// having read an unexpected piece of text from the file.
private void HandleUnexpectedText(string text, int lineNumber)
{
string message = string.Format(Resources.DikeProfileDataReader_HandleUnexpectedText_Line_0_is_invalid,
text);
throw CreateCriticalFileReadException(lineNumber, message);
}
private void ValidateNoMissingKeywords()
{
string[] missingKeywords = GetMissingKeywords();
if (missingKeywords.Any())
{
string criticalErrorMessage = string.Format(Resources.DikeProfileDataReader_ValidateNoMissingKeywords_List_mising_keywords_0_,
string.Join(", ", missingKeywords));
string message = new FileReaderErrorMessageBuilder(fileBeingRead)
.Build(criticalErrorMessage);
throw new CriticalFileReadException(message);
}
}
private string[] GetMissingKeywords()
{
var requiredKeywords = new[]
{
Keywords.VERSIE,
Keywords.ID,
Keywords.RICHTING,
Keywords.DAM,
Keywords.DAMHOOGTE,
Keywords.VOORLAND,
Keywords.DAMWAND,
Keywords.KRUINHOOGTE,
Keywords.DIJK,
Keywords.MEMO
};
return requiredKeywords.Where(z => !readKeywords.HasFlag(z))
.Select(z => z.ToString())
.ToArray();
}
}
}