From cc6c10b0f9a404274a8eef6f5f6b892d526a5202 Mon Sep 17 00:00:00 2001 From: markpollack Date: Fri, 13 Nov 2009 04:49:45 +0000 Subject: [PATCH] SPRNET-989 - Avoid throwing exception in Spring's custom timespan converter when delegating to default .NET BCL implementation. Thanks Roberto Paterlini! --- .../Core/TypeConversion/TimeSpanConverter.cs | 246 ++++++++++++++++-- .../TypeConversion/TimeSpanConverterTests.cs | 8 +- 2 files changed, 233 insertions(+), 21 deletions(-) diff --git a/src/Spring/Spring.Core/Core/TypeConversion/TimeSpanConverter.cs b/src/Spring/Spring.Core/Core/TypeConversion/TimeSpanConverter.cs index b2c1a122..ea13f38e 100644 --- a/src/Spring/Spring.Core/Core/TypeConversion/TimeSpanConverter.cs +++ b/src/Spring/Spring.Core/Core/TypeConversion/TimeSpanConverter.cs @@ -23,24 +23,239 @@ using System; using System.ComponentModel; using System.Globalization; +using System.Text.RegularExpressions; #endregion namespace Spring.Core.TypeConversion { + #region Specifier parsers + + #if NET_1_1 + + /// + /// Nullable TimeSpan + /// + /// + /// Can be replaced with a TimeSpan? in .NET 2 + /// + class TimeSpanNullable { + + readonly bool _hasValue; + readonly TimeSpan _value; + + /// + /// ctor without Value; + /// + public TimeSpanNullable() { + _hasValue = false; + } + + /// + /// ctor with Value + /// + /// + public TimeSpanNullable(TimeSpan timeSpan) { + _hasValue = true; + _value = timeSpan; + } + + /// + /// HasValue + /// + public bool HasValue { + get { return _hasValue; } + } + + /// + /// Value if HasValue==true + /// + public TimeSpan Value { + get { return _value; } + } + } + + #else + + using TimeSpanNullable = Nullable; + + #endif + + + /// + /// Base parser for custom specifiers. + /// + abstract class SpecifierParser + { + const RegexOptions ParsingOptions = RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.IgnoreCase; + + /// + /// Specifier + /// + public abstract string Specifier { get; } + + /// + /// Convert int value to a Timespan based on the specifier + /// + /// + /// + public abstract TimeSpan Parse(int value); + + /// + /// Check if the string contains the specifier and + /// + /// + /// + public TimeSpanNullable Match(string value) + { + string regex = @"^(\d+)" + Specifier + "$"; + Match match = Regex.Match(value, regex, ParsingOptions); + + if (!match.Success) return new TimeSpanNullable(); + + return new TimeSpanNullable(Parse(int.Parse(match.Groups[1].Value))); + } + + } + + /// + /// Recognize 10d as ten days + /// + class DaySpecifier: SpecifierParser + { + /// + /// Day specifier: d + /// + public override string Specifier + { + get { return "d"; } + } + + /// + /// Parse value as days + /// + /// Timespan in days + /// + public override TimeSpan Parse(int value) + { + return TimeSpan.FromDays(value); + } + } + + /// + /// Recognize 10h as ten hours + /// + class HourSpecifier : SpecifierParser + { + /// + /// Hour specifier: h + /// + public override string Specifier + { + get { return "h"; } + } + + /// + /// Parse value as hours + /// + /// Timespan in hours + /// + public override TimeSpan Parse(int value) + { + return TimeSpan.FromHours(value); + } + } + + /// + /// Recognize 10m as ten minutes + /// + class MinuteSpecifier : SpecifierParser + { + /// + /// Minute specifier: m + /// + public override string Specifier + { + get { return "m"; } + } + + /// + /// Parse value as minutes + /// + /// Timespan in minutes + /// + public override TimeSpan Parse(int value) + { + return TimeSpan.FromMinutes(value); + } + } + + /// + /// Recognize 10s as ten seconds + /// + class SecondSpecifier : SpecifierParser + { + /// + /// Second specifier: s + /// + public override string Specifier + { + get { return "s"; } + } + + /// + /// Parse value as seconds + /// + /// Timespan in seconds + /// + public override TimeSpan Parse(int value) + { + return TimeSpan.FromSeconds(value); + } + } + + /// + /// Recognize 10ms as ten milliseconds + /// + class MillisecondSpecifier : SpecifierParser + { + /// + /// Millisecond specifier: ms + /// + public override string Specifier + { + get { return "ms"; } + } + + /// + /// Parse value as milliseconds + /// + /// Timespan in milliseconds + /// + public override TimeSpan Parse(int value) + { + return TimeSpan.FromMilliseconds(value); + } + } + + #endregion + /// /// Converter for instances. /// /// Bruno Baia + /// Roberto Paterlini public class TimeSpanConverter : System.ComponentModel.TimeSpanConverter { #region Constants - private const string DaySpecifier = "d"; - private const string HourSpecifier = "h"; - private const string MinuteSpecifier = "m"; - private const string SecondSpecifier = "s"; - private const string MillisecondSpecifier = "ms"; + static readonly SpecifierParser[] Specifiers = { + new DaySpecifier(), + new HourSpecifier(), + new MinuteSpecifier(), + new SecondSpecifier(), + new MillisecondSpecifier() + }; #endregion @@ -77,26 +292,17 @@ namespace Spring.Core.TypeConversion ITypeDescriptorContext context, CultureInfo culture, object value) { - if (value is string) + string stringValue = value as string; + if (stringValue!=null) { try { - string timeSpan = ((string)value).ToLower(); - int specifierLengh = (timeSpan.EndsWith(MillisecondSpecifier)) ? 2 : 1; - int time = int.Parse(timeSpan.Substring(0, timeSpan.Length - specifierLengh)); + stringValue = stringValue.Trim(); - switch (timeSpan.Substring(timeSpan.Length - specifierLengh, specifierLengh)) + foreach (SpecifierParser specifierParser in Specifiers) { - case MillisecondSpecifier: - return TimeSpan.FromMilliseconds((double)time); - case SecondSpecifier: - return TimeSpan.FromSeconds((double)time); - case MinuteSpecifier: - return TimeSpan.FromMinutes((double)time); - case HourSpecifier: - return TimeSpan.FromHours((double)time); - case DaySpecifier: - return TimeSpan.FromDays((double)time); + TimeSpanNullable res = specifierParser.Match(stringValue); + if (res.HasValue) return res.Value; } } catch { } diff --git a/test/Spring/Spring.Core.Tests/Core/TypeConversion/TimeSpanConverterTests.cs b/test/Spring/Spring.Core.Tests/Core/TypeConversion/TimeSpanConverterTests.cs index 9bbfa346..c06327e1 100644 --- a/test/Spring/Spring.Core.Tests/Core/TypeConversion/TimeSpanConverterTests.cs +++ b/test/Spring/Spring.Core.Tests/Core/TypeConversion/TimeSpanConverterTests.cs @@ -21,7 +21,7 @@ #region Imports using System; -using System.Reflection; + using NUnit.Framework; #endregion @@ -113,10 +113,16 @@ namespace Spring.Core.TypeConversion public void ConvertFromStringWithHourSpecifier() { TimeSpanConverter tsc = new TimeSpanConverter(); + object timeSpan = tsc.ConvertFrom("1H"); Assert.IsNotNull(timeSpan); Assert.IsTrue(timeSpan is TimeSpan); Assert.AreEqual(TimeSpan.FromHours(1), (TimeSpan)timeSpan); + + tsc.ConvertFrom("1h"); + Assert.IsNotNull(timeSpan); + Assert.IsTrue(timeSpan is TimeSpan); + Assert.AreEqual(TimeSpan.FromHours(1), (TimeSpan)timeSpan); } [Test]