Added an interface for the scheduler service (this might be once candidate for replacement by different implementations); added a new helper class for the StringBuilder that allows garbage-free appending of integers and floats; added unit tests for most of the code

git-svn-id: file:///srv/devel/repo-conversion/nusu@188 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2010-02-11 21:07:04 +00:00
parent 66f0ae9b34
commit 237fb57fc8
8 changed files with 468 additions and 3 deletions

View File

@ -145,6 +145,7 @@
<Compile Include="Source\Scheduling\GenericTimeSource.Test.cs">
<DependentUpon>GenericTimeSource.cs</DependentUpon>
</Compile>
<Compile Include="Source\Scheduling\ISchedulerService.cs" />
<Compile Include="Source\Scheduling\ITimeSource.cs" />
<Compile Include="Source\Scheduling\Scheduler.cs" />
<Compile Include="Source\Scheduling\GenericTimeSource.cs" />
@ -305,6 +306,10 @@
<Compile Include="Source\IO\ChainStream.Test.cs">
<DependentUpon>ChainStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\StringBuilderHelper.cs" />
<Compile Include="Source\StringBuilderHelper.Test.cs">
<DependentUpon>StringBuilderHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\StringHelper.cs" />
<Compile Include="Source\StringHelper.Test.cs">
<DependentUpon>StringHelper.cs</DependentUpon>

View File

@ -131,6 +131,7 @@
<Compile Include="Source\Scheduling\GenericTimeSource.Test.cs">
<DependentUpon>GenericTimeSource.cs</DependentUpon>
</Compile>
<Compile Include="Source\Scheduling\ISchedulerService.cs" />
<Compile Include="Source\Scheduling\ITimeSource.cs" />
<Compile Include="Source\Scheduling\Scheduler.cs" />
<Compile Include="Source\Scheduling\GenericTimeSource.cs" />
@ -291,6 +292,10 @@
<Compile Include="Source\IO\ChainStream.Test.cs">
<DependentUpon>ChainStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\StringBuilderHelper.cs" />
<Compile Include="Source\StringBuilderHelper.Test.cs">
<DependentUpon>StringBuilderHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\StringHelper.cs" />
<Compile Include="Source\StringHelper.Test.cs">
<DependentUpon>StringHelper.cs</DependentUpon>

View File

@ -18,11 +18,11 @@ License along with this library
*/
#endregion
#if UNITTEST
using System;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support {

View File

@ -0,0 +1,101 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library 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
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using System.Threading;
namespace Nuclex.Support.Scheduling {
/// <summary>Service that allows the scheduled invocation of tasks</summary>
public interface ISchedulerService {
/// <summary>Schedules a notification at the specified absolute time</summary>
/// <param name="notificationTime">
/// Absolute time at which the notification will occur
/// </param>
/// <param name="callback">
/// Callback that will be invoked when the notification is due
/// </param>
/// <returns>A handle that can be used to cancel the notification</returns>
/// <remarks>
/// The notification is scheduled for the indicated absolute time. If the system
/// enters/leaves daylight saving time or the date/time is changed (for example
/// when the system synchronizes with an NTP server), this will affect
/// the notification. So if you need to be notified after a fixed time, use
/// the NotifyIn() method instead.
/// </remarks>
object NotifyAt(DateTime notificationTime, WaitCallback callback);
/// <summary>
/// Schedules a recurring notification after the specified amount of milliseconds
/// </summary>
/// <param name="delayMilliseconds">
/// Milliseconds after which the first notification will occur
/// </param>
/// <param name="intervalMilliseconds">
/// Interval in milliseconds at which the notification will be repeated
/// </param>
/// <param name="callback">
/// Callback that will be invoked when the notification is due
/// </param>
/// <returns>A handle that can be used to cancel the notification</returns>
object NotifyEach(int delayMilliseconds, int intervalMilliseconds, WaitCallback callback);
/// <summary>
/// Schedules a recurring notification after the specified time span
/// </summary>
/// <param name="delay">Delay after which the first notification will occur</param>
/// <param name="interval">Interval at which the notification will be repeated</param>
/// <param name="callback">
/// Callback that will be invoked when the notification is due
/// </param>
/// <returns>A handle that can be used to cancel the notification</returns>
object NotifyEach(TimeSpan delay, TimeSpan interval, WaitCallback callback);
/// <summary>
/// Schedules a notification after the specified amount of milliseconds
/// </summary>
/// <param name="delayMilliseconds">
/// Number of milliseconds after which the notification will occur
/// </param>
/// <param name="callback">
/// Callback that will be invoked when the notification is due
/// </param>
/// <returns>A handle that can be used to cancel the notification</returns>
object NotifyIn(int delayMilliseconds, WaitCallback callback);
/// <summary>Schedules a notification after the specified time span</summary>
/// <param name="delay">Delay after which the notification will occur</param>
/// <param name="callback">
/// Callback that will be invoked when the notification is due
/// </param>
/// <returns>A handle that can be used to cancel the notification</returns>
object NotifyIn(TimeSpan delay, WaitCallback callback);
/// <summary>Cancels a scheduled notification</summary>
/// <param name="notificationHandle">
/// Handle of the notification that will be cancelled
/// </param>
void Cancel(object notificationHandle);
}
} // namespace Nuclex.Support.Scheduling

View File

@ -28,7 +28,7 @@ using Nuclex.Support.Collections;
namespace Nuclex.Support.Scheduling {
/// <summary>Schedules actions for execution at a future point in time</summary>
public class Scheduler : IDisposable {
public class Scheduler : ISchedulerService, IDisposable {
/// <summary>One tick is 100 ns, meaning 10000 ticks equal 1 ms</summary>
private const long TicksPerMillisecond = 10000;

View File

@ -0,0 +1,151 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library 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
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
#if UNITTEST
using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>
/// Unit test for the helper class to .NET's string builder
/// </summary>
[TestFixture]
public class StringBuilderHelperTest {
/// <summary>
/// Verifies that bytes are correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendByte() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, (byte)255);
Assert.AreEqual("255", builder.ToString());
}
/// <summary>
/// Verifies that a byte with value 0 is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendNullByte() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, (byte)0);
Assert.AreEqual("0", builder.ToString());
}
/// <summary>
/// Verifies that a positive integer is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendPositiveInteger() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, 12345);
Assert.AreEqual("12345", builder.ToString());
}
/// <summary>
/// Verifies that an integer with value 0 is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendNullInteger() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, 0);
Assert.AreEqual("0", builder.ToString());
}
/// <summary>
/// Verifies that a negative integer is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendNegativeInteger() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, -12345);
Assert.AreEqual("-12345", builder.ToString());
}
/// <summary>
/// Verifies that negative floating point values are correctly converted
/// </summary>
[Test]
public void TestAppendNegativeFloat() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, -32.015625f);
Assert.AreEqual("-32.015625", builder.ToString());
}
/// <summary>
/// Verifies that positive floating point values are correctly converted
/// </summary>
[Test]
public void TestAppendPositiveFloat() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, 10.0625f);
Assert.AreEqual("10.0625", builder.ToString());
}
/// <summary>
/// Verifies that very small floating point values are correctly converted
/// </summary>
[Test]
public void TestAppendSmallFloat() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, 0.00390625f);
Assert.AreEqual("0.00390625", builder.ToString());
}
/// <summary>
/// Verifies that very large floating point values are correctly converted
/// </summary>
[Test]
public void TestAppendHugeFloat() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, 1000000000.0f);
Assert.AreEqual("1000000000.0", builder.ToString());
}
/// <summary>
/// Verifies that the contents of a string builder can be cleared
/// </summary>
[Test]
public void TestClear() {
StringBuilder builder = new StringBuilder("Hello World");
StringBuilderHelper.Clear(builder);
Assert.AreEqual(string.Empty, builder.ToString());
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

View File

@ -0,0 +1,202 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library 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
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace Nuclex.Support {
/// <summary>Contains helper methods for the string builder class</summary>
public class StringBuilderHelper {
/// <summary>Predefined unicode characters for the numbers 0 to 9</summary>
private static readonly char[] numbers = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
};
/// <summary>Clears the contents of a string builder</summary>
/// <param name="builder">String builder that will be cleared</param>
public static void Clear(StringBuilder builder) {
builder.Remove(0, builder.Length);
}
/// <summary>
/// Appends an integer to a string builder without generating garbage
/// </summary>
/// <param name="builder">String builder to which an integer will be appended</param>
/// <param name="value">Byte that will be appended to the string builder</param>
/// <remarks>
/// The normal StringBuilder.Append() method generates garbage when converting
/// integer arguments whereas this method will avoid any garbage, albeit probably
/// with a small performance impact compared to the built-in method.
/// </remarks>
public static void Append(StringBuilder builder, byte value) {
recursiveAppend(builder, value);
}
/// <summary>
/// Appends an integer to a string builder without generating garbage
/// </summary>
/// <param name="builder">String builder to which an integer will be appended</param>
/// <param name="value">Integer that will be appended to the string builder</param>
/// <remarks>
/// The normal StringBuilder.Append() method generates garbage when converting
/// integer arguments whereas this method will avoid any garbage, albeit probably
/// with a small performance impact compared to the built-in method.
/// </remarks>
public static void Append(StringBuilder builder, int value) {
if(value < 0) {
builder.Append('-');
recursiveAppend(builder, -value);
} else {
recursiveAppend(builder, value);
}
}
/// <summary>
/// Appends an long integer to a string builder without generating garbage
/// </summary>
/// <param name="builder">String builder to which an integer will be appended</param>
/// <param name="value">Long integer that will be appended to the string builder</param>
/// <remarks>
/// The normal StringBuilder.Append() method generates garbage when converting
/// integer arguments whereas this method will avoid any garbage, albeit probably
/// with a small performance impact compared to the built-in method.
/// </remarks>
public static void Append(StringBuilder builder, long value) {
if(value < 0) {
builder.Append('-');
recursiveAppend(builder, -value);
} else {
recursiveAppend(builder, value);
}
}
/// <summary>
/// Appends a floating point value to a string builder without generating garbage
/// </summary>
/// <param name="builder">String builder the value will be appended to</param>
/// <param name="value">Value that will be appended to the string builder</param>
public static void Append(StringBuilder builder, float value) {
const int ExponentBits = 0xFF; // Bit mask for the exponent bits
const int FractionalBitCount = 23; // Number of bits for fractional part
const int ExponentBias = 127; // Bias subtraced from exponent
const int NumericBitCount = 31; // Bits without sign
// You do not modify these as they're calculated based on the
const int FractionalBits = (2 << FractionalBitCount) - 1;
const int HighestFractionalBit = (1 << FractionalBitCount);
const int FractionalBitCountPlusOne = FractionalBitCount + 1;
int intValue = FloatHelper.ReinterpretAsInt(value);
int exponent = ((intValue >> FractionalBitCount) & ExponentBits) - ExponentBias;
int mantissa = (intValue & FractionalBits) | HighestFractionalBit;
int integral;
int fractional;
if(exponent >= 0) {
if(exponent >= FractionalBitCount) {
Debug.Assert(exponent < NumericBitCount);
integral = mantissa << (exponent - FractionalBitCount);
fractional = 0;
} else {
integral = mantissa >> (FractionalBitCount - exponent);
fractional = (mantissa << (exponent + 1)) & FractionalBits;
}
} else {
Debug.Assert(exponent >= -FractionalBitCount);
integral = 0;
fractional = (mantissa & FractionalBits) >> -(exponent + 1);
}
/*
if(exponent >= NumericBitCount) {
throw new ArgumentException("Value too large", "value");
} else if(exponent < -FractionalBitCount) {
throw new ArgumentException("Value too small", "value");
} else if(exponent >= FractionalBitCount) {
integral = mantissa << (exponent - FractionalBitCount);
fractional = 0;
} else if(exponent >= 0) {
integral = mantissa >> (FractionalBitCount - exponent);
fractional = (mantissa << (exponent + 1)) & FractionalBits;
} else { // exp2 < 0
integral = 0;
fractional = (mantissa & FractionalBits) >> -(exponent + 1);
}
*/
// Build the integral part
if(intValue < 0) {
builder.Append('-');
}
if(integral == 0) {
builder.Append('0');
} else {
recursiveAppend(builder, integral);
}
builder.Append('.');
// Build the fractional part
if(fractional == 0) {
builder.Append('0');
} else {
while(fractional != 0) {
fractional *= 10;
int digit = (fractional >> FractionalBitCountPlusOne);
builder.Append(numbers[digit]);
fractional &= FractionalBits;
}
}
}
/// <summary>Recursively appends a number's characters to a string builder</summary>
/// <param name="builder">String builder the number will be appended to</param>
/// <param name="remaining">Remaining digits that will be recursively processed</param>
private static void recursiveAppend(StringBuilder builder, int remaining) {
int digit;
int tenth = Math.DivRem(remaining, 10, out digit);
if(tenth > 0) {
recursiveAppend(builder, tenth);
}
builder.Append(numbers[digit]);
}
/// <summary>Recursively appends a number's characters to a string builder</summary>
/// <param name="builder">String builder the number will be appended to</param>
/// <param name="remaining">Remaining digits that will be recursively processed</param>
private static void recursiveAppend(StringBuilder builder, long remaining) {
long digit;
long tenth = Math.DivRem(remaining, 10, out digit);
if(tenth > 0) {
recursiveAppend(builder, tenth);
}
builder.Append(numbers[digit]);
}
}
} // namespace Nuclex.Support

View File

@ -165,3 +165,4 @@ namespace Nuclex.Support {
}
} // namespace Nuclex.Support