diff --git a/.gitignore b/.gitignore index fdb3d25f0..3147848dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ +BenchmarkDotNet.Artifacts/ + bin/ obj/ +packages/ .idea/ latest/ /env-vars.bat @@ -42,5 +45,18 @@ App_Data/ *.resharper.user *.suo *.user +.vs/ +.vscode/ +*.lock.json +*.nuget.props +*.nuget.targets NuGet/ -build/ \ No newline at end of file +NuGet.Pcl/ +NuGet.Signed/ + +build/ +_NCrunch*/ +*.ncrunchproject +*.ncrunchsolution +*.xml +*.xap \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..79adc4650 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceRoot}/tests/ServiceStack.Text.Tests/bin/Debug/netcoreapp1.1/ServiceStack.Text.Tests.dll", + //"args": ["--test=ServiceStack.Text.Tests.ReflectionExtensionTests", "--labels=All"], + "args": ["--labels=All"], + "cwd": "${workspaceRoot}", + "stopAtEntry": false, + "externalConsole": false + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..0da8dcb66 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,30 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "command": "dotnet", + "args": [], + "options": { + "env": { + "FrameworkPathOverride": "/usr/lib/mono/4.5/" + } + }, + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "src/ServiceStack.Text.sln", + "-v", + "m" + ], + "problemMatcher": "$msCompile", + "group": { + "_id": "build", + "isDefault": false + } + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..9cbc97217 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +## Contributor License Agreement + +* Please sign the [Contributor License Agreement](https://docs.google.com/forms/d/16Op0fmKaqYtxGL4sg7w_g-cXXyCoWjzppgkuqzOeKyk/viewform) in order to have your changes merged. + +See the [Contributing Wiki](https://github.com/ServiceStack/ServiceStack/wiki/Contributing) to learn how you can Contribute! diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c5e98c937..000000000 --- a/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2007-2011, Demis Bellot, ServiceStack. -http://www.servicestack.net -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ServiceStack nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 000000000..42daf5f44 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/NuGet/lib/sl4-windowsphone71/ServiceStack.Text.WP.XML b/NuGet/lib/sl4-windowsphone71/ServiceStack.Text.WP.XML deleted file mode 100644 index 89c9163a5..000000000 --- a/NuGet/lib/sl4-windowsphone71/ServiceStack.Text.WP.XML +++ /dev/null @@ -1,409 +0,0 @@ - - - - ServiceStack.Text.WP - - - - - Creates an instance of a Type from a string value - - - - - A fast, standards-based, serialization-issue free DateTime serailizer. - - - - - Class to hold - - - - - - WCF Json format: /Date(unixts+0000)/ - - - - - - - WCF Json format: /Date(unixts+0000)/ - - - - - - - Shortcut escape when we're sure value doesn't contain any escaped chars - - - - - - - Since Silverlight doesn't have char.ConvertFromUtf32() so putting Mono's implemenation inline. - - - - - - - Creates an instance of a Type from a string value - - - - - Determines whether the specified type is convertible from string. - - The type. - - true if the specified type is convertible from string; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Parses the specified type. - - The type. - The value. - - - - - Useful extension method to get the Dictionary[string,string] representation of any POCO type. - - - - - - Recursively prints the contents of any POCO object in a human-friendly, readable format - - - - - - Implement the serializer using a more static approach - - - - - - Determines whether this serializer can create the specified type from a string. - - The type. - - true if this instance [can create from string] the specified type; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Deserializes from reader. - - The reader. - - - - - Serializes to string. - - The value. - - - - - Serializes to writer. - - The value. - The writer. - - - - Parses the specified value. - - The value. - - - - - Pretty Thread-Safe cache class from: - http://code.google.com/p/dapper-dot-net/source/browse/Dapper/SqlMapper.cs - - This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), - and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** - equality. The type is fully thread-safe. - - - - - Utils to load types - - - - - Find the type from the name supplied - - [typeName] or [typeName, assemblyName] - - - - - Find type if it exists - - - - The type if it exists - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - Gets or sets a value indicating if the framework should throw serialization exceptions - or continue regardless of deserialization errors. If the framework - will throw; otherwise, it will parse as many fields as possible. The default is . - - - - - Provide hint to MonoTouch AOT compiler to pre-compile generic classes for all your DTOs. - Just needs to be called once in a static constructor. - - - - - Never emit type info for this type - - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - Define custom serialization fn for BCL Structs - - - - - Define custom deserialization fn for BCL Structs - - - - - Exclude specific properties of this type from being serialized - - - - - Opt-in flag to set some Value Types to be treated as a Ref Type - - - - - micro optimizations: using flags instead of value.IndexOfAny(EscapeChars) - - - - - - - Parses the specified value. - - The value. - - - - - A class to allow the conversion of doubles to string representations of - their exact decimal values. The implementation aims for readability over - efficiency. - - Courtesy of @JonSkeet - http://www.yoda.arachsys.com/csharp/DoubleConverter.cs - - - - - - - - How many digits are *after* the decimal point - - - - - Constructs an arbitrary decimal expansion from the given long. - The long must not be negative. - - - - - Multiplies the current expansion by the given amount, which should - only be 2 or 5. - - - - - Shifts the decimal point; a negative value makes - the decimal expansion bigger (as fewer digits come after the - decimal place) and a positive value makes the decimal - expansion smaller. - - - - - Removes leading/trailing zeroes from the expansion. - - - - - Converts the value to a proper decimal string representation. - - - - - Implement the serializer using a more static approach - - - - - - A hashset implementation that uses an IDictionary - - - - - micro optimizations: using flags instead of value.IndexOfAny(EscapeChars) - - - - - - - @jonskeet: Collection of utility methods which operate on streams. - r285, February 26th 2009: http://www.yoda.arachsys.com/csharp/miscutil/ - - - - - Reads the given stream up to the end, returning the data as a byte - array. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer size. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer for transferring data. Note that the - current contents of the buffer is ignored, so the buffer needn't - be cleared beforehand. - - - - - Copies all the data from one stream into another. - - - - - Copies all the data from one stream into another, using a buffer - of the given size. - - - - - Copies all the data from one stream into another, using the given - buffer for transferring data. Note that the current contents of - the buffer is ignored, so the buffer needn't be cleared beforehand. - - - - - Reads exactly the given number of bytes from the specified stream. - If the end of the stream is reached before the specified amount - of data is read, an exception is thrown. - - - - - Reads into a buffer, filling it completely. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Same as ReadExactly, but without the argument checks. - - - - - Converts from base: 0 - 62 - - The source. - From. - To. - - - - - Skip the encoding process for 'safe strings' - - - - - - - Implement the serializer using a more static approach - - - - - - Get the type(string) constructor if exists - - The type. - - - - diff --git a/NuGet/lib/sl4-windowsphone71/ServiceStack.Text.WP.dll b/NuGet/lib/sl4-windowsphone71/ServiceStack.Text.WP.dll deleted file mode 100644 index 89b073582..000000000 Binary files a/NuGet/lib/sl4-windowsphone71/ServiceStack.Text.WP.dll and /dev/null differ diff --git a/NuGet/lib/sl4/ServiceStack.Text.dll b/NuGet/lib/sl4/ServiceStack.Text.dll deleted file mode 100644 index 73cf2f687..000000000 Binary files a/NuGet/lib/sl4/ServiceStack.Text.dll and /dev/null differ diff --git a/NuGet/lib/sl4/ServiceStack.Text.xml b/NuGet/lib/sl4/ServiceStack.Text.xml deleted file mode 100644 index 7bb9bfa94..000000000 --- a/NuGet/lib/sl4/ServiceStack.Text.xml +++ /dev/null @@ -1,385 +0,0 @@ - - - - ServiceStack.Text - - - - - Shortcut escape when we're sure value doesn't contain any escaped chars - - - - - - - Since Silverlight doesn't have char.ConvertFromUtf32() so putting Mono's implemenation inline. - - - - - - - Implement the serializer using a more static approach - - - - - - A fast, standards-based, serialization-issue free DateTime serailizer. - - - - - Creates an instance of a Type from a string value - - - - - Determines whether this serializer can create the specified type from a string. - - The type. - - true if this instance [can create from string] the specified type; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Deserializes from reader. - - The reader. - - - - - Serializes to string. - - The value. - - - - - Serializes to writer. - - The value. - The writer. - - - - Parses the specified value. - - The value. - - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - Provide hint to MonoTouch AOT compiler to pre-compile generic classes for all your DTOs. - Just needs to be called once in a static constructor. - - - - - Never emit type info for this type - - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - Define custom serialization fn for BCL Structs - - - - - Define custom deserialization fn for BCL Structs - - - - - Exclude specific properties of this type from being serialized - - - - - Pretty Thread-Safe cache class from: - http://code.google.com/p/dapper-dot-net/source/browse/Dapper/SqlMapper.cs - - This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), - and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** - equality. The type is fully thread-safe. - - - - - Creates an instance of a Type from a string value - - - - - Determines whether the specified type is convertible from string. - - The type. - - true if the specified type is convertible from string; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Parses the specified type. - - The type. - The value. - - - - - Useful extension method to get the Dictionary[string,string] representation of any POCO type. - - - - - - Recursively prints the contents of any POCO object in a human-friendly, readable format - - - - - - A class to allow the conversion of doubles to string representations of - their exact decimal values. The implementation aims for readability over - efficiency. - - Courtesy of @JonSkeet - http://www.yoda.arachsys.com/csharp/DoubleConverter.cs - - - - - - - - How many digits are *after* the decimal point - - - - - Constructs an arbitrary decimal expansion from the given long. - The long must not be negative. - - - - - Multiplies the current expansion by the given amount, which should - only be 2 or 5. - - - - - Shifts the decimal point; a negative value makes - the decimal expansion bigger (as fewer digits come after the - decimal place) and a positive value makes the decimal - expansion smaller. - - - - - Removes leading/trailing zeroes from the expansion. - - - - - Converts the value to a proper decimal string representation. - - - - - @jonskeet: Collection of utility methods which operate on streams. - r285, February 26th 2009: http://www.yoda.arachsys.com/csharp/miscutil/ - - - - - Reads the given stream up to the end, returning the data as a byte - array. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer size. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer for transferring data. Note that the - current contents of the buffer is ignored, so the buffer needn't - be cleared beforehand. - - - - - Copies all the data from one stream into another. - - - - - Copies all the data from one stream into another, using a buffer - of the given size. - - - - - Copies all the data from one stream into another, using the given - buffer for transferring data. Note that the current contents of - the buffer is ignored, so the buffer needn't be cleared beforehand. - - - - - Reads exactly the given number of bytes from the specified stream. - If the end of the stream is reached before the specified amount - of data is read, an exception is thrown. - - - - - Reads into a buffer, filling it completely. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Same as ReadExactly, but without the argument checks. - - - - - Implement the serializer using a more static approach - - - - - - Parses the specified value. - - The value. - - - - - Converts from base: 0 - 62 - - The source. - From. - To. - - - - - Skip the encoding process for 'safe strings' - - - - - - - Class to hold - - - - - - Get the type(string) constructor if exists - - The type. - - - - - Implement the serializer using a more static approach - - - - - - Utils to load types - - - - - Find the type from the name supplied - - [typeName] or [typeName, assemblyName] - - - - - Find type if it exists - - - - The type if it exists - - - - micro optimizations: using flags instead of value.IndexOfAny(EscapeChars) - - - - - - - WCF Json format: /Date(unixts+0000)/ - - - - - - - micro optimizations: using flags instead of value.IndexOfAny(EscapeChars) - - - - - - diff --git a/NuGet/lib/sl5/ServiceStack.Text.dll b/NuGet/lib/sl5/ServiceStack.Text.dll deleted file mode 100644 index 4e69ec900..000000000 Binary files a/NuGet/lib/sl5/ServiceStack.Text.dll and /dev/null differ diff --git a/NuGet/lib/sl5/ServiceStack.Text.xml b/NuGet/lib/sl5/ServiceStack.Text.xml deleted file mode 100644 index 7bb9bfa94..000000000 --- a/NuGet/lib/sl5/ServiceStack.Text.xml +++ /dev/null @@ -1,385 +0,0 @@ - - - - ServiceStack.Text - - - - - Shortcut escape when we're sure value doesn't contain any escaped chars - - - - - - - Since Silverlight doesn't have char.ConvertFromUtf32() so putting Mono's implemenation inline. - - - - - - - Implement the serializer using a more static approach - - - - - - A fast, standards-based, serialization-issue free DateTime serailizer. - - - - - Creates an instance of a Type from a string value - - - - - Determines whether this serializer can create the specified type from a string. - - The type. - - true if this instance [can create from string] the specified type; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Deserializes from reader. - - The reader. - - - - - Serializes to string. - - The value. - - - - - Serializes to writer. - - The value. - The writer. - - - - Parses the specified value. - - The value. - - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - Provide hint to MonoTouch AOT compiler to pre-compile generic classes for all your DTOs. - Just needs to be called once in a static constructor. - - - - - Never emit type info for this type - - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - Define custom serialization fn for BCL Structs - - - - - Define custom deserialization fn for BCL Structs - - - - - Exclude specific properties of this type from being serialized - - - - - Pretty Thread-Safe cache class from: - http://code.google.com/p/dapper-dot-net/source/browse/Dapper/SqlMapper.cs - - This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), - and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** - equality. The type is fully thread-safe. - - - - - Creates an instance of a Type from a string value - - - - - Determines whether the specified type is convertible from string. - - The type. - - true if the specified type is convertible from string; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Parses the specified type. - - The type. - The value. - - - - - Useful extension method to get the Dictionary[string,string] representation of any POCO type. - - - - - - Recursively prints the contents of any POCO object in a human-friendly, readable format - - - - - - A class to allow the conversion of doubles to string representations of - their exact decimal values. The implementation aims for readability over - efficiency. - - Courtesy of @JonSkeet - http://www.yoda.arachsys.com/csharp/DoubleConverter.cs - - - - - - - - How many digits are *after* the decimal point - - - - - Constructs an arbitrary decimal expansion from the given long. - The long must not be negative. - - - - - Multiplies the current expansion by the given amount, which should - only be 2 or 5. - - - - - Shifts the decimal point; a negative value makes - the decimal expansion bigger (as fewer digits come after the - decimal place) and a positive value makes the decimal - expansion smaller. - - - - - Removes leading/trailing zeroes from the expansion. - - - - - Converts the value to a proper decimal string representation. - - - - - @jonskeet: Collection of utility methods which operate on streams. - r285, February 26th 2009: http://www.yoda.arachsys.com/csharp/miscutil/ - - - - - Reads the given stream up to the end, returning the data as a byte - array. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer size. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer for transferring data. Note that the - current contents of the buffer is ignored, so the buffer needn't - be cleared beforehand. - - - - - Copies all the data from one stream into another. - - - - - Copies all the data from one stream into another, using a buffer - of the given size. - - - - - Copies all the data from one stream into another, using the given - buffer for transferring data. Note that the current contents of - the buffer is ignored, so the buffer needn't be cleared beforehand. - - - - - Reads exactly the given number of bytes from the specified stream. - If the end of the stream is reached before the specified amount - of data is read, an exception is thrown. - - - - - Reads into a buffer, filling it completely. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Same as ReadExactly, but without the argument checks. - - - - - Implement the serializer using a more static approach - - - - - - Parses the specified value. - - The value. - - - - - Converts from base: 0 - 62 - - The source. - From. - To. - - - - - Skip the encoding process for 'safe strings' - - - - - - - Class to hold - - - - - - Get the type(string) constructor if exists - - The type. - - - - - Implement the serializer using a more static approach - - - - - - Utils to load types - - - - - Find the type from the name supplied - - [typeName] or [typeName, assemblyName] - - - - - Find type if it exists - - - - The type if it exists - - - - micro optimizations: using flags instead of value.IndexOfAny(EscapeChars) - - - - - - - WCF Json format: /Date(unixts+0000)/ - - - - - - - micro optimizations: using flags instead of value.IndexOfAny(EscapeChars) - - - - - - diff --git a/NuGet/servicestack.text.nuspec b/NuGet/servicestack.text.nuspec deleted file mode 100644 index 34e9c486c..000000000 --- a/NuGet/servicestack.text.nuspec +++ /dev/null @@ -1,29 +0,0 @@ - - - - ServiceStack.Text - .NET's fastest JSON Serializer by ServiceStack - 3.9.32 - Demis Bellot - Demis Bellot - .NET's fastest JSON, JSV and CSV Text Serializers (3x faster than JSON.NET) - - .NET's fastest JSON, JSV and CSV Text Serializers (3x faster than JSON.NET). Fast, Light, Resilient. - Benchmarks at: http://servicestack.net/benchmarks/ - Includes the String and Stream functionality for all the ServiceStack projects including: - - T.Dump() generic extension method for easy dbugging and introspection of types - - WebRequest, List, Dictionary and DateTime extensions - - https://github.com/ServiceStack/ServiceStack.Text - https://github.com/ServiceStack/ServiceStack.Text/blob/master/LICENSE - http://www.servicestack.net/logo-100x100.png - JSON Text Serializer CSV JSV Dump PrettyPrint Fast - en-US - ServiceStack 2012 and contributors - - - - - - - diff --git a/NuGet/stackexpress.text.nuspec b/NuGet/stackexpress.text.nuspec deleted file mode 100644 index 91a6f7107..000000000 --- a/NuGet/stackexpress.text.nuspec +++ /dev/null @@ -1,25 +0,0 @@ - - - - StackExpress.Text - .NET's fastest JSON Serializer by StackExpress - $version$ - Demis Bellot - Demis Bellot - .NET's fastest JSON, JSV and CSV Text Serializers (3x faster than JSON.NET) - - .NET's fastest JSON, JSV and CSV Text Serializers (3x faster than JSON.NET). Fast, Light, Resilient. - Benchmarks at: http://servicestack.net/benchmarks/ - Includes the String and Stream functionality for all the StackExpress projects including: - - T.Dump() generic extension method for easy dbugging and introspection of types - - WebRequest, List, Dictionary and DateTime extensions - - https://github.com/ServiceStack/ServiceStack.Text - https://github.com/ServiceStack/ServiceStack.Text/blob/master/LICENSE - http://www.servicestack.net/logo-100x100.png - JSON Text Serializer CSV JSV Dump PrettyPrint Fast - en-US - StackExpress 2012 and contributors - - - diff --git a/README.md b/README.md index bf58a5f57..50e856543 100644 --- a/README.md +++ b/README.md @@ -1,450 +1,5 @@ -[Join the new google group](http://groups.google.com/group/servicestack) or -follow [@demisbellot](http://twitter.com/demisbellot) and [@ServiceStack](http://twitter.com/servicestack) -for twitter updates. +Follow [@ServiceStack](https://twitter.com/servicestack), [view the docs](https://docs.servicestack.net), use [StackOverflow](https://stackoverflow.com/questions/ask?tags=servicestack,servicestack.redis) or [Customer Forums](https://forums.servicestack.net/) for support. -# The Home of [.NET's fastest JSON](http://www.servicestack.net/mythz_blog/?p=344), [JSV](http://www.servicestack.net/mythz_blog/?p=176) and CSV Text Serializers. +# Read ServiceStack.Text Docs at [docs.servicestack.net/text](https://docs.servicestack.net/text/) -ServiceStack.Text is an **independent, dependency-free** serialization library that contains all of ServiceStack's text processing functionality, including: - -* [JsonSerializer](http://www.servicestack.net/mythz_blog/?p=344) -* [TypeSerializer (JSV-Format)](https://github.com/ServiceStack/ServiceStack.Text/wiki/JSV-Format) -* CsvSerializer -* [T.Dump extension method](http://www.servicestack.net/mythz_blog/?p=202) -* StringExtensions - Xml/Json/Csv/Url encoding, BaseConvert, Rot13, Hex escape, etc. -* Stream, Reflection, List, DateTime, etc extensions and utils - -Supports custom builds for: .NET 3.5+, Mono, MonoTouch/MonoDroid, Silverlight 4/5, XBOX, Windows Phone 7 - -All in a single **144kb** dependency-free ServiceStack.Text.dll - -# Simple API - -Like most of the interfaces in Service Stack, the API is simple and descriptive. In most cases these are the only methods that you would commonly use: - - string TypeSerializer.SerializeToString(T value) - void TypeSerializer.SerializeToWriter(T value, TextWriter writer) - - T TypeSerializer.DeserializeFromString(string value) - T TypeSerializer.DeserializeFromReader(TextReader reader) - -Where *T* can be any .NET POCO type. That's all there is - the API was intentionally left simple :) - -### Dynamic JSON parsing API - - JsonObject.Parse() - JsonArrayObjects.Parse() - -### Extension Methods - - T FromJson() - string ToJson(T) - - T FromJsv() - string ToJsv(T) - -Dump / Diagnostic Extensions: - - T Dump() - T Print() - T PrintDump() - string Fmt(args) - -URL Extensions: - - string GetStringFromUrl() - string GetJsonFromUrl() - string GetResponseStatus() - string UrlEncode() / UrlDecode() - string HexEscape() / HexUnescape() - string UrlFormat() / AppendPath() / AppendPaths() / WithTrailingSlash() - string WithoutExtension() / ParentDirectory() / ReadAllText() - -Stream Extensions: - - Stream WriteTo(Stream) / CopyTo() - StreamReader ReadLines() - Stream ReadFully() / ReadExactly() - -String Utils: - - string SplitOnFirst() / SplitOnLast() - string IndexOfAny() - string StripHtml() / ToCamelCase() - string SafeSubstring() - string ToUtf8Bytes() / FromUtf8Bytes() - -and many more String, Reflection, List, Dictionary, DateTime extensions... - -### Supports Dynamic JSON as well - -Although usually used to (de)serialize C#/.NET POCO types, it also includes a flexible API allowing you to deserialize any -JSON payload without it's concrete type, see these real-world examples: - - - [Parsing GitHub's v3 API with typed DTOs](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/GithubV3ApiTests.cs) - - [Parsing GitHub's JSON response](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/GitHubRestTests.cs) - - [Parsing Google Maps JSON Response](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/GMapDirectionsTests.cs) - - [Parsing Centroid](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/CentroidTests.cs) - -Also a thin **.NET 4.0 Dynamic JSON** wrapper around ServiceStack's JSON library is included in the -[ServiceStack.Razor](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Razor/DynamicJson.cs) -project. It provides a dynamic, but more succinct API than the above options. - -## NuGet ServiceStack.Text - -![Install-Pacakage ServiceStack.Text](http://servicestack.net/img/nuget-servicestack.text.png) - - -## ServiceStack.JsonSerializer - the fastest JSON Serializer for .NET -For reasons outlined [in this blog post](http://www.servicestack.net/mythz_blog/?p=344) I decided to re-use *TypeSerializer's* text processing-core to create ServiceStack.JsonSerializer - the fastest JSON Serializer for .NET. -Based on the [Northwind Benchmarks](http://www.servicestack.net/benchmarks/NorthwindDatabaseRowsSerialization.100000-times.2010-08-17.html) it's *3.6x* faster than .NET's BCL JsonDataContractSerializer and *3x* faster then the previous fastest JSON serializer benchmarked - [JSON.NET](http://json.codeplex.com/). - -A comprehensive set of other .NET benchmarks are maintained at [servicestack.net/benchmarks](http://www.servicestack.net/benchmarks/). - -## ServiceStack.CsvSerializer -As CSV is an important format in many data access and migration scenarios, it became [the latest format included in ServiceStack](https://github.com/ServiceStack/ServiceStack/wiki/ServiceStack-CSV-Format) which allows all your existing web services to take advantage of the new format without config or code-changes. As its built using the same tech that makes the JSON and JSV serializers so fast, we expect it to be the fastest POCO CSV Serializer for .NET. - -## ServiceStack.TypeSerializer and the JSV-format -Included in this project is `TypeSerializer` - The fastest and most compact text-based serializer for .NET. It's a light-weight compact Text Serializer which can be used to serialize any .NET data type including your own custom POCO's and DataContract's. More info on its JSV Format can be found on the [introductory post](http://www.servicestack.net/mythz_blog/?p=176). - -## T.Dump() Extension method -Another useful library to have in your .NET toolbox is the [T.Dump() Extension Method](http://www.servicestack.net/mythz_blog/?p=202). Under the hood it uses a *Pretty Print* Output of the JSV Format to recursively dump the contents of any .NET object. Example usage and output: - - var model = new TestModel(); - Console.WriteLine(model.Dump()); - - //Example Output - { - Int: 1, - String: One, - DateTime: 2010-04-11, - Guid: c050437f6fcd46be9b2d0806a0860b3e, - EmptyIntList: [], - IntList: - [ - 1, - 2, - 3 - ], - StringList: - [ - one, - two, - three - ], - StringIntMap: - { - a: 1, - b: 2, - c: 3 - } - } - - -# Download -### ServiceStack.Text is included with [ServiceStack.zip](https://github.com/ServiceStack/ServiceStack/downloads) -### or available to download separately in a standalone [ServiceStack.Text.zip](https://github.com/ServiceStack/ServiceStack.Text/downloads). - -
- -# ServiceStack's JsonSerializer - -ServiceStack's JsonSerializer is optimized for serializing C# POCO types in and out of JSON as fast, compact and cleanly as possible. In most cases C# objects serializes as you would expect them to without added json extensions or serializer-specific artefacts. - -JsonSerializer provides a simple API that allows you to serialize any .NET generic or runtime type into a string, TextWriter/TextReader or Stream. - -### Serialization API - - string SerializeToString(T) - void SerializeToWriter(T, TextWriter) - void SerializeToStream(T, Stream) - string SerializeToString(object, Type) - void SerializeToWriter(object, Type, TextWriter) - void SerializeToStream(object, Type, Stream) - -### Deserialization API - - T DeserializeFromString(string) - T DeserializeFromReader(TextReader) - object DeserializeFromString(string, Type) - object DeserializeFromReader(reader, Type) - object DeserializeFromStream(Type, Stream) - T DeserializeFromStream(Stream) - -### Extension methods - - string ToJson(this T) - T FromJson(this string) - -Convenient **ToJson/FromJson** extension methods are also included reducing the amount of code required, e.g: - - new []{ 1, 2, 3 }.ToJson() //= [1,2,3] - "[1,2,3]".FromJson() //= int []{ 1, 2, 3 } - -## JSON Format - -JSON is a lightweight text serialization format with a spec that's so simple that it fits on one page: [http://www.json.org](json.org). - -The only valid values in JSON are: - - * string - * number - * object - * array - * true - * false - * null - -Where most allowed values are scalar and the only complex types available are objects and arrays. Although limited, the above set of types make a good fit and can express most programming data structures. - -### number, true, false types - -All C# boolean and numeric data types are stored as-is without quotes. - -### null type - -For the most compact output null values are omitted from the serialized by default. If you want to include null values set the global configuration: - - JsConfig.IncludeNullValues = true; - -### string type - -All other scalar values are stored as strings that are surrounded with double quotes. - -### C# Structs and Value Types - -Because a C# struct is a value type whose public properties are normally just convenience properties around a single scalar value, they are ignored instead the **TStruct.ToString()** method is used to serialize and either the **static TStruct.ParseJson()**/**static TStruct.ParseJsv()** methods or **new TStruct(string)** constructor will be used to deserialize the value type if it exists. - -### array type - -Any List, Queue, Stack, Array, Collection, Enumerables including custom enumerable types are stored in exactly the same way as a JavaScript array literal, i.e: - - [1,2,3,4,5] - -All elements in an array must be of the same type. If a custom type is both an IEnumerable and has properties it will be treated as an array and the extra properties will be ignored. - -### object type - -The JSON object type is the most flexible and is how most complex .NET types are serialized. The JSON object type is a key-value pair JavaScript object literal where the key is always a double-quoted string. - -Any IDictionary is serialized into a standard JSON object, i.e: - - {"A":1,"B":2,"C":3,"D":4} - -Which happens to be the same as C# POCO types (inc. Interfaces) with the values: - -`new MyClass { A=1, B=2, C=3, D=4 }` - - {"A":1,"B":2,"C":3,"D":4} - -Only public properties on reference types are serialized with the C# Property Name used for object key and the Property Value as the value. At the moment it is not possible to customize the Property Name. - -JsonSerializer also supports serialization of anonymous types in much the same way: - -`new { A=1, B=2, C=3, D=4 }` - - {"A":1,"B":2,"C":3,"D":4} - - -## Custom Serialization - -Although JsonSerializer is optimized for serializing .NET POCO types, it still provides some options to change the convention-based serialization routine. - -### Using Structs to Customize JSON - -This makes it possible to customize the serialization routine and provide an even more compact wire format. - -E.g. Instead of using a JSON object to represent a point - - { Width=20, Height=10 } - -You could use a struct and reduce it to just: - - "20x10" - -By overriding **ToString()** and providing a static **Size ParseJson()** method: - - public struct Size - { - public double Width { get; set; } - public double Height { get; set; } - - public override string ToString() - { - return Width + "x" + Height; - } - - public static Size ParseJson(string json) - { - var size = json.Split('x'); - return new Size { - Width = double.Parse(size[0]), - Height = double.Parse(size[1]) - }; - } - } - -Which would change it to the more compact JSON output: - - new Size { Width = 20, Height = 10 }.ToJson() // = "20x10" - -That allows you to deserialize it back in the same way: - - var size = "20x10".FromJson(); - -### Using Custom IEnumerable class to serialize a JSON array - -In addition to using a Struct you can optionally use a custom C# IEnumerable type to provide a strong-typed wrapper around a JSON array: - - public class Point : IEnumerable - { - double[] points = new double[2]; - - public double X - { - get { return points[0]; } - set { points[0] = value; } - } - - public double Y - { - get { return points[1]; } - set { points[1] = value; } - } - - public IEnumerator GetEnumerator() - { - foreach (var point in points) - yield return point; - } - } - -Which serializes the Point into a compact JSON array: - - new Point { X = 1, Y = 2 }.ToJson() // = [1,2] - -### Custom Serialization Routines - -If you can't change the definition of a ValueType (e.g. because its in the BCL), you can assign a custom serialization / -deserialization routine to use instead. E.g. here's how you can add support for `System.Drawing.Color`: - - JsConfig.SerializeFn = c => c.ToString().Replace("Color ","").Replace("[","").Replace("]",""); - JsConfig.DeSerializeFn = System.Drawing.Color.FromName; - -## Custom Deserialization - -Because the same wire format shared between Dictionaries, POCOs and anonymous types, in most cases what you serialize with one type can be deserialized with another, i.e. an Anonymous type can be deserialized back into a Dictionary which can be deserialized into a strong-typed POCO and vice-versa. - -Although the JSON Serializer is best optimized for serializing and deserializing .NET types, it's flexible enough to consume 3rd party JSON apis although this generally requires custom de-serialization to convert it into an idiomatic .NET type. - -[GitHubRestTests.cs](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/GitHubRestTests.cs) - - 1. Using [JsonObject](https://github.com/ServiceStack/ServiceStack.Text/blob/master/src/ServiceStack.Text/JsonObject.cs) - 2. Using Generic .NET Collection classes - 3. Using Customized DTO's in the shape of the 3rd party JSON response - -[CentroidTests](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/CentroidTests.cs) is another example that uses the JsonObject to parse a complex custom JSON response. - - -#TypeSerializer Details (JSV Format) - -Out of the box .NET provides a fairly quick but verbose Xml DataContractSerializer or a slightly more compact but slower JsonDataContractSerializer. -Both of these options are fragile and likely to break with any significant schema changes. -TypeSerializer addresses these shortcomings by being both smaller and significantly faster than the most popular options. -It's also more resilient, e.g. a strongly-typed POCO object can be deserialized back into a loosely-typed string Dictionary and vice-versa. - -With that in mind, TypeSerializer's main features are: - - - Fastest and most compact text-serializer for .NET - - Human readable and writeable, self-describing text format - - Non-invasive and configuration-free - - Resilient to schema changes - - Serializes / De-serializes any .NET data type (by convention) - + Supports custom, compact serialization of structs by overriding `ToString()` and `static T Parse(string)` methods - + Can serialize inherited, interface or 'late-bound objects' data types - + Respects opt-in DataMember custom serialization for DataContract dto types. - -These characteristics make it ideal for use anywhere you need to store or transport .NET data-types, e.g. for text blobs in a ORM, data in and out of a key-value store or as the text-protocol in .NET to .NET web services. - -As such, it's utilized within ServiceStack's other components: - - OrmLite - to store complex types on table models as text blobs in a database field and - - [ServiceStack.Redis](https://github.com/ServiceStack/ServiceStack.Redis) - to store rich POCO data types into the very fast [redis](http://code.google.com/p/redis) instances. - -You may also be interested in the very useful [T.Dump() extension method](http://www.servicestack.net/mythz_blog/?p=202) for recursively viewing the contents of any C# POCO Type. - ---- - -# Performance -Type Serializer is actually the fastest and most compact *text serializer* available for .NET. -Out of all the serializers benchmarked, it is the only one to remain competitive with [protobuf-net's](http://code.google.com/p/protobuf-net/) very fast implementation of [Protocol Buffers](http://code.google.com/apis/protocolbuffers/) - google's high-speed binary protocol. - -Below is a series of benchmarks serialize the different tables in the [Northwind database](http://code.google.com/p/servicestack/source/browse/trunk/Common/Northwind.Benchmarks/Northwind.Common/DataModel/NorthwindData.cs) (3202 records) with the most popular serializers available for .NET: - -### Combined results for serializing / deserialzing a single row of each table in the Northwind database 1,000,000 times -_[view the detailed benchmarks](http://www.servicestack.net/benchmarks/NorthwindDatabaseRowsSerialization.1000000-times.2010-02-06.html)_ - - - - - - - - - - - - - - - - - -
SerializerSizePeformance
Microsoft DataContractSerializer4.68x6.72x
Microsoft JsonDataContractSerializer2.24x10.18x
Microsoft BinaryFormatter5.62x9.06x
NewtonSoft.Json2.30x8.15x
ProtoBuf.net1x1x
ServiceStack TypeSerializer1.78x1.92x
- -_number of times larger in size and slower in performance than the best - lower is better_ - -Microsoft's JavaScriptSerializer was also benchmarked but excluded as it was up to 280x times slower - basically don't use it, ever. - - -# JSV Text Format (JSON + CSV) - -Type Serializer uses a hybrid CSV-style escaping + JavaScript-like text-based format that is optimized for both size and speed. I'm naming this JSV-format (i.e. JSON + CSV) - -In many ways it is similar to JavaScript, e.g. any List, Array, Collection of ints, longs, etc are stored in exactly the same way, i.e: - [1,2,3,4,5] - -Any IDictionary is serialized like JavaScript, i.e: - {A:1,B:2,C:3,D:4} - -Which also happens to be the same as C# POCO class with the values - -`new MyClass { A=1, B=2, C=3, D=4 }` - - {A:1,B:2,C:3,D:4} - -JSV is *white-space significant*, which means normal string values can be serialized without quotes, e.g: - -`new MyClass { Foo="Bar", Greet="Hello World!"}` is serialized as: - - {Foo:Bar,Greet:Hello World!} - - -### CSV escaping - -Any string with any of the following characters: `[]{},"` -is escaped using CSV-style escaping where the value is wrapped in double quotes, e.g: - -`new MyClass { Name = "Me, Junior" }` is serialized as: - - {Name:"Me, Junior"} - -A value with a double-quote is escaped with another double quote e.g: - -`new MyClass { Size = "2\" x 1\"" }` is serialized as: - - {Size:"2"" x 1"""} - - -## Rich support for resilience and schema versioning -To better illustrate the resilience of `TypeSerializer` and the JSV Format check out a real world example of it when it's used to [Painlessly migrate between old and new types in Redis](https://github.com/ServiceStack/ServiceStack.Redis/wiki/MigrationsUsingSchemalessNoSql). - -Support for dynamic payloads and late-bound objects is explained in the post [Versatility of JSV Late-bound objects](http://www.servicestack.net/mythz_blog/?p=314). +### This repository [has moved](https://docs.servicestack.net/mono-repo) to [github.com/ServiceStack/ServiceStack/ServiceStack.Text](https://github.com/ServiceStack/ServiceStack/tree/main/ServiceStack.Text) diff --git a/build.cmd b/build.cmd deleted file mode 100644 index 2505a1f97..000000000 --- a/build.cmd +++ /dev/null @@ -1,15 +0,0 @@ -@echo off - -set target=%1 -if "%target%" == "" ( - set target=UnitTests -) - -if "%target%" == "NuGetPack" ( - if "%BUILD_NUMBER%" == "" ( - echo BUILD_NUMBER environment variable is not set. - exit; - ) -) - -%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild Build\Build.proj /target:%target% /v:M /fl /flp:LogFile=msbuild.log;Verbosity=Normal /nr:false \ No newline at end of file diff --git a/build/Build.proj b/build/Build.proj deleted file mode 100644 index d349d03f0..000000000 --- a/build/Build.proj +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - 3 - 9 - $(BUILD_NUMBER) - - - - $(MSBuildProjectDirectory)/.. - $(BuildSolutionDir)/src - Release - $(BuildSolutionDir)/src/.nuget/nuget.exe - $(BuildSolutionDir)/NuGet/ - $(MSBuildProjectDirectory) - $(BuildSolutionDir)/NuGet/lib - $(BuildSolutionDir)/NuGet/stackexpress.text.nuspec - $(MajorVersion).$(MinorVersion).$(PatchVersion).0 - -unstable - $(MajorVersion).$(MinorVersion).$(PatchVersion)$(UnstableTag) - $(MajorVersion).$(MinorVersion)$(PatchVersion) - - - - - - - - - - - BeforeBuildSolutions; - BuildSolutions - - - - - - - - - - - - - - - - - - - - - - - - - - \d+\.\d+\.\d+\.\d+ - $(Version) - - - ServiceStackVersion = \d+\.\d+m; - ServiceStackVersion = $(EnvVersion)m; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/NuGet.exe b/build/NuGet.exe deleted file mode 100644 index 4300c9ff9..000000000 Binary files a/build/NuGet.exe and /dev/null differ diff --git a/build/NuGetPack.cmd b/build/NuGetPack.cmd deleted file mode 100644 index f62d3ae85..000000000 --- a/build/NuGetPack.cmd +++ /dev/null @@ -1 +0,0 @@ -nuget pack ..\NuGet\servicestack.text.nuspec -symbols \ No newline at end of file diff --git a/build/build-core.proj b/build/build-core.proj new file mode 100644 index 000000000..707725c77 --- /dev/null +++ b/build/build-core.proj @@ -0,0 +1,87 @@ + + + + + + 6 + 0 + $(BUILD_NUMBER) + + + + $(MSBuildProjectDirectory)/.. + $(BuildSolutionDir)/src + $(BuildSolutionDir)/tests + Release + $(BuildSolutionDir)/NuGet/ + $(MajorVersion).$(MinorVersion).$(PatchVersion) + + + + + BeforeBuildSolutions; + BuildSolutions + + + + + + + + + + + + + + + + + + + + + + + + + + + + ServiceStackVersion = \d+\.\d+m; + ServiceStackVersion = $(MajorVersion).$(MinorVersion)$(PatchVersion)m; + + + + new DateTime.* + new DateTime($([System.DateTime]::Now.ToString(`yyyy,MM,dd`))); + + + + + <Version>[^<]* + <Version>$(PackageVersion) + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/build-deps.proj b/build/build-deps.proj new file mode 100644 index 000000000..38e06c69b --- /dev/null +++ b/build/build-deps.proj @@ -0,0 +1,83 @@ + + + + + + 6 + 0 + $(BUILD_NUMBER) + + + + $(MSBuildProjectDirectory)/.. + $(BuildSolutionDir)/src + $(BuildSolutionDir)/tests + Release + $(BuildSolutionDir)/NuGet/ + $(MajorVersion).$(MinorVersion).$(PatchVersion) + + + + + BeforeBuildSolutions; + BuildSolutions + + + + + + + + + + + + + + + + + + + + + + + + + ServiceStackVersion = \d+\.\d+m; + ServiceStackVersion = $(MajorVersion).$(MinorVersion)$(PatchVersion)m; + + + + new DateTime.* + new DateTime($([System.DateTime]::Now.ToString(`yyyy,MM,dd`))); + + + + + <Version>[^<]* + <Version>$(PackageVersion) + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/build.bat b/build/build.bat index 52796fa06..4b85ae7e7 100644 --- a/build/build.bat +++ b/build/build.bat @@ -1,15 +1,3 @@ +SET MSBUILD="C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe" -REM SET BUILD=Debug -SET BUILD=Release - -COPY ..\src\ServiceStack.Text\bin\%BUILD%\*.* ..\..\ServiceStack\release\latest\ -COPY ..\src\ServiceStack.Text\bin\%BUILD%\*.* ..\..\ServiceStack\release\latest\ServiceStack.Text\ -COPY ..\src\ServiceStack.Text\bin\%BUILD%\*.* ..\..\ServiceStack\lib -COPY ..\src\ServiceStack.Text\bin\%BUILD%\*.* ..\..\ServiceStack.Contrib\lib -COPY ..\src\ServiceStack.Text\bin\%BUILD%\*.* ..\..\ServiceStack.Redis\lib -COPY ..\src\ServiceStack.Text\bin\%BUILD%\*.* ..\..\ServiceStack.OrmLite\lib -COPY ..\src\ServiceStack.Text\bin\%BUILD%\*.* ..\..\ServiceStack.Examples\lib -COPY ..\src\ServiceStack.Text\bin\%BUILD%\*.* ..\..\ServiceStack.RedisWebServices\lib - -MD ..\NuGet\lib\net35 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\*.* ..\NuGet\lib\net35 +%MSBUILD% build.proj /p:Configuration=Release /p:MinorVersion=12 /p:PatchVersion=1 diff --git a/build/build.proj b/build/build.proj new file mode 100644 index 000000000..7d2eae117 --- /dev/null +++ b/build/build.proj @@ -0,0 +1,92 @@ + + + + + + 6 + 0 + $(BUILD_NUMBER) + + + + $(MSBuildProjectDirectory)/.. + $(BuildSolutionDir)/src + $(BuildSolutionDir)/tests + Release + $(BuildSolutionDir)/NuGet/ + $(MajorVersion).$(MinorVersion).$(PatchVersion) + + + + + BeforeBuildSolutions; + BuildSolutions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ServiceStackVersion = \d+\.\d+m; + ServiceStackVersion = $(MajorVersion).$(MinorVersion)$(PatchVersion)m; + + + + new DateTime.* + new DateTime($([System.DateTime]::Now.ToString(`yyyy,MM,dd`))); + + + + + <Version>[^<]* + <Version>$(PackageVersion) + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/Build.tasks b/build/build.tasks similarity index 100% rename from build/Build.tasks rename to build/build.tasks diff --git a/lib/tests/Northwind.Common.dll b/lib/tests/Northwind.Common.dll deleted file mode 100644 index 7e8fb46be..000000000 Binary files a/lib/tests/Northwind.Common.dll and /dev/null differ diff --git a/lib/tests/Northwind.Common.pdb b/lib/tests/Northwind.Common.pdb deleted file mode 100644 index dfc5120ce..000000000 Binary files a/lib/tests/Northwind.Common.pdb and /dev/null differ diff --git a/lib/tests/Platform.dll b/lib/tests/Platform.dll deleted file mode 100644 index 9261e75ff..000000000 Binary files a/lib/tests/Platform.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.Client.dll b/lib/tests/ServiceStack.Client.dll deleted file mode 100644 index 0d5788ff9..000000000 Binary files a/lib/tests/ServiceStack.Client.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.Client.pdb b/lib/tests/ServiceStack.Client.pdb deleted file mode 100644 index b0b6b8169..000000000 Binary files a/lib/tests/ServiceStack.Client.pdb and /dev/null differ diff --git a/lib/tests/ServiceStack.Common.Tests.dll b/lib/tests/ServiceStack.Common.Tests.dll deleted file mode 100644 index b96dd60e9..000000000 Binary files a/lib/tests/ServiceStack.Common.Tests.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.Common.Tests.pdb b/lib/tests/ServiceStack.Common.Tests.pdb deleted file mode 100644 index 178adffa3..000000000 Binary files a/lib/tests/ServiceStack.Common.Tests.pdb and /dev/null differ diff --git a/lib/tests/ServiceStack.Common.dll b/lib/tests/ServiceStack.Common.dll deleted file mode 100644 index fc7fe2667..000000000 Binary files a/lib/tests/ServiceStack.Common.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.Common.pdb b/lib/tests/ServiceStack.Common.pdb deleted file mode 100644 index 5dbff54d7..000000000 Binary files a/lib/tests/ServiceStack.Common.pdb and /dev/null differ diff --git a/lib/tests/ServiceStack.Interfaces.dll b/lib/tests/ServiceStack.Interfaces.dll deleted file mode 100644 index 8fe3b9fac..000000000 Binary files a/lib/tests/ServiceStack.Interfaces.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.Messaging.Tests.dll b/lib/tests/ServiceStack.Messaging.Tests.dll deleted file mode 100644 index 5a045af8b..000000000 Binary files a/lib/tests/ServiceStack.Messaging.Tests.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.Messaging.Tests.pdb b/lib/tests/ServiceStack.Messaging.Tests.pdb deleted file mode 100644 index 0872d15f0..000000000 Binary files a/lib/tests/ServiceStack.Messaging.Tests.pdb and /dev/null differ diff --git a/lib/tests/ServiceStack.OrmLite.SqlServer.dll b/lib/tests/ServiceStack.OrmLite.SqlServer.dll deleted file mode 100644 index 6b6d1235d..000000000 Binary files a/lib/tests/ServiceStack.OrmLite.SqlServer.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.OrmLite.dll b/lib/tests/ServiceStack.OrmLite.dll deleted file mode 100644 index b493b2d41..000000000 Binary files a/lib/tests/ServiceStack.OrmLite.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.Redis.dll b/lib/tests/ServiceStack.Redis.dll deleted file mode 100644 index c00019f8b..000000000 Binary files a/lib/tests/ServiceStack.Redis.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.ServiceInterface.dll b/lib/tests/ServiceStack.ServiceInterface.dll deleted file mode 100644 index 0e703d2d0..000000000 Binary files a/lib/tests/ServiceStack.ServiceInterface.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.ServiceModel.dll b/lib/tests/ServiceStack.ServiceModel.dll deleted file mode 100644 index 658990c7f..000000000 Binary files a/lib/tests/ServiceStack.ServiceModel.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.ServiceModel.pdb b/lib/tests/ServiceStack.ServiceModel.pdb deleted file mode 100644 index 7fb55d545..000000000 Binary files a/lib/tests/ServiceStack.ServiceModel.pdb and /dev/null differ diff --git a/lib/tests/ServiceStack.Text.dll b/lib/tests/ServiceStack.Text.dll deleted file mode 100644 index fc9da1c19..000000000 Binary files a/lib/tests/ServiceStack.Text.dll and /dev/null differ diff --git a/lib/tests/ServiceStack.dll b/lib/tests/ServiceStack.dll deleted file mode 100644 index 01f7230c2..000000000 Binary files a/lib/tests/ServiceStack.dll and /dev/null differ diff --git a/lib/tests/nunit-console/NUnitFitTests.html b/lib/tests/nunit-console/NUnitFitTests.html deleted file mode 100644 index ca5cd4ff7..000000000 --- a/lib/tests/nunit-console/NUnitFitTests.html +++ /dev/null @@ -1,277 +0,0 @@ - - - -

NUnit Acceptance Tests

-

- Developers love self-referential programs! Hence, NUnit has always run all it's - own tests, even those that are not really unit tests. -

Now, beginning with NUnit 2.4, NUnit has top-level tests using Ward Cunningham's - FIT framework. At this time, the tests are pretty rudimentary, but it's a start - and it's a framework for doing more. -

Running the Tests

-

Open a console or shell window and navigate to the NUnit bin directory, which - contains this file. To run the test under Microsoft .Net, enter the command -

    runFile NUnitFitTests.html TestResults.html .
- To run it under Mono, enter -
    mono runFile.exe NUnitFitTests.html TestResults.html .
- Note the space and dot at the end of each command. The results of your test - will be in TestResults.html in the same directory. -

Platform and CLR Version

- - - - -
NUnit.Fixtures.PlatformInfo
-

Verify Unit Tests

-

- Load and run the NUnit unit tests, verifying that the results are as expected. - When these tests are run on different platforms, different numbers of tests may - be skipped, so the values for Skipped and Run tests are informational only. -

- The number of tests in each assembly should be constant across all platforms - - any discrepancy usually means that one of the test source files was not - compiled on the platform. There should be no failures and no tests ignored. -

Note: - At the moment, the nunit.extensions.tests assembly is failing because the - fixture doesn't initialize addins in the test domain. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NUnit.Fixtures.AssemblyRunner
AssemblyTests()Run()Skipped()Ignored()Failures()
nunit.framework.tests.dll397  00
nunit.core.tests.dll355  00
nunit.util.tests.dll238  00
nunit.mocks.tests.dll43  00
nunit.extensions.tests.dll5  00
nunit-console.tests.dll40  00
nunit.uikit.tests.dll34  00
nunit-gui.tests.dll15  00
nunit.fixtures.tests.dll6  00
-

Code Snippet Tests

-

- These tests create a test assembly from a snippet of code and then load and run - the tests that it contains, verifying that the structure of the loaded tests is - as expected and that the number of tests run, skipped, ignored or failed is - correct. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NUnit.Fixtures.SnippetRunner
CodeTree()Run()Skipped()Ignored()Failures()
public class TestClass
-{
-}
-
EMPTY0000
using NUnit.Framework;
-
-[TestFixture]
-public class TestClass
-{
-}
-
TestClass0000
using NUnit.Framework;
-
-[TestFixture]
-public class TestClass
-{
-    [Test]
-    public void T1() { }
-    [Test]
-    public void T2() { }
-    [Test]
-    public void T3() { }
-}
-
TestClass
->T1
->T2
->T3
-
3000
using NUnit.Framework;
-
-[TestFixture]
-public class TestClass1
-{
-    [Test]
-    public void T1() { }
-}
-
-[TestFixture]
-public class TestClass2
-{
-    [Test]
-    public void T2() { }
-    [Test]
-    public void T3() { }
-}
-
TestClass1
->T1
-TestClass2
->T2
->T3
-
3000
using NUnit.Framework;
-
-[TestFixture]
-public class TestClass
-{
-    [Test]
-    public void T1() { }
-    [Test, Ignore]
-    public void T2() { }
-    [Test]
-    public void T3() { }
-}
-
TestClass
->T1
->T2
->T3
-
2010
using NUnit.Framework;
-
-[TestFixture]
-public class TestClass
-{
-    [Test]
-    public void T1() { }
-    [Test, Explicit]
-    public void T2() { }
-    [Test]
-    public void T3() { }
-}
-
TestClass
->T1
->T2
->T3
-
2100
-

Summary Information

- - - - -
fit.Summary
- - diff --git a/lib/tests/nunit-console/NUnitTests.VisualState.xml b/lib/tests/nunit-console/NUnitTests.VisualState.xml deleted file mode 100644 index 83cb44ace..000000000 --- a/lib/tests/nunit-console/NUnitTests.VisualState.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - [0-1000]D:\Dev\NUnit\nunit-2.6\work\builds\net\3.5\release\NUnitTests.nunit - [0-1000]D:\Dev\NUnit\nunit-2.6\work\builds\net\3.5\release\NUnitTests.nunit - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit-console/NUnitTests.config b/lib/tests/nunit-console/NUnitTests.config deleted file mode 100644 index 72ed67c79..000000000 --- a/lib/tests/nunit-console/NUnitTests.config +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/tests/nunit-console/NUnitTests.nunit b/lib/tests/nunit-console/NUnitTests.nunit deleted file mode 100644 index 15b98d3f0..000000000 --- a/lib/tests/nunit-console/NUnitTests.nunit +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/lib/tests/nunit-console/agent.conf b/lib/tests/nunit-console/agent.conf deleted file mode 100644 index ddbcd8ea6..000000000 --- a/lib/tests/nunit-console/agent.conf +++ /dev/null @@ -1,4 +0,0 @@ - - 8080 - . - \ No newline at end of file diff --git a/lib/tests/nunit-console/agent.log.conf b/lib/tests/nunit-console/agent.log.conf deleted file mode 100644 index b5bcd9da4..000000000 --- a/lib/tests/nunit-console/agent.log.conf +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/lib/tests/nunit-console/launcher.log.conf b/lib/tests/nunit-console/launcher.log.conf deleted file mode 100644 index b5bcd9da4..000000000 --- a/lib/tests/nunit-console/launcher.log.conf +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/lib/tests/nunit-console/nunit-agent-x86.exe.config b/lib/tests/nunit-console/nunit-agent-x86.exe.config deleted file mode 100644 index de2caf60f..000000000 --- a/lib/tests/nunit-console/nunit-agent-x86.exe.config +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit-console/nunit-agent.exe.config b/lib/tests/nunit-console/nunit-agent.exe.config deleted file mode 100644 index de2caf60f..000000000 --- a/lib/tests/nunit-console/nunit-agent.exe.config +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit-console/nunit-console-runner.dll b/lib/tests/nunit-console/nunit-console-runner.dll deleted file mode 100644 index e637d6523..000000000 Binary files a/lib/tests/nunit-console/nunit-console-runner.dll and /dev/null differ diff --git a/lib/tests/nunit-console/nunit-console-x86.exe.config b/lib/tests/nunit-console/nunit-console-x86.exe.config deleted file mode 100644 index 8a6a2a6a6..000000000 --- a/lib/tests/nunit-console/nunit-console-x86.exe.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit-console/nunit-console.exe.config b/lib/tests/nunit-console/nunit-console.exe.config deleted file mode 100644 index 8a6a2a6a6..000000000 --- a/lib/tests/nunit-console/nunit-console.exe.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit-console/nunit-x86.exe.config b/lib/tests/nunit-console/nunit-x86.exe.config deleted file mode 100644 index e3c06b7fe..000000000 --- a/lib/tests/nunit-console/nunit-x86.exe.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit-console/nunit.core.dll b/lib/tests/nunit-console/nunit.core.dll deleted file mode 100644 index 1c5778255..000000000 Binary files a/lib/tests/nunit-console/nunit.core.dll and /dev/null differ diff --git a/lib/tests/nunit-console/nunit.core.interfaces.dll b/lib/tests/nunit-console/nunit.core.interfaces.dll deleted file mode 100644 index 941d4931d..000000000 Binary files a/lib/tests/nunit-console/nunit.core.interfaces.dll and /dev/null differ diff --git a/lib/tests/nunit-console/nunit.exe.config b/lib/tests/nunit-console/nunit.exe.config deleted file mode 100644 index e3c06b7fe..000000000 --- a/lib/tests/nunit-console/nunit.exe.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit-console/nunit.framework.dll b/lib/tests/nunit-console/nunit.framework.dll deleted file mode 100644 index 215767d2f..000000000 Binary files a/lib/tests/nunit-console/nunit.framework.dll and /dev/null differ diff --git a/lib/tests/nunit-console/nunit.framework.extensions.dll b/lib/tests/nunit-console/nunit.framework.extensions.dll deleted file mode 100644 index 91e05857a..000000000 Binary files a/lib/tests/nunit-console/nunit.framework.extensions.dll and /dev/null differ diff --git a/lib/tests/nunit-console/nunit.framework.xml b/lib/tests/nunit-console/nunit.framework.xml deleted file mode 100644 index 7b0e798ca..000000000 --- a/lib/tests/nunit-console/nunit.framework.xml +++ /dev/null @@ -1,10892 +0,0 @@ - - - - nunit.framework - - - - - Attribute used to apply a category to a test - - - - - The name of the category - - - - - Construct attribute for a given category based on - a name. The name may not contain the characters ',', - '+', '-' or '!'. However, this is not checked in the - constructor since it would cause an error to arise at - as the test was loaded without giving a clear indication - of where the problem is located. The error is handled - in NUnitFramework.cs by marking the test as not - runnable. - - The name of the category - - - - Protected constructor uses the Type name as the name - of the category. - - - - - The name of the category - - - - - Used to mark a field for use as a datapoint when executing a theory - within the same fixture that requires an argument of the field's Type. - - - - - Used to mark an array as containing a set of datapoints to be used - executing a theory within the same fixture that requires an argument - of the Type of the array elements. - - - - - Attribute used to provide descriptive text about a - test case or fixture. - - - - - Construct the attribute - - Text describing the test - - - - Gets the test description - - - - - Enumeration indicating how the expected message parameter is to be used - - - - Expect an exact match - - - Expect a message containing the parameter string - - - Match the regular expression provided as a parameter - - - Expect a message that starts with the parameter string - - - - ExpectedExceptionAttribute - - - - - - Constructor for a non-specific exception - - - - - Constructor for a given type of exception - - The type of the expected exception - - - - Constructor for a given exception name - - The full name of the expected exception - - - - Gets or sets the expected exception type - - - - - Gets or sets the full Type name of the expected exception - - - - - Gets or sets the expected message text - - - - - Gets or sets the user message displayed in case of failure - - - - - Gets or sets the type of match to be performed on the expected message - - - - - Gets the name of a method to be used as an exception handler - - - - - ExplicitAttribute marks a test or test fixture so that it will - only be run if explicitly executed from the gui or command line - or if it is included by use of a filter. The test will not be - run simply because an enclosing suite is run. - - - - - Default constructor - - - - - Constructor with a reason - - The reason test is marked explicit - - - - The reason test is marked explicit - - - - - Attribute used to mark a test that is to be ignored. - Ignored tests result in a warning message when the - tests are run. - - - - - Constructs the attribute without giving a reason - for ignoring the test. - - - - - Constructs the attribute giving a reason for ignoring the test - - The reason for ignoring the test - - - - The reason for ignoring a test - - - - - Abstract base for Attributes that are used to include tests - in the test run based on environmental settings. - - - - - Constructor with no included items specified, for use - with named property syntax. - - - - - Constructor taking one or more included items - - Comma-delimited list of included items - - - - Name of the item that is needed in order for - a test to run. Multiple itemss may be given, - separated by a comma. - - - - - Name of the item to be excluded. Multiple items - may be given, separated by a comma. - - - - - The reason for including or excluding the test - - - - - PlatformAttribute is used to mark a test fixture or an - individual method as applying to a particular platform only. - - - - - Constructor with no platforms specified, for use - with named property syntax. - - - - - Constructor taking one or more platforms - - Comma-deliminted list of platforms - - - - CultureAttribute is used to mark a test fixture or an - individual method as applying to a particular Culture only. - - - - - Constructor with no cultures specified, for use - with named property syntax. - - - - - Constructor taking one or more cultures - - Comma-deliminted list of cultures - - - - Marks a test to use a combinatorial join of any argument data - provided. NUnit will create a test case for every combination of - the arguments provided. This can result in a large number of test - cases and so should be used judiciously. This is the default join - type, so the attribute need not be used except as documentation. - - - - - PropertyAttribute is used to attach information to a test as a name/value pair.. - - - - - Construct a PropertyAttribute with a name and string value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and int value - - The name of the property - The property value - - - - Construct a PropertyAttribute with a name and double value - - The name of the property - The property value - - - - Constructor for derived classes that set the - property dictionary directly. - - - - - Constructor for use by derived classes that use the - name of the type as the property name. Derived classes - must ensure that the Type of the property value is - a standard type supported by the BCL. Any custom - types will cause a serialization Exception when - in the client. - - - - - Gets the property dictionary for this attribute - - - - - Default constructor - - - - - Marks a test to use pairwise join of any argument data provided. - NUnit will attempt too excercise every pair of argument values at - least once, using as small a number of test cases as it can. With - only two arguments, this is the same as a combinatorial join. - - - - - Default constructor - - - - - Marks a test to use a sequential join of any argument data - provided. NUnit will use arguements for each parameter in - sequence, generating test cases up to the largest number - of argument values provided and using null for any arguments - for which it runs out of values. Normally, this should be - used with the same number of arguments for each parameter. - - - - - Default constructor - - - - - Summary description for MaxTimeAttribute. - - - - - Construct a MaxTimeAttribute, given a time in milliseconds. - - The maximum elapsed time in milliseconds - - - - RandomAttribute is used to supply a set of random values - to a single parameter of a parameterized test. - - - - - ValuesAttribute is used to provide literal arguments for - an individual parameter of a test. - - - - - Abstract base class for attributes that apply to parameters - and supply data for the parameter. - - - - - Gets the data to be provided to the specified parameter - - - - - The collection of data to be returned. Must - be set by any derived attribute classes. - We use an object[] so that the individual - elements may have their type changed in GetData - if necessary. - - - - - Construct with one argument - - - - - - Construct with two arguments - - - - - - - Construct with three arguments - - - - - - - - Construct with an array of arguments - - - - - - Get the collection of values to be used as arguments - - - - - Construct a set of doubles from 0.0 to 1.0, - specifying only the count. - - - - - - Construct a set of doubles from min to max - - - - - - - - Construct a set of ints from min to max - - - - - - - - Get the collection of values to be used as arguments - - - - - RangeAttribute is used to supply a range of values to an - individual parameter of a parameterized test. - - - - - Construct a range of ints using default step of 1 - - - - - - - Construct a range of ints specifying the step size - - - - - - - - Construct a range of longs - - - - - - - - Construct a range of doubles - - - - - - - - Construct a range of floats - - - - - - - - RepeatAttribute may be applied to test case in order - to run it multiple times. - - - - - Construct a RepeatAttribute - - The number of times to run the test - - - - RequiredAddinAttribute may be used to indicate the names of any addins - that must be present in order to run some or all of the tests in an - assembly. If the addin is not loaded, the entire assembly is marked - as NotRunnable. - - - - - Initializes a new instance of the class. - - The required addin. - - - - Gets the name of required addin. - - The required addin name. - - - - Summary description for SetCultureAttribute. - - - - - Construct given the name of a culture - - - - - - Summary description for SetUICultureAttribute. - - - - - Construct given the name of a culture - - - - - - SetUpAttribute is used in a TestFixture to identify a method - that is called immediately before each test is run. It is - also used in a SetUpFixture to identify the method that is - called once, before any of the subordinate tests are run. - - - - - Attribute used to mark a class that contains one-time SetUp - and/or TearDown methods that apply to all the tests in a - namespace or an assembly. - - - - - Attribute used to mark a static (shared in VB) property - that returns a list of tests. - - - - - Attribute used in a TestFixture to identify a method that is - called immediately after each test is run. It is also used - in a SetUpFixture to identify the method that is called once, - after all subordinate tests have run. In either case, the method - is guaranteed to be called, even if an exception is thrown. - - - - - Provide actions to execute before and after tests. - - - - - When implemented by an attribute, this interface implemented to provide actions to execute before and after tests. - - - - - Executed before each test is run - - Provides details about the test that is going to be run. - - - - Executed after each test is run - - Provides details about the test that has just been run. - - - - Provides the target for the action attribute - - The target for the action attribute - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - publc void TestDescriptionMethod() - {} - } - - - - - - Descriptive text for this test - - - - - TestCaseAttribute is used to mark parameterized test cases - and provide them with their arguments. - - - - - The ITestCaseData interface is implemented by a class - that is able to return complete testcases for use by - a parameterized test method. - - NOTE: This interface is used in both the framework - and the core, even though that results in two different - types. However, sharing the source code guarantees that - the various implementations will be compatible and that - the core is able to reflect successfully over the - framework implementations of ITestCaseData. - - - - - Gets the argument list to be provided to the test - - - - - Gets the expected result - - - - - Indicates whether a result has been specified. - This is necessary because the result may be - null, so it's value cannot be checked. - - - - - Gets the expected exception Type - - - - - Gets the FullName of the expected exception - - - - - Gets the name to be used for the test - - - - - Gets the description of the test - - - - - Gets a value indicating whether this is ignored. - - true if ignored; otherwise, false. - - - - Gets a value indicating whether this is explicit. - - true if explicit; otherwise, false. - - - - Gets the ignore reason. - - The ignore reason. - - - - Construct a TestCaseAttribute with a list of arguments. - This constructor is not CLS-Compliant - - - - - - Construct a TestCaseAttribute with a single argument - - - - - - Construct a TestCaseAttribute with a two arguments - - - - - - - Construct a TestCaseAttribute with a three arguments - - - - - - - - Gets the list of arguments to a test case - - - - - Gets or sets the expected result. - - The result. - - - - Gets the expected result. - - The result. - - - - Gets a flag indicating whether an expected - result has been set. - - - - - Gets a list of categories associated with this test; - - - - - Gets or sets the category associated with this test. - May be a single category or a comma-separated list. - - - - - Gets or sets the expected exception. - - The expected exception. - - - - Gets or sets the name the expected exception. - - The expected name of the exception. - - - - Gets or sets the expected message of the expected exception - - The expected message of the exception. - - - - Gets or sets the type of match to be performed on the expected message - - - - - Gets or sets the description. - - The description. - - - - Gets or sets the name of the test. - - The name of the test. - - - - Gets or sets the ignored status of the test - - - - - Gets or sets the ignored status of the test - - - - - Gets or sets the explicit status of the test - - - - - Gets or sets the reason for not running the test - - - - - Gets or sets the reason for not running the test. - Set has the side effect of marking the test as ignored. - - The ignore reason. - - - - FactoryAttribute indicates the source to be used to - provide test cases for a test method. - - - - - Construct with the name of the factory - for use with languages - that don't support params arrays. - - An array of the names of the factories that will provide data - - - - Construct with a Type and name - for use with languages - that don't support params arrays. - - The Type that will provide data - The name of the method, property or field that will provide data - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - Gets or sets the category associated with this test. - May be a single category or a comma-separated list. - - - - - [TestFixture] - public class ExampleClass - {} - - - - - Default constructor - - - - - Construct with a object[] representing a set of arguments. - In .NET 2.0, the arguments may later be separated into - type arguments and constructor arguments. - - - - - - Descriptive text for this fixture - - - - - Gets and sets the category for this fixture. - May be a comma-separated list of categories. - - - - - Gets a list of categories for this fixture - - - - - The arguments originally provided to the attribute - - - - - Gets or sets a value indicating whether this should be ignored. - - true if ignore; otherwise, false. - - - - Gets or sets the ignore reason. May set Ignored as a side effect. - - The ignore reason. - - - - Get or set the type arguments. If not set - explicitly, any leading arguments that are - Types are taken as type arguments. - - - - - Attribute used to identify a method that is - called before any tests in a fixture are run. - - - - - Attribute used to identify a method that is called after - all the tests in a fixture have run. The method is - guaranteed to be called, even if an exception is thrown. - - - - - Adding this attribute to a method within a - class makes the method callable from the NUnit test runner. There is a property - called Description which is optional which you can provide a more detailed test - description. This class cannot be inherited. - - - - [TestFixture] - public class Fixture - { - [Test] - public void MethodToTest() - {} - - [Test(Description = "more detailed description")] - publc void TestDescriptionMethod() - {} - } - - - - - - Used on a method, marks the test with a timeout value in milliseconds. - The test will be run in a separate thread and is cancelled if the timeout - is exceeded. Used on a method or assembly, sets the default timeout - for all contained test methods. - - - - - Construct a TimeoutAttribute given a time in milliseconds - - The timeout value in milliseconds - - - - Marks a test that must run in the STA, causing it - to run in a separate thread if necessary. - - On methods, you may also use STAThreadAttribute - to serve the same purpose. - - - - - Construct a RequiresSTAAttribute - - - - - Marks a test that must run in the MTA, causing it - to run in a separate thread if necessary. - - On methods, you may also use MTAThreadAttribute - to serve the same purpose. - - - - - Construct a RequiresMTAAttribute - - - - - Marks a test that must run on a separate thread. - - - - - Construct a RequiresThreadAttribute - - - - - Construct a RequiresThreadAttribute, specifying the apartment - - - - - ValueSourceAttribute indicates the source to be used to - provide data for one parameter of a test method. - - - - - Construct with the name of the factory - for use with languages - that don't support params arrays. - - The name of the data source to be used - - - - Construct with a Type and name - for use with languages - that don't support params arrays. - - The Type that will provide data - The name of the method, property or field that will provide data - - - - The name of a the method, property or fiend to be used as a source - - - - - A Type to be used as a source - - - - - AttributeExistsConstraint tests for the presence of a - specified attribute on a Type. - - - - - The Constraint class is the base of all built-in constraints - within NUnit. It provides the operator overloads used to combine - constraints. - - - - - The IConstraintExpression interface is implemented by all - complete and resolvable constraints and expressions. - - - - - Return the top-level constraint for this expression - - - - - - Static UnsetObject used to detect derived constraints - failing to set the actual value. - - - - - The actual value being tested against a constraint - - - - - The display name of this Constraint for use by ToString() - - - - - Argument fields used by ToString(); - - - - - The builder holding this constraint - - - - - Construct a constraint with no arguments - - - - - Construct a constraint with one argument - - - - - Construct a constraint with two arguments - - - - - Sets the ConstraintBuilder holding this constraint - - - - - Write the failure message to the MessageWriter provided - as an argument. The default implementation simply passes - the constraint and the actual value to the writer, which - then displays the constraint description and the value. - - Constraints that need to provide additional details, - such as where the error occured can override this. - - The MessageWriter on which to display the message - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Test whether the constraint is satisfied by an - ActualValueDelegate that returns the value to be tested. - The default implementation simply evaluates the delegate - but derived classes may override it to provide for delayed - processing. - - An ActualValueDelegate - True for success, false for failure - - - - Test whether the constraint is satisfied by a given reference. - The default implementation simply dereferences the value but - derived classes may override it to provide for delayed processing. - - A reference to the value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - Default override of ToString returns the constraint DisplayName - followed by any arguments within angle brackets. - - - - - - Returns the string representation of this constraint - - - - - This operator creates a constraint that is satisfied only if both - argument constraints are satisfied. - - - - - This operator creates a constraint that is satisfied if either - of the argument constraints is satisfied. - - - - - This operator creates a constraint that is satisfied if the - argument constraint is not satisfied. - - - - - Returns a DelayedConstraint with the specified delay time. - - The delay in milliseconds. - - - - - Returns a DelayedConstraint with the specified delay time - and polling interval. - - The delay in milliseconds. - The interval at which to test the constraint. - - - - - The display name of this Constraint for use by ToString(). - The default value is the name of the constraint with - trailing "Constraint" removed. Derived classes may set - this to another name in their constructors. - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending And - to the current constraint. - - - - - Returns a ConstraintExpression by appending Or - to the current constraint. - - - - - Class used to detect any derived constraints - that fail to set the actual value in their - Matches override. - - - - - Constructs an AttributeExistsConstraint for a specific attribute Type - - - - - - Tests whether the object provides the expected attribute. - - A Type, MethodInfo, or other ICustomAttributeProvider - True if the expected attribute is present, otherwise false - - - - Writes the description of the constraint to the specified writer - - - - - AttributeConstraint tests that a specified attribute is present - on a Type or other provider and that the value of the attribute - satisfies some other constraint. - - - - - Abstract base class used for prefixes - - - - - The base constraint - - - - - Construct given a base constraint - - - - - - Constructs an AttributeConstraint for a specified attriute - Type and base constraint. - - - - - - - Determines whether the Type or other provider has the - expected attribute and if its value matches the - additional constraint specified. - - - - - Writes a description of the attribute to the specified writer. - - - - - Writes the actual value supplied to the specified writer. - - - - - Returns a string representation of the constraint. - - - - - BasicConstraint is the abstract base for constraints that - perform a simple comparison to a constant value. - - - - - Initializes a new instance of the class. - - The expected. - The description. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - NullConstraint tests that the actual value is null - - - - - Initializes a new instance of the class. - - - - - TrueConstraint tests that the actual value is true - - - - - Initializes a new instance of the class. - - - - - FalseConstraint tests that the actual value is false - - - - - Initializes a new instance of the class. - - - - - NaNConstraint tests that the actual value is a double or float NaN - - - - - Test that the actual value is an NaN - - - - - - - Write the constraint description to a specified writer - - - - - - BinaryConstraint is the abstract base of all constraints - that combine two other constraints in some fashion. - - - - - The first constraint being combined - - - - - The second constraint being combined - - - - - Construct a BinaryConstraint from two other constraints - - The first constraint - The second constraint - - - - AndConstraint succeeds only if both members succeed. - - - - - Create an AndConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply both member constraints to an actual value, succeeding - succeeding only if both of them succeed. - - The actual value - True if the constraints both succeeded - - - - Write a description for this contraint to a MessageWriter - - The MessageWriter to receive the description - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - OrConstraint succeeds if either member succeeds - - - - - Create an OrConstraint from two other constraints - - The first constraint - The second constraint - - - - Apply the member constraints to an actual value, succeeding - succeeding as soon as one of them succeeds. - - The actual value - True if either constraint succeeded - - - - Write a description for this contraint to a MessageWriter - - The MessageWriter to receive the description - - - - CollectionConstraint is the abstract base class for - constraints that operate on collections. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Determines whether the specified enumerable is empty. - - The enumerable. - - true if the specified enumerable is empty; otherwise, false. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Protected method to be implemented by derived classes - - - - - - - CollectionItemsEqualConstraint is the abstract base class for all - collection constraints that apply some notion of item equality - as a part of their operation. - - - - - Construct an empty CollectionConstraint - - - - - Construct a CollectionConstraint - - - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Compares two collection members for equality - - - - - Return a new CollectionTally for use in making tests - - The collection to be included in the tally - - - - Flag the constraint to ignore case and return self. - - - - - EmptyCollectionConstraint tests whether a collection is empty. - - - - - Check that the collection is empty - - - - - - - Write the constraint description to a MessageWriter - - - - - - UniqueItemsConstraint tests whether all the items in a - collection are unique. - - - - - Check that all items are unique. - - - - - - - Write a description of this constraint to a MessageWriter - - - - - - CollectionContainsConstraint is used to test whether a collection - contains an expected object as a member. - - - - - Construct a CollectionContainsConstraint - - - - - - Test whether the expected item is contained in the collection - - - - - - - Write a descripton of the constraint to a MessageWriter - - - - - - CollectionEquivalentCOnstraint is used to determine whether two - collections are equivalent. - - - - - Construct a CollectionEquivalentConstraint - - - - - - Test whether two collections are equivalent - - - - - - - Write a description of this constraint to a MessageWriter - - - - - - CollectionSubsetConstraint is used to determine whether - one collection is a subset of another - - - - - Construct a CollectionSubsetConstraint - - The collection that the actual value is expected to be a subset of - - - - Test whether the actual collection is a subset of - the expected collection provided. - - - - - - - Write a description of this constraint to a MessageWriter - - - - - - CollectionOrderedConstraint is used to test whether a collection is ordered. - - - - - Construct a CollectionOrderedConstraint - - - - - Modifies the constraint to use an IComparer and returns self. - - - - - Modifies the constraint to use an IComparer<T> and returns self. - - - - - Modifies the constraint to use a Comparison<T> and returns self. - - - - - Modifies the constraint to test ordering by the value of - a specified property and returns self. - - - - - Test whether the collection is ordered - - - - - - - Write a description of the constraint to a MessageWriter - - - - - - Returns the string representation of the constraint. - - - - - - If used performs a reverse comparison - - - - - CollectionTally counts (tallies) the number of - occurences of each object in one or more enumerations. - - - - - Construct a CollectionTally object from a comparer and a collection - - - - - Try to remove an object from the tally - - The object to remove - True if successful, false if the object was not found - - - - Try to remove a set of objects from the tally - - The objects to remove - True if successful, false if any object was not found - - - - The number of objects remaining in the tally - - - - - ComparisonAdapter class centralizes all comparisons of - values in NUnit, adapting to the use of any provided - IComparer, IComparer<T> or Comparison<T> - - - - - Returns a ComparisonAdapter that wraps an IComparer - - - - - Returns a ComparisonAdapter that wraps an IComparer<T> - - - - - Returns a ComparisonAdapter that wraps a Comparison<T> - - - - - Compares two objects - - - - - Gets the default ComparisonAdapter, which wraps an - NUnitComparer object. - - - - - Construct a ComparisonAdapter for an IComparer - - - - - Compares two objects - - - - - - - - Construct a default ComparisonAdapter - - - - - ComparisonAdapter<T> extends ComparisonAdapter and - allows use of an IComparer<T> or Comparison<T> - to actually perform the comparison. - - - - - Construct a ComparisonAdapter for an IComparer<T> - - - - - Compare a Type T to an object - - - - - Construct a ComparisonAdapter for a Comparison<T> - - - - - Compare a Type T to an object - - - - - Abstract base class for constraints that compare values to - determine if one is greater than, equal to or less than - the other. This class supplies the Using modifiers. - - - - - ComparisonAdapter to be used in making the comparison - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - - - - Modifies the constraint to use an IComparer and returns self - - - - - Modifies the constraint to use an IComparer<T> and returns self - - - - - Modifies the constraint to use a Comparison<T> and returns self - - - - - Delegate used to delay evaluation of the actual value - to be used in evaluating a constraint - - - - - ConstraintBuilder maintains the stacks that are used in - processing a ConstraintExpression. An OperatorStack - is used to hold operators that are waiting for their - operands to be reognized. a ConstraintStack holds - input constraints as well as the results of each - operator applied. - - - - - Initializes a new instance of the class. - - - - - Appends the specified operator to the expression by first - reducing the operator stack and then pushing the new - operator on the stack. - - The operator to push. - - - - Appends the specified constraint to the expresson by pushing - it on the constraint stack. - - The constraint to push. - - - - Sets the top operator right context. - - The right context. - - - - Reduces the operator stack until the topmost item - precedence is greater than or equal to the target precedence. - - The target precedence. - - - - Resolves this instance, returning a Constraint. If the builder - is not currently in a resolvable state, an exception is thrown. - - The resolved constraint - - - - Gets a value indicating whether this instance is resolvable. - - - true if this instance is resolvable; otherwise, false. - - - - - OperatorStack is a type-safe stack for holding ConstraintOperators - - - - - Initializes a new instance of the class. - - The builder. - - - - Pushes the specified operator onto the stack. - - The op. - - - - Pops the topmost operator from the stack. - - - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - Gets the topmost operator without modifying the stack. - - The top. - - - - ConstraintStack is a type-safe stack for holding Constraints - - - - - Initializes a new instance of the class. - - The builder. - - - - Pushes the specified constraint. As a side effect, - the constraint's builder field is set to the - ConstraintBuilder owning this stack. - - The constraint. - - - - Pops this topmost constrait from the stack. - As a side effect, the constraint's builder - field is set to null. - - - - - - Gets a value indicating whether this is empty. - - true if empty; otherwise, false. - - - - Gets the topmost constraint without modifying the stack. - - The topmost constraint - - - - ConstraintExpression represents a compound constraint in the - process of being constructed from a series of syntactic elements. - - Individual elements are appended to the expression as they are - reognized. Once an actual Constraint is appended, the expression - returns a resolvable Constraint. - - - - - ConstraintExpressionBase is the abstract base class for the - ConstraintExpression class, which represents a - compound constraint in the process of being constructed - from a series of syntactic elements. - - NOTE: ConstraintExpressionBase is separate because the - ConstraintExpression class was generated in earlier - versions of NUnit. The two classes may be combined - in a future version. - - - - - The ConstraintBuilder holding the elements recognized so far - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class passing in a ConstraintBuilder, which may be pre-populated. - - The builder. - - - - Returns a string representation of the expression as it - currently stands. This should only be used for testing, - since it has the side-effect of resolving the expression. - - - - - - Appends an operator to the expression and returns the - resulting expression itself. - - - - - Appends a self-resolving operator to the expression and - returns a new ResolvableConstraintExpression. - - - - - Appends a constraint to the expression and returns that - constraint, which is associated with the current state - of the expression being built. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the - class passing in a ConstraintBuilder, which may be pre-populated. - - The builder. - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns the constraint provided as an argument - used to allow custom - custom constraints to easily participate in the syntax. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the suppled argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the suppled argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the suppled argument - - - - - Returns a constraint that tests whether the - actual value is less than the suppled argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the suppled argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the suppled argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the Regex pattern supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the Regex pattern supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - With is currently a NOP - reserved for future use. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the suppled argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the suppled argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the suppled argument - - - - - Returns a constraint that tests whether the - actual value is less than the suppled argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the suppled argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the suppled argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a new ContainsConstraint. This constraint - will, in turn, make use of the appropriate second-level - constraint, depending on the type of the actual argument. - This overload is only used if the item sought is a string, - since any other type implies that we are looking for a - collection member. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the Regex pattern supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the Regex pattern supplied as an argument. - - - - - Returns a constraint that fails if the actual - value matches the pattern supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - The ConstraintOperator class is used internally by a - ConstraintBuilder to represent an operator that - modifies or combines constraints. - - Constraint operators use left and right precedence - values to determine whether the top operator on the - stack should be reduced before pushing a new operator. - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - The syntax element preceding this operator - - - - - The syntax element folowing this operator - - - - - The precedence value used when the operator - is about to be pushed to the stack. - - - - - The precedence value used when the operator - is on the top of the stack. - - - - - PrefixOperator takes a single constraint and modifies - it's action in some way. - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Returns the constraint created by applying this - prefix to another constraint. - - - - - - - Negates the test of the constraint it wraps. - - - - - Constructs a new NotOperator - - - - - Returns a NotConstraint applied to its argument. - - - - - Abstract base for operators that indicate how to - apply a constraint to items in a collection. - - - - - Constructs a CollectionOperator - - - - - Represents a constraint that succeeds if all the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - they all succeed. - - - - - Represents a constraint that succeeds if any of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - any of them succeed. - - - - - Represents a constraint that succeeds if none of the - members of a collection match a base constraint. - - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - Represents a constraint that succeeds if the specified - count of members of a collection match a base constraint. - - - - - Construct an ExactCountOperator for a specified count - - The expected count - - - - Returns a constraint that will apply the argument - to the members of a collection, succeeding if - none of them succeed. - - - - - Represents a constraint that simply wraps the - constraint provided as an argument, without any - further functionality, but which modifes the - order of evaluation because of its precedence. - - - - - Constructor for the WithOperator - - - - - Returns a constraint that wraps its argument - - - - - Abstract base class for operators that are able to reduce to a - constraint whether or not another syntactic element follows. - - - - - Operator used to test for the presence of a named Property - on an object and optionally apply further tests to the - value of that property. - - - - - Constructs a PropOperator for a particular named property - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Gets the name of the property to which the operator applies - - - - - Operator that tests for the presence of a particular attribute - on a type and optionally applies further tests to the attribute. - - - - - Construct an AttributeOperator for a particular Type - - The Type of attribute tested - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Operator that tests that an exception is thrown and - optionally applies further tests to the exception. - - - - - Construct a ThrowsOperator - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - Abstract base class for all binary operators - - - - - Reduce produces a constraint from the operator and - any arguments. It takes the arguments from the constraint - stack and pushes the resulting constraint on it. - - - - - - Abstract method that produces a constraint by applying - the operator to its left and right constraint arguments. - - - - - Gets the left precedence of the operator - - - - - Gets the right precedence of the operator - - - - - Operator that requires both it's arguments to succeed - - - - - Construct an AndOperator - - - - - Apply the operator to produce an AndConstraint - - - - - Operator that requires at least one of it's arguments to succeed - - - - - Construct an OrOperator - - - - - Apply the operator to produce an OrConstraint - - - - - ContainsConstraint tests a whether a string contains a substring - or a collection contains an object. It postpones the decision of - which test to use until the type of the actual argument is known. - This allows testing whether a string is contained in a collection - or as a substring of another string using the same syntax. - - - - - Initializes a new instance of the class. - - The expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to ignore case and return self. - - - - - Applies a delay to the match so that a match can be evaluated in the future. - - - - - Creates a new DelayedConstraint - - The inner constraint two decorate - The time interval after which the match is performed - If the value of is less than 0 - - - - Creates a new DelayedConstraint - - The inner constraint two decorate - The time interval after which the match is performed - The time interval used for polling - If the value of is less than 0 - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - Test whether the constraint is satisfied by a delegate - - The delegate whose value is to be tested - True for if the base constraint fails, false if it succeeds - - - - Test whether the constraint is satisfied by a given reference. - Overridden to wait for the specified delay period before - calling the base constraint with the dereferenced value. - - A reference to the value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a MessageWriter. - - The writer on which the actual value is displayed - - - - Returns the string representation of the constraint. - - - - - EmptyDirectoryConstraint is used to test that a directory is empty - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - EmptyConstraint tests a whether a string or collection is empty, - postponing the decision about which test is applied until the - type of the actual argument is known. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - EqualConstraint is able to compare an actual value with the - expected value provided in its constructor. Two objects are - considered equal if both are null, or if both have the same - value. NUnit has special semantics for some object types. - - - - - If true, strings in error messages will be clipped - - - - - NUnitEqualityComparer used to test equality. - - - - - Initializes a new instance of the class. - - The expected value. - - - - Flag the constraint to use a tolerance when determining equality. - - Tolerance value to be used - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied Comparison object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Flag the constraint to use the supplied IEqualityComparer object. - - The IComparer object to use. - Self. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write a failure message. Overridden to provide custom - failure messages for EqualConstraint. - - The MessageWriter to write to - - - - Write description of this constraint - - The MessageWriter to write to - - - - Display the failure information for two collections that did not match. - - The MessageWriter on which to display - The expected collection. - The actual collection - The depth of this failure in a set of nested collections - - - - Displays a single line showing the types and sizes of the expected - and actual enumerations, collections or arrays. If both are identical, - the value is only shown once. - - The MessageWriter on which to display - The expected collection or array - The actual collection or array - The indentation level for the message line - - - - Displays a single line showing the point in the expected and actual - arrays at which the comparison failed. If the arrays have different - structures or dimensions, both values are shown. - - The MessageWriter on which to display - The expected array - The actual array - Index of the failure point in the underlying collections - The indentation level for the message line - - - - Display the failure information for two IEnumerables that did not match. - - The MessageWriter on which to display - The expected enumeration. - The actual enumeration - The depth of this failure in a set of nested collections - - - - Flag the constraint to ignore case and return self. - - - - - Flag the constraint to suppress string clipping - and return self. - - - - - Flag the constraint to compare arrays as collections - and return self. - - - - - Switches the .Within() modifier to interpret its tolerance as - a distance in representable values (see remarks). - - Self. - - Ulp stands for "unit in the last place" and describes the minimum - amount a given value can change. For any integers, an ulp is 1 whole - digit. For floating point values, the accuracy of which is better - for smaller numbers and worse for larger numbers, an ulp depends - on the size of the number. Using ulps for comparison of floating - point results instead of fixed tolerances is safer because it will - automatically compensate for the added inaccuracy of larger numbers. - - - - - Switches the .Within() modifier to interpret its tolerance as - a percentage that the actual values is allowed to deviate from - the expected value. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in days. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in hours. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in minutes. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in seconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in milliseconds. - - Self - - - - Causes the tolerance to be interpreted as a TimeSpan in clock ticks. - - Self - - - - EqualityAdapter class handles all equality comparisons - that use an IEqualityComparer, IEqualityComparer<T> - or a ComparisonAdapter. - - - - - Compares two objects, returning true if they are equal - - - - - Returns true if the two objects can be compared by this adapter. - The base adapter cannot handle IEnumerables except for strings. - - - - - Returns an EqualityAdapter that wraps an IComparer. - - - - - Returns an EqualityAdapter that wraps an IEqualityComparer. - - - - - Returns an EqualityAdapter that wraps an IEqualityComparer<T>. - - - - - Returns an EqualityAdapter that wraps an IComparer<T>. - - - - - Returns an EqualityAdapter that wraps a Comparison<T>. - - - - - EqualityAdapter that wraps an IComparer. - - - - - Returns true if the two objects can be compared by this adapter. - Generic adapter requires objects of the specified type. - - - - - EqualityAdapter that wraps an IComparer. - - - - Helper routines for working with floating point numbers - - - The floating point comparison code is based on this excellent article: - http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm - - - "ULP" means Unit in the Last Place and in the context of this library refers to - the distance between two adjacent floating point numbers. IEEE floating point - numbers can only represent a finite subset of natural numbers, with greater - accuracy for smaller numbers and lower accuracy for very large numbers. - - - If a comparison is allowed "2 ulps" of deviation, that means the values are - allowed to deviate by up to 2 adjacent floating point values, which might be - as low as 0.0000001 for small numbers or as high as 10.0 for large numbers. - - - - - Compares two floating point values for equality - First floating point value to be compared - Second floating point value t be compared - - Maximum number of representable floating point values that are allowed to - be between the left and the right floating point values - - True if both numbers are equal or close to being equal - - - Floating point values can only represent a finite subset of natural numbers. - For example, the values 2.00000000 and 2.00000024 can be stored in a float, - but nothing inbetween them. - - - This comparison will count how many possible floating point values are between - the left and the right number. If the number of possible values between both - numbers is less than or equal to maxUlps, then the numbers are considered as - being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - Compares two double precision floating point values for equality - First double precision floating point value to be compared - Second double precision floating point value t be compared - - Maximum number of representable double precision floating point values that are - allowed to be between the left and the right double precision floating point values - - True if both numbers are equal or close to being equal - - - Double precision floating point values can only represent a limited series of - natural numbers. For example, the values 2.0000000000000000 and 2.0000000000000004 - can be stored in a double, but nothing inbetween them. - - - This comparison will count how many possible double precision floating point - values are between the left and the right number. If the number of possible - values between both numbers is less than or equal to maxUlps, then the numbers - are considered as being equal. - - - Implementation partially follows the code outlined here: - http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ - - - - - - Reinterprets the memory contents of a floating point value as an integer value - - - Floating point value whose memory contents to reinterpret - - - The memory contents of the floating point value interpreted as an integer - - - - - Reinterprets the memory contents of a double precision floating point - value as an integer value - - - Double precision floating point value whose memory contents to reinterpret - - - The memory contents of the double precision floating point value - interpreted as an integer - - - - - Reinterprets the memory contents of an integer as a floating point value - - Integer value whose memory contents to reinterpret - - The memory contents of the integer value interpreted as a floating point value - - - - - Reinterprets the memory contents of an integer value as a double precision - floating point value - - Integer whose memory contents to reinterpret - - The memory contents of the integer interpreted as a double precision - floating point value - - - - Union of a floating point variable and an integer - - - The union's value as a floating point variable - - - The union's value as an integer - - - The union's value as an unsigned integer - - - Union of a double precision floating point variable and a long - - - The union's value as a double precision floating point variable - - - The union's value as a long - - - The union's value as an unsigned long - - - - Tests whether a value is greater than the value supplied to its constructor - - - - - The value against which a comparison is to be made - - - - - Initializes a new instance of the class. - - The expected value. - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Tests whether a value is greater than or equal to the value supplied to its constructor - - - - - The value against which a comparison is to be made - - - - - Initializes a new instance of the class. - - The expected value. - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Tests whether a value is less than the value supplied to its constructor - - - - - The value against which a comparison is to be made - - - - - Initializes a new instance of the class. - - The expected value. - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Tests whether a value is less than or equal to the value supplied to its constructor - - - - - The value against which a comparison is to be made - - - - - Initializes a new instance of the class. - - The expected value. - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - MessageWriter is the abstract base for classes that write - constraint descriptions and messages in some form. The - class has separate methods for writing various components - of a message, allowing implementations to tailor the - presentation as needed. - - - - - Construct a MessageWriter given a culture - - - - - Method to write single line message with optional args, usually - written to precede the general failure message. - - The message to be written - Any arguments used in formatting the message - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a givel - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The constraint that failed - - - - Display Expected and Actual lines for given values. This - method may be called by constraints that need more control over - the display of actual and expected values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given values, including - a tolerance value on the Expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in locating the point where the strings differ - If true, the strings should be clipped to fit the line - - - - Writes the text for a connector. - - The connector. - - - - Writes the text for a predicate. - - The predicate. - - - - Writes the text for an expected value. - - The expected value. - - - - Writes the text for a modifier - - The modifier. - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Abstract method to get the max line length - - - - - Static methods used in creating messages - - - - - Static string used when strings are clipped - - - - - Returns the representation of a type as used in NUnitLite. - This is the same as Type.ToString() except for arrays, - which are displayed with their declared sizes. - - - - - - - Converts any control characters in a string - to their escaped representation. - - The string to be converted - The converted string - - - - Return the a string representation for a set of indices into an array - - Array of indices for which a string is needed - - - - Get an array of indices representing the point in a enumerable, - collection or array corresponding to a single int index into the - collection. - - The collection to which the indices apply - Index in the collection - Array of indices - - - - Clip a string to a given length, starting at a particular offset, returning the clipped - string with ellipses representing the removed parts - - The string to be clipped - The maximum permitted length of the result string - The point at which to start clipping - The clipped string - - - - Clip the expected and actual strings in a coordinated fashion, - so that they may be displayed together. - - - - - - - - - Shows the position two strings start to differ. Comparison - starts at the start index. - - The expected string - The actual string - The index in the strings at which comparison should start - Boolean indicating whether case should be ignored - -1 if no mismatch found, or the index where mismatch found - - - - The Numerics class contains common operations on numeric values. - - - - - Checks the type of the object, returning true if - the object is a numeric type. - - The object to check - true if the object is a numeric type - - - - Checks the type of the object, returning true if - the object is a floating point numeric type. - - The object to check - true if the object is a floating point numeric type - - - - Checks the type of the object, returning true if - the object is a fixed point numeric type. - - The object to check - true if the object is a fixed point numeric type - - - - Test two numeric values for equality, performing the usual numeric - conversions and using a provided or default tolerance. If the tolerance - provided is Empty, this method may set it to a default tolerance. - - The expected value - The actual value - A reference to the tolerance in effect - True if the values are equal - - - - Compare two numeric values, performing the usual numeric conversions. - - The expected value - The actual value - The relationship of the values to each other - - - - NUnitComparer encapsulates NUnit's default behavior - in comparing two objects. - - - - - Compares two objects - - - - - - - - Returns the default NUnitComparer. - - - - - Generic version of NUnitComparer - - - - - - Compare two objects of the same type - - - - - NUnitEqualityComparer encapsulates NUnit's handling of - equality tests between objects. - - - - - - - - - - Compares two objects for equality within a tolerance - - The first object to compare - The second object to compare - The tolerance to use in the comparison - - - - - If true, all string comparisons will ignore case - - - - - If true, arrays will be treated as collections, allowing - those of different dimensions to be compared - - - - - Comparison objects used in comparisons for some constraints. - - - - - Compares two objects for equality within a tolerance. - - - - - Helper method to compare two arrays - - - - - Method to compare two DirectoryInfo objects - - first directory to compare - second directory to compare - true if equivalent, false if not - - - - Returns the default NUnitEqualityComparer - - - - - Gets and sets a flag indicating whether case should - be ignored in determining equality. - - - - - Gets and sets a flag indicating that arrays should be - compared as collections, without regard to their shape. - - - - - Gets and sets an external comparer to be used to - test for equality. It is applied to members of - collections, in place of NUnit's own logic. - - - - - Gets the list of failure points for the last Match performed. - - - - - FailurePoint class represents one point of failure - in an equality test. - - - - - The location of the failure - - - - - The expected value - - - - - The actual value - - - - - Indicates whether the expected value is valid - - - - - Indicates whether the actual value is valid - - - - - PathConstraint serves as the abstract base of constraints - that operate on paths and provides several helper methods. - - - - - The expected path used in the constraint - - - - - The actual path being tested - - - - - Flag indicating whether a caseInsensitive comparison should be made - - - - - Construct a PathConstraint for a give expected path - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Returns true if the expected path and actual path match - - - - - Returns the string representation of this constraint - - - - - Canonicalize the provided path - - - The path in standardized form - - - - Test whether two paths are the same - - The first path - The second path - Indicates whether case should be ignored - - - - - Test whether one path is under another path - - The first path - supposed to be the parent path - The second path - supposed to be the child path - Indicates whether case should be ignored - - - - - Test whether one path is the same as or under another path - - The first path - supposed to be the parent path - The second path - supposed to be the child path - - - - - Modifies the current instance to be case-insensitve - and returns it. - - - - - Modifies the current instance to be case-sensitve - and returns it. - - - - - Summary description for SamePathConstraint. - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The expected path - The actual path - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - SubPathConstraint tests that the actual path is under the expected path - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The expected path - The actual path - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - SamePathOrUnderConstraint tests that one path is under another - - - - - Initializes a new instance of the class. - - The expected path - - - - Test whether the constraint is satisfied by a given value - - The expected path - The actual path - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Predicate constraint wraps a Predicate in a constraint, - returning success if the predicate is true. - - - - - Construct a PredicateConstraint from a predicate - - - - - Determines whether the predicate succeeds when applied - to the actual value. - - - - - Writes the description to a MessageWriter - - - - - NotConstraint negates the effect of some other constraint - - - - - Initializes a new instance of the class. - - The base constraint to be negated. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for if the base constraint fails, false if it succeeds - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a MessageWriter. - - The writer on which the actual value is displayed - - - - AllItemsConstraint applies another constraint to each - item in a collection, succeeding if they all succeed. - - - - - Construct an AllItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - Write a description of this constraint to a MessageWriter - - - - - - SomeItemsConstraint applies another constraint to each - item in a collection, succeeding if any of them succeeds. - - - - - Construct a SomeItemsConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - succeeding if any item succeeds. - - - - - - - Write a description of this constraint to a MessageWriter - - - - - - NoItemConstraint applies another constraint to each - item in a collection, failing if any of them succeeds. - - - - - Construct a NoItemConstraint on top of an existing constraint - - - - - - Apply the item constraint to each item in the collection, - failing if any item fails. - - - - - - - Write a description of this constraint to a MessageWriter - - - - - - ExactCoutConstraint applies another constraint to each - item in a collection, succeeding only if a specified - number of items succeed. - - - - - Construct an ExactCountConstraint on top of an existing constraint - - - - - - - Apply the item constraint to each item in the collection, - succeeding only if the expected number of items pass. - - - - - - - Write a description of this constraint to a MessageWriter - - - - - - PropertyExistsConstraint tests that a named property - exists on the object provided through Match. - - Originally, PropertyConstraint provided this feature - in addition to making optional tests on the vaue - of the property. The two constraints are now separate. - - - - - Initializes a new instance of the class. - - The name of the property. - - - - Test whether the property exists for a given object - - The object to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a - MessageWriter. - - The writer on which the actual value is displayed - - - - Returns the string representation of the constraint. - - - - - - PropertyConstraint extracts a named property and uses - its value as the actual value for a chained constraint. - - - - - Initializes a new instance of the class. - - The name. - The constraint to apply to the property. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - Returns the string representation of the constraint. - - - - - - RangeConstraint tests whethe two values are within a - specified range. - - - - - Initializes a new instance of the class. - - From. - To. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - ResolvableConstraintExpression is used to represent a compound - constraint being constructed at a point where the last operator - may either terminate the expression or may have additional - qualifying constraints added to it. - - It is used, for example, for a Property element or for - an Exception element, either of which may be optionally - followed by constraints that apply to the property or - exception. - - - - - Create a new instance of ResolvableConstraintExpression - - - - - Create a new instance of ResolvableConstraintExpression, - passing in a pre-populated ConstraintBuilder. - - - - - Resolve the current expression to a Constraint - - - - - This operator creates a constraint that is satisfied only if both - argument constraints are satisfied. - - - - - This operator creates a constraint that is satisfied only if both - argument constraints are satisfied. - - - - - This operator creates a constraint that is satisfied only if both - argument constraints are satisfied. - - - - - This operator creates a constraint that is satisfied if either - of the argument constraints is satisfied. - - - - - This operator creates a constraint that is satisfied if either - of the argument constraints is satisfied. - - - - - This operator creates a constraint that is satisfied if either - of the argument constraints is satisfied. - - - - - This operator creates a constraint that is satisfied if the - argument constraint is not satisfied. - - - - - Appends an And Operator to the expression - - - - - Appends an Or operator to the expression. - - - - - ReusableConstraint wraps a resolved constraint so that it - may be saved and reused as needed. - - - - - Construct a ReusableConstraint - - The constraint or expression to be reused - - - - Conversion operator from a normal constraint to a ReusableConstraint. - - The original constraint to be wrapped as a ReusableConstraint - - - - - Returns the string representation of the constraint. - - A string representing the constraint - - - - Resolves the ReusableConstraint by returning the constraint - that it originally wrapped. - - A resolved constraint - - - - SameAsConstraint tests whether an object is identical to - the object passed to its constructor - - - - - Initializes a new instance of the class. - - The expected object. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - BinarySerializableConstraint tests whether - an object is serializable in binary format. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - Returns the string representation - - - - - BinarySerializableConstraint tests whether - an object is serializable in binary format. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - Returns the string representation of this constraint - - - - - StringConstraint is the abstract base for constraints - that operate on strings. It supports the IgnoreCase - modifier for string operations. - - - - - The expected value - - - - - Indicates whether tests should be case-insensitive - - - - - Constructs a StringConstraint given an expected value - - The expected value - - - - Modify the constraint to ignore case in matching. - - - - - EmptyStringConstraint tests whether a string is empty. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - NullEmptyStringConstraint tests whether a string is either null or empty. - - - - - Constructs a new NullOrEmptyStringConstraint - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - SubstringConstraint can test whether a string contains - the expected substring. - - - - - Initializes a new instance of the class. - - The expected. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - StartsWithConstraint can test whether a string starts - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - EndsWithConstraint can test whether a string ends - with an expected substring. - - - - - Initializes a new instance of the class. - - The expected string - - - - Test whether the constraint is matched by the actual value. - This is a template method, which calls the IsMatch method - of the derived class. - - - - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - RegexConstraint can test whether a string matches - the pattern provided. - - - - - Initializes a new instance of the class. - - The pattern. - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True for success, false for failure - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - ThrowsConstraint is used to test the exception thrown by - a delegate by applying a constraint to it. - - - - - Initializes a new instance of the class, - using a constraint to be applied to the exception. - - A constraint to apply to the caught exception. - - - - Executes the code of the delegate and captures any exception. - If a non-null base constraint was provided, it applies that - constraint to the exception. - - A delegate representing the code to be tested - True if an exception is thrown and the constraint succeeds, otherwise false - - - - Converts an ActualValueDelegate to a TestDelegate - before calling the primary overload. - - - - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - Returns the string representation of this constraint - - - - - Get the actual exception thrown - used by Assert.Throws. - - - - - ThrowsNothingConstraint tests that a delegate does not - throw an exception. - - - - - Test whether the constraint is satisfied by a given value - - The value to be tested - True if no exception is thrown, otherwise false - - - - Converts an ActualValueDelegate to a TestDelegate - before calling the primary overload. - - - - - - - Write the constraint description to a MessageWriter - - The writer on which the description is displayed - - - - Write the actual value for a failing constraint test to a - MessageWriter. The default implementation simply writes - the raw value of actual, leaving it to the writer to - perform any formatting. - - The writer on which the actual value is displayed - - - - Modes in which the tolerance value for a comparison can - be interpreted. - - - - - The tolerance was created with a value, without specifying - how the value would be used. This is used to prevent setting - the mode more than once and is generally changed to Linear - upon execution of the test. - - - - - The tolerance is used as a numeric range within which - two compared values are considered to be equal. - - - - - Interprets the tolerance as the percentage by which - the two compared values my deviate from each other. - - - - - Compares two values based in their distance in - representable numbers. - - - - - The Tolerance class generalizes the notion of a tolerance - within which an equality test succeeds. Normally, it is - used with numeric types, but it can be used with any - type that supports taking a difference between two - objects and comparing that difference to a value. - - - - - Constructs a linear tolerance of a specdified amount - - - - - Constructs a tolerance given an amount and ToleranceMode - - - - - Tests that the current Tolerance is linear with a - numeric value, throwing an exception if it is not. - - - - - Returns an empty Tolerance object, equivalent to - specifying no tolerance. In most cases, it results - in an exact match but for floats and doubles a - default tolerance may be used. - - - - - Returns a zero Tolerance object, equivalent to - specifying an exact match. - - - - - Gets the ToleranceMode for the current Tolerance - - - - - Gets the value of the current Tolerance instance. - - - - - Returns a new tolerance, using the current amount as a percentage. - - - - - Returns a new tolerance, using the current amount in Ulps. - - - - - Returns a new tolerance with a TimeSpan as the amount, using - the current amount as a number of days. - - - - - Returns a new tolerance with a TimeSpan as the amount, using - the current amount as a number of hours. - - - - - Returns a new tolerance with a TimeSpan as the amount, using - the current amount as a number of minutes. - - - - - Returns a new tolerance with a TimeSpan as the amount, using - the current amount as a number of seconds. - - - - - Returns a new tolerance with a TimeSpan as the amount, using - the current amount as a number of milliseconds. - - - - - Returns a new tolerance with a TimeSpan as the amount, using - the current amount as a number of clock ticks. - - - - - Returns true if the current tolerance is empty. - - - - - TypeConstraint is the abstract base for constraints - that take a Type as their expected value. - - - - - The expected Type used by the constraint - - - - - Construct a TypeConstraint for a given Type - - - - - - Write the actual value for a failing constraint test to a - MessageWriter. TypeConstraints override this method to write - the name of the type. - - The writer on which the actual value is displayed - - - - ExactTypeConstraint is used to test that an object - is of the exact type provided in the constructor - - - - - Construct an ExactTypeConstraint for a given Type - - The expected Type. - - - - Test that an object is of the exact type specified - - The actual value. - True if the tested object is of the exact type provided, otherwise false. - - - - Write the description of this constraint to a MessageWriter - - The MessageWriter to use - - - - ExceptionTypeConstraint is a special version of ExactTypeConstraint - used to provided detailed info about the exception thrown in - an error message. - - - - - Constructs an ExceptionTypeConstraint - - - - - Write the actual value for a failing constraint test to a - MessageWriter. Overriden to write additional information - in the case of an Exception. - - The MessageWriter to use - - - - InstanceOfTypeConstraint is used to test that an object - is of the same type provided or derived from it. - - - - - Construct an InstanceOfTypeConstraint for the type provided - - The expected Type - - - - Test whether an object is of the specified type or a derived type - - The object to be tested - True if the object is of the provided type or derives from it, otherwise false. - - - - Write a description of this constraint to a MessageWriter - - The MessageWriter to use - - - - AssignableFromConstraint is used to test that an object - can be assigned from a given Type. - - - - - Construct an AssignableFromConstraint for the type provided - - - - - - Test whether an object can be assigned from the specified type - - The object to be tested - True if the object can be assigned a value of the expected Type, otherwise false. - - - - Write a description of this constraint to a MessageWriter - - The MessageWriter to use - - - - AssignableToConstraint is used to test that an object - can be assigned to a given Type. - - - - - Construct an AssignableToConstraint for the type provided - - - - - - Test whether an object can be assigned to the specified type - - The object to be tested - True if the object can be assigned a value of the expected Type, otherwise false. - - - - Write a description of this constraint to a MessageWriter - - The MessageWriter to use - - - - Thrown when an assertion failed. - - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Thrown when a test executes inconclusively. - - - - - The error message that explains - the reason for the exception - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - Thrown when an assertion failed. - - - - - - - The error message that explains - the reason for the exception - The exception that caused the - current exception - - - - Serialization Constructor - - - - - - - - - - - Compares two objects of a given Type for equality within a tolerance - - The first object to compare - The second object to compare - The tolerance to use in the comparison - - - - - The different targets a test action attribute can be applied to - - - - - Default target, which is determined by where the action attribute is attached - - - - - Target a individual test case - - - - - Target a suite of test cases - - - - - Delegate used by tests that execute code and - capture any thrown exception. - - - - - The Assert class contains a collection of static methods that - implement the most common assertions used in NUnit. - - - - - We don't actually want any instances of this object, but some people - like to inherit from it to add other static methods. Hence, the - protected constructor disallows any instances of this object. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Helper for Assert.AreEqual(double expected, double actual, ...) - allowing code generation to work consistently. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - The message to initialize the with. - - - - Throws a with the message and arguments - that are passed in. This allows a test to be cut short, with a result - of success returned to NUnit. - - - - - Throws an with the message and arguments - that are passed in. This is used by the other Assert functions. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This is used by the other Assert functions. - - The message to initialize the with. - - - - Throws an . - This is used by the other Assert functions. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as ignored. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as ignored. - - - - - Throws an with the message and arguments - that are passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - Arguments to be used in formatting the message - - - - Throws an with the message that is - passed in. This causes the test to be reported as inconclusive. - - The message to initialize the with. - - - - Throws an . - This causes the test to be reported as Inconclusive. - - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - The message that will be displayed on failure - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - The message that will be displayed on failure - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to a referenced value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to a referenced value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - The message that will be displayed on failure - - - - Apply a constraint to a referenced value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - A Constraint to be applied - The actual value to test - The message that will be displayed on failure - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - Used as a synonym for That in rare cases where a private setter - causes a Visual Basic compilation error. - - - This method is provided for use by VB developers needing to test - the value of properties with private setters. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - The message that will be displayed on failure - - - - Verifies that a delegate throws a particular exception when called. - - A constraint to be satisfied by the exception - A TestSnippet delegate - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestSnippet delegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestSnippet delegate - The message that will be displayed on failure - - - - Verifies that a delegate throws a particular exception when called. - - The exception Type expected - A TestSnippet delegate - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestSnippet delegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestSnippet delegate - The message that will be displayed on failure - - - - Verifies that a delegate throws a particular exception when called. - - Type of the expected exception - A TestSnippet delegate - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - The message that will be displayed on failure - - - - Verifies that a delegate throws an exception when called - and returns it. - - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - The message that will be displayed on failure - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - The message that will be displayed on failure - - - - Verifies that a delegate throws an exception of a certain Type - or one derived from it when called and returns it. - - The expected Exception Type - A TestDelegate - - - - Verifies that a delegate does not throw an exception - - A TestSnippet delegate - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Verifies that a delegate does not throw an exception. - - A TestSnippet delegate - The message that will be displayed on failure - - - - Verifies that a delegate does not throw an exception. - - A TestSnippet delegate - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display in case of failure - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - The message to display in case of failure - - - - Asserts that a condition is false. If the condition is true the method throws - an . - - The evaluated condition - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - - - - Verifies that the object that is passed in is not equal to null - If the object is null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - The message to display in case of failure - - - - Verifies that the object that is passed in is equal to null - If the object is not null then an - is thrown. - - The object that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - The message to display in case of failure - - - - Verifies that the double that is passed in is an NaN value. - If the object is not NaN then an - is thrown. - - The value that is to be tested - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - The message to display in case of failure - - - - Assert that a string is empty - that is equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing ICollection - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - The message to display in case of failure - - - - Assert that a string is not empty - that is not equal to string.Empty - - The string to be tested - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - The message to display in case of failure - - - - Assert that an array, list or other collection is not empty - - An array, list or other collection implementing ICollection - - - - Assert that a string is either null or equal to string.Empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is either null or equal to string.Empty - - The string to be tested - The message to display in case of failure - - - - Assert that a string is either null or equal to string.Empty - - The string to be tested - - - - Assert that a string is not null or empty - - The string to be tested - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Assert that a string is not null or empty - - The string to be tested - The message to display in case of failure - - - - Assert that a string is not null or empty - - The string to be tested - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - - - - Asserts that an object may be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - The message to display in case of failure - - - - Asserts that an object may not be assigned a value of a given Type. - - The expected Type. - The object under examination - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - - - - Asserts that an object is an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - The message to display in case of failure - - - - Asserts that an object is not an instance of a given type. - - The expected Type - The object being examined - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are equal. If they are not, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - The message to display in case of failure - - - - Verifies that two doubles are equal considering a delta. If the - expected value is infinity then the delta value is ignored. If - they are not equal then an is - thrown. - - The expected value - The actual value - The maximum acceptable difference between the - the expected and the actual - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - - - - Verifies that two objects are equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are not equal an is thrown. - - The value that is expected - The actual value - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - The message to display in case of failure - - - - Verifies that two values are not equal. If they are equal, then an - is thrown. - - The expected value - The actual value - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - The message to display in case of failure - - - - Verifies that two objects are not equal. Two objects are considered - equal if both are null, or if both have the same value. NUnit - has special semantics for some object types. - If they are equal an is thrown. - - The value that is expected - The actual value - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - - - - Asserts that two objects refer to the same object. If they - are not the same an is thrown. - - The expected object - The actual object - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - The message to display in case of failure - - - - Asserts that two objects do not refer to the same object. If they - are the same an is thrown. - - The expected object - The actual object - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than the second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - The message to display in case of failure - - - - Verifies that the first value is greater than or equal tothe second - value. If it is not, then an - is thrown. - - The first value, expected to be greater - The second value, expected to be less - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - The message to display in case of failure - - - - Verifies that the first value is less than or equal to the second - value. If it is not, then an - is thrown. - - The first value, expected to be less - The second value, expected to be greater - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - The message to display in case of failure - Array of objects to be used in formatting the message - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - The message to display in case of failure - - - - Asserts that an object is contained in a list. - - The expected object - The list to be examined - - - - Gets the number of assertions executed so far and - resets the counter to zero. - - - - - AssertionHelper is an optional base class for user tests, - allowing the use of shorter names for constraints and - asserts and avoiding conflict with the definition of - , from which it inherits much of its - behavior, in certain mock object frameworks. - - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. Works - identically to Assert.That - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. Works - identically to Assert.That. - - A Constraint to be applied - The actual value to test - The message that will be displayed on failure - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. Works - identically to Assert.That - - A Constraint to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - The message that will be displayed on failure - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to a referenced value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - - - - Apply a constraint to a referenced value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - The message that will be displayed on failure - - - - Apply a constraint to a referenced value, succeeding if the constraint - is satisfied and throwing an assertion exception on failure. - - A Constraint to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to Assert.That. - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically to Assert.That. - - The evaluated condition - The message to display if the condition is false - - - - Asserts that a condition is true. If the condition is false the method throws - an . Works Identically Assert.That. - - The evaluated condition - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - Returns a ListMapper based on a collection. - - The original collection - - - - - Provides static methods to express the assumptions - that must be met for a test to give a meaningful - result. If an assumption is not met, the test - should produce an inconclusive result. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - An ActualValueDelegate returning the value to be tested - The message that will be displayed on failure - - - - Apply a constraint to an actual value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - An ActualValueDelegate returning the value to be tested - A Constraint expression to be applied - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Apply a constraint to a referenced value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - - - - Apply a constraint to a referenced value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - - - - Apply a constraint to a referenced value, succeeding if the constraint - is satisfied and throwing an InconclusiveException on failure. - - A Constraint expression to be applied - The actual value to test - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - Arguments to be used in formatting the message - - - - Asserts that a condition is true. If the condition is false the method throws - an . - - The evaluated condition - The message to display if the condition is false - - - - Asserts that a condition is true. If the condition is false the - method throws an . - - The evaluated condition - - - - Asserts that the code represented by a delegate throws an exception - that satisfies the constraint provided. - - A TestDelegate to be executed - A ThrowsConstraint used in the test - - - - A set of Assert methods operationg on one or more collections - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - The message that will be displayed on failure - - - - Asserts that all items contained in collection are of the type specified by expectedType. - - IEnumerable containing objects to be considered - System.Type that all objects in collection must be instances of - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable containing objects to be considered - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable containing objects to be considered - The message that will be displayed on failure - - - - Asserts that all items contained in collection are not equal to null. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - The message that will be displayed on failure - - - - Ensures that every object contained in collection exists within the collection - once and only once. - - IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are exactly equal. The collections must have the same count, - and contain the exact same objects in the same order. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - - - - Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - - - - Asserts that expected and actual are not exactly equal. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not exactly equal. - If comparer is not null then it will be used to compare the objects. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The IComparer to use in comparing objects from each IEnumerable - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - - - - Asserts that expected and actual are not equivalent. - - The first IEnumerable of objects to be considered - The second IEnumerable of objects to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - The message that will be displayed on failure - - - - Asserts that collection contains actual as an item. - - IEnumerable of objects to be considered - Object to be found within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - The message that will be displayed on failure - - - - Asserts that collection does not contain actual as an item. - - IEnumerable of objects to be considered - Object that cannot exist within collection - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that superset is not a subject of subset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that superset is not a subject of subset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - - - - Asserts that superset is not a subject of subset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Asserts that superset is a subset of subset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - - - - Asserts that superset is a subset of subset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - - - - Asserts that superset is a subset of subset. - - The IEnumerable superset to be considered - The IEnumerable subset to be considered - The message that will be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is empty - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - - - - Assert that an array,list or other collection is empty - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - The message to be displayed on failure - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - The message to be displayed on failure - Arguments to be used in formatting the message - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - The message to be displayed on failure - - - - Assert that an array, list or other collection is ordered - - An array, list or other collection implementing IEnumerable - A custom comparer to perform the comparisons - - - - Static helper class used in the constraint-based syntax - - - - - Creates a new SubstringConstraint - - The value of the substring - A SubstringConstraint - - - - Creates a new CollectionContainsConstraint. - - The item that should be found. - A new CollectionContainsConstraint - - - - Summary description for DirectoryAssert - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - We don't actually want any instances of this object, but some people - like to inherit from it to add other static methods. Hence, the - protected constructor disallows any instances of this object. - - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if directories are not equal - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A directory path string containing the value that is expected - A directory path string containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A directory path string containing the value that is expected - A directory path string containing the actual value - The message to display if directories are not equal - - - - Verifies that two directories are equal. Two directories are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A directory path string containing the value that is expected - A directory path string containing the actual value - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - The message to display if directories are not equal - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory containing the value that is expected - A directory containing the actual value - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory path string containing the value that is expected - A directory path string containing the actual value - The message to display if directories are equal - Arguments to be used in formatting the message - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory path string containing the value that is expected - A directory path string containing the actual value - The message to display if directories are equal - - - - Asserts that two directories are not equal. If they are equal - an is thrown. - - A directory path string containing the value that is expected - A directory path string containing the actual value - - - - Asserts that the directory is empty. If it is not empty - an is thrown. - - A directory to search - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory is empty. If it is not empty - an is thrown. - - A directory to search - The message to display if directories are not equal - - - - Asserts that the directory is empty. If it is not empty - an is thrown. - - A directory to search - - - - Asserts that the directory is empty. If it is not empty - an is thrown. - - A directory to search - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory is empty. If it is not empty - an is thrown. - - A directory to search - The message to display if directories are not equal - - - - Asserts that the directory is empty. If it is not empty - an is thrown. - - A directory to search - - - - Asserts that the directory is not empty. If it is empty - an is thrown. - - A directory to search - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory is not empty. If it is empty - an is thrown. - - A directory to search - The message to display if directories are not equal - - - - Asserts that the directory is not empty. If it is empty - an is thrown. - - A directory to search - - - - Asserts that the directory is not empty. If it is empty - an is thrown. - - A directory to search - The message to display if directories are not equal - Arguments to be used in formatting the message - - - - Asserts that the directory is not empty. If it is empty - an is thrown. - - A directory to search - The message to display if directories are not equal - - - - Asserts that the directory is not empty. If it is empty - an is thrown. - - A directory to search - - - - Asserts that path contains actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - The message to display if directory is not within the path - Arguments to be used in formatting the message - - - - Asserts that path contains actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - The message to display if directory is not within the path - - - - Asserts that path contains actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - - - - Asserts that path contains actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - The message to display if directory is not within the path - Arguments to be used in formatting the message - - - - Asserts that path contains actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - The message to display if directory is not within the path - - - - Asserts that path contains actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - - - - Asserts that path does not contain actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - The message to display if directory is not within the path - Arguments to be used in formatting the message - - - - Asserts that path does not contain actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - The message to display if directory is not within the path - - - - Asserts that path does not contain actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - - - - Asserts that path does not contain actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - The message to display if directory is not within the path - Arguments to be used in formatting the message - - - - Asserts that path does not contain actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - The message to display if directory is not within the path - - - - Asserts that path does not contain actual as a subdirectory or - an is thrown. - - A directory to search - sub-directory asserted to exist under directory - - - - Summary description for FileAssert. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - We don't actually want any instances of this object, but some people - like to inherit from it to add other static methods. Hence, the - protected constructor disallows any instances of this object. - - - - - Verifies that two Streams are equal. Two Streams are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The expected Stream - The actual Stream - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two Streams are equal. Two Streams are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The expected Stream - The actual Stream - The message to display if objects are not equal - - - - Verifies that two Streams are equal. Two Streams are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The expected Stream - The actual Stream - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if objects are not equal - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - A file containing the value that is expected - A file containing the actual value - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if objects are not equal - - - - Verifies that two files are equal. Two files are considered - equal if both are null, or if both have the same value byte for byte. - If they are not equal an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - - - - Asserts that two Streams are not equal. If they are equal - an is thrown. - - The expected Stream - The actual Stream - The message to be displayed when the two Stream are the same. - Arguments to be used in formatting the message - - - - Asserts that two Streams are not equal. If they are equal - an is thrown. - - The expected Stream - The actual Stream - The message to be displayed when the Streams are the same. - - - - Asserts that two Streams are not equal. If they are equal - an is thrown. - - The expected Stream - The actual Stream - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - A file containing the value that is expected - A file containing the actual value - The message to display if objects are not equal - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - A file containing the value that is expected - A file containing the actual value - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if Streams are not equal - Arguments to be used in formatting the message - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - The message to display if objects are not equal - - - - Asserts that two files are not equal. If they are equal - an is thrown. - - The path to a file containing the value that is expected - The path to a file containing the actual value - - - - GlobalSettings is a place for setting default values used - by the framework in performing asserts. - - - - - Default tolerance for floating point equality - - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding only if a specified number of them succeed. - - - - - Returns a new PropertyConstraintExpression, which will either - test for the existence of the named property on the object - being tested or apply any following constraint to that property. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new AttributeConstraint checking for the - presence of a particular attribute on an object. - - - - - Returns a new CollectionContainsConstraint checking for the - presence of a particular object in the collection. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if at least one of them succeeds. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them fail. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Length property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Count property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the Message property of the object being tested. - - - - - Returns a new ConstraintExpression, which will apply the following - constraint to the InnerException property of the object being tested. - - - - - Interface implemented by a user fixture in order to - validate any expected exceptions. It is only called - for test methods marked with the ExpectedException - attribute. - - - - - Method to handle an expected exception - - The exception to be handled - - - - Helper class with properties and methods that supply - a number of constraints used in Asserts. - - - - - Returns a constraint that tests two items for equality - - - - - Returns a constraint that tests that two references are the same object - - - - - Returns a constraint that tests whether the - actual value is greater than the suppled argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the suppled argument - - - - - Returns a constraint that tests whether the - actual value is greater than or equal to the suppled argument - - - - - Returns a constraint that tests whether the - actual value is less than the suppled argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the suppled argument - - - - - Returns a constraint that tests whether the - actual value is less than or equal to the suppled argument - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual - value is of the exact type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is of the type supplied as an argument or a derived type. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is assignable from the type supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a collection containing the same elements as the - collection supplied as an argument. - - - - - Returns a constraint that tests whether the actual value - is a subset of the collection supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the Regex pattern supplied as an argument. - - - - - Returns a constraint that tests whether the path provided - is the same as an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the path provided - is the same path or under an expected path after canonicalization. - - - - - Returns a constraint that tests whether the actual value falls - within a specified range. - - - - - Returns a ConstraintExpression that negates any - following constraint. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - Returns a constraint that tests for null - - - - - Returns a constraint that tests for True - - - - - Returns a constraint that tests for False - - - - - Returns a constraint that tests for a positive value - - - - - Returns a constraint that tests for a negative value - - - - - Returns a constraint that tests for NaN - - - - - Returns a constraint that tests for empty - - - - - Returns a constraint that tests whether a collection - contains all unique items. - - - - - Returns a constraint that tests whether an object graph is serializable in binary format. - - - - - Returns a constraint that tests whether an object graph is serializable in xml format. - - - - - Returns a constraint that tests whether a collection is ordered - - - - - The Iz class is a synonym for Is intended for use in VB, - which regards Is as a keyword. - - - - - The List class is a helper class with properties and methods - that supply a number of constraints used with lists and collections. - - - - - List.Map returns a ListMapper, which can be used to map - the original collection to another collection. - - - - - - - ListMapper is used to transform a collection used as an actual argument - producing another collection to be used in the assertion. - - - - - Construct a ListMapper based on a collection - - The collection to be transformed - - - - Produces a collection containing all the values of a property - - The collection of property values - - - - - Randomizer returns a set of random values in a repeatable - way, to allow re-running of tests if necessary. - - - - - Get a randomizer for a particular member, returning - one that has already been created if it exists. - This ensures that the same values are generated - each time the tests are reloaded. - - - - - Get a randomizer for a particular parameter, returning - one that has already been created if it exists. - This ensures that the same values are generated - each time the tests are reloaded. - - - - - Construct a randomizer using a random seed - - - - - Construct a randomizer using a specified seed - - - - - Return an array of random doubles between 0.0 and 1.0. - - - - - - - Return an array of random doubles with values in a specified range. - - - - - Return an array of random ints with values in a specified range. - - - - - Get a random seed for use in creating a randomizer. - - - - - The SpecialValue enum is used to represent TestCase arguments - that cannot be used as arguments to an Attribute. - - - - - Null represents a null value, which cannot be used as an - argument to an attriute under .NET 1.x - - - - - Basic Asserts on strings. - - - - - The Equals method throws an AssertionException. This is done - to make sure there is no mistake by calling this function. - - - - - - - override the default ReferenceEquals to throw an AssertionException. This - implementation makes sure there is no mistake in calling this function - as part of Assert. - - - - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - The message to display in case of failure - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string is not found within another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - The message to display in case of failure - - - - Asserts that a string is found within another string. - - The expected string - The string to be examined - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - The message to display in case of failure - - - - Asserts that a string starts with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - The message to display in case of failure - - - - Asserts that a string does not start with another string. - - The expected string - The string to be examined - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - The message to display in case of failure - - - - Asserts that a string ends with another string. - - The expected string - The string to be examined - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - The message to display in case of failure - - - - Asserts that a string does not end with another string. - - The expected string - The string to be examined - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - - - - Asserts that two strings are equal, without regard to case. - - The expected string - The actual string - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that two strings are Notequal, without regard to case. - - The expected string - The actual string - The message to display in case of failure - - - - Asserts that two strings are not equal, without regard to case. - - The expected string - The actual string - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - The message to display in case of failure - - - - Asserts that a string matches an expected regular expression pattern. - - The regex pattern to be matched - The actual string - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - The message to display in case of failure - Arguments used in formatting the message - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - The message to display in case of failure - - - - Asserts that a string does not match an expected regular expression pattern. - - The regex pattern to be used - The actual string - - - - The TestCaseData class represents a set of arguments - and other parameter info to be used for a parameterized - test case. It provides a number of instance modifiers - for use in initializing the test case. - - Note: Instance modifiers are getters that return - the same instance after modifying it's state. - - - - - The argument list to be provided to the test - - - - - The expected result to be returned - - - - - Set to true if this has an expected result - - - - - The expected exception Type - - - - - The FullName of the expected exception - - - - - The name to be used for the test - - - - - The description of the test - - - - - A dictionary of properties, used to add information - to tests without requiring the class to change. - - - - - If true, indicates that the test case is to be ignored - - - - - If true, indicates that the test case is marked explicit - - - - - The reason for ignoring a test case - - - - - Initializes a new instance of the class. - - The arguments. - - - - Initializes a new instance of the class. - - The argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - - - - Initializes a new instance of the class. - - The first argument. - The second argument. - The third argument. - - - - Sets the expected result for the test - - The expected result - A modified TestCaseData - - - - Sets the expected exception type for the test - - Type of the expected exception. - The modified TestCaseData instance - - - - Sets the expected exception type for the test - - FullName of the expected exception. - The modified TestCaseData instance - - - - Sets the name of the test case - - The modified TestCaseData instance - - - - Sets the description for the test case - being constructed. - - The description. - The modified TestCaseData instance. - - - - Applies a category to the test - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Applies a named property to the test - - - - - - - - Ignores this TestCase. - - - - - - Ignores this TestCase, specifying the reason. - - The reason. - - - - - Marks this TestCase as Explicit - - - - - - Marks this TestCase as Explicit, specifying the reason. - - The reason. - - - - - Gets the argument list to be provided to the test - - - - - Gets the expected result - - - - - Returns true if the result has been set - - - - - Gets the expected exception Type - - - - - Gets the FullName of the expected exception - - - - - Gets the name to be used for the test - - - - - Gets the description of the test - - - - - Gets a value indicating whether this is ignored. - - true if ignored; otherwise, false. - - - - Gets a value indicating whether this is explicit. - - true if explicit; otherwise, false. - - - - Gets the ignore reason. - - The ignore reason. - - - - Gets a list of categories associated with this test. - - - - - Gets the property dictionary for this test - - - - - Provide the context information of the current test - - - - - Constructs a TestContext using the provided context dictionary - - A context dictionary - - - - Get the current test context. This is created - as needed. The user may save the context for - use within a test, but it should not be used - outside the test for which it is created. - - - - - Gets a TestAdapter representing the currently executing test in this context. - - - - - Gets a ResultAdapter representing the current result for the test - executing in this context. - - - - - Gets the directory containing the current test assembly. - - - - - Gets the directory to be used for outputing files created - by this test run. - - - - - TestAdapter adapts a Test for consumption by - the user test code. - - - - - Constructs a TestAdapter for this context - - The context dictionary - - - - The name of the test. - - - - - The FullName of the test - - - - - The properties of the test. - - - - - ResultAdapter adapts a TestResult for consumption by - the user test code. - - - - - Construct a ResultAdapter for a context - - The context holding the result - - - - The TestState of current test. This maps to the ResultState - used in nunit.core and is subject to change in the future. - - - - - The TestStatus of current test. This enum will be used - in future versions of NUnit and so is to be preferred - to the TestState value. - - - - - Provides details about a test - - - - - Creates an instance of TestDetails - - The fixture that the test is a member of, if available. - The method that implements the test, if available. - The full name of the test. - A string representing the type of test, e.g. "Test Case". - Indicates if the test represents a suite of tests. - - - - The fixture that the test is a member of, if available. - - - - - The method that implements the test, if available. - - - - - The full name of the test. - - - - - A string representing the type of test, e.g. "Test Case". - - - - - Indicates if the test represents a suite of tests. - - - - - The ResultState enum indicates the result of running a test - - - - - The result is inconclusive - - - - - The test was not runnable. - - - - - The test has been skipped. - - - - - The test has been ignored. - - - - - The test succeeded - - - - - The test failed - - - - - The test encountered an unexpected exception - - - - - The test was cancelled by the user - - - - - The TestStatus enum indicates the result of running a test - - - - - The test was inconclusive - - - - - The test has skipped - - - - - The test succeeded - - - - - The test failed - - - - - Helper class with static methods used to supply constraints - that operate on strings. - - - - - Returns a constraint that succeeds if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value contains the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value starts with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that fails if the actual - value ends with the substring supplied as an argument. - - - - - Returns a constraint that succeeds if the actual - value matches the Regex pattern supplied as an argument. - - - - - Returns a constraint that fails if the actual - value matches the pattern supplied as an argument. - - - - - Returns a ConstraintExpression, which will apply - the following constraint to all members of a collection, - succeeding if all of them succeed. - - - - - TextMessageWriter writes constraint descriptions and messages - in displayable form as a text stream. It tailors the display - of individual message components to form the standard message - format of NUnit assertion failure messages. - - - - - Prefix used for the expected value line of a message - - - - - Prefix used for the actual value line of a message - - - - - Length of a message prefix - - - - - Construct a TextMessageWriter - - - - - Construct a TextMessageWriter, specifying a user message - and optional formatting arguments. - - - - - - - Method to write single line message with optional args, usually - written to precede the general failure message, at a givel - indentation level. - - The indentation level of the message - The message to be written - Any arguments used in formatting the message - - - - Display Expected and Actual lines for a constraint. This - is called by MessageWriter's default implementation of - WriteMessageTo and provides the generic two-line display. - - The constraint that failed - - - - Display Expected and Actual lines for given values. This - method may be called by constraints that need more control over - the display of actual and expected values than is provided - by the default implementation. - - The expected value - The actual value causing the failure - - - - Display Expected and Actual lines for given values, including - a tolerance value on the expected line. - - The expected value - The actual value causing the failure - The tolerance within which the test was made - - - - Display the expected and actual string values on separate lines. - If the mismatch parameter is >=0, an additional line is displayed - line containing a caret that points to the mismatch point. - - The expected string value - The actual string value - The point at which the strings don't match or -1 - If true, case is ignored in string comparisons - If true, clip the strings to fit the max line length - - - - Writes the text for a connector. - - The connector. - - - - Writes the text for a predicate. - - The predicate. - - - - Write the text for a modifier. - - The modifier. - - - - Writes the text for an expected value. - - The expected value. - - - - Writes the text for an actual value. - - The actual value. - - - - Writes the text for a generalized value. - - The value. - - - - Writes the text for a collection value, - starting at a particular point, to a max length - - The collection containing elements to write. - The starting point of the elements to write - The maximum number of elements to write - - - - Write the generic 'Expected' line for a constraint - - The constraint that failed - - - - Write the generic 'Expected' line for a given value - - The expected value - - - - Write the generic 'Expected' line for a given value - and tolerance. - - The expected value - The tolerance within which the test was made - - - - Write the generic 'Actual' line for a constraint - - The constraint for which the actual value is to be written - - - - Write the generic 'Actual' line for a given value - - The actual value causing a failure - - - - Gets or sets the maximum line length for this writer - - - - - Helper class with properties and methods that supply - constraints that operate on exceptions. - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the exact type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying the type of exception expected - - - - - Creates a constraint specifying an expected exception - - - - - Creates a constraint specifying an exception with a given InnerException - - - - - Creates a constraint specifying an expected TargetInvocationException - - - - - Creates a constraint specifying an expected TargetInvocationException - - - - - Creates a constraint specifying an expected TargetInvocationException - - - - - Creates a constraint specifying that no exception is thrown - - - - diff --git a/lib/tests/nunit-console/nunit.mocks.dll b/lib/tests/nunit-console/nunit.mocks.dll deleted file mode 100644 index 33479511f..000000000 Binary files a/lib/tests/nunit-console/nunit.mocks.dll and /dev/null differ diff --git a/lib/tests/nunit-console/nunit.util.dll b/lib/tests/nunit-console/nunit.util.dll deleted file mode 100644 index c837e9ecf..000000000 Binary files a/lib/tests/nunit-console/nunit.util.dll and /dev/null differ diff --git a/lib/tests/nunit-console/pnunit-agent.exe.config b/lib/tests/nunit-console/pnunit-agent.exe.config deleted file mode 100644 index c1516ef41..000000000 --- a/lib/tests/nunit-console/pnunit-agent.exe.config +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit-console/pnunit-launcher.exe.config b/lib/tests/nunit-console/pnunit-launcher.exe.config deleted file mode 100644 index c1516ef41..000000000 --- a/lib/tests/nunit-console/pnunit-launcher.exe.config +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit-console/pnunit.framework.dll b/lib/tests/nunit-console/pnunit.framework.dll deleted file mode 100644 index f8cf2790d..000000000 Binary files a/lib/tests/nunit-console/pnunit.framework.dll and /dev/null differ diff --git a/lib/tests/nunit-console/pnunit.tests.dll b/lib/tests/nunit-console/pnunit.tests.dll deleted file mode 100644 index 6e577c9ef..000000000 Binary files a/lib/tests/nunit-console/pnunit.tests.dll and /dev/null differ diff --git a/lib/tests/nunit-console/runpnunit.bat b/lib/tests/nunit-console/runpnunit.bat deleted file mode 100644 index 43b3a69f8..000000000 --- a/lib/tests/nunit-console/runpnunit.bat +++ /dev/null @@ -1,3 +0,0 @@ -start pnunit-agent 8080 . -start pnunit-agent 8081 . -pnunit-launcher test.conf diff --git a/lib/tests/nunit-console/test.conf b/lib/tests/nunit-console/test.conf deleted file mode 100644 index ce825ebe3..000000000 --- a/lib/tests/nunit-console/test.conf +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - Testing - - - Testing - pnunit.tests.dll - TestLibraries.Testing.EqualTo19 - $agent_host:8080 - - - - - - - Parallel_Tests - - - ParallelTest_A_Test - pnunit.tests.dll - TestLibraries.ParallelExample.ParallelTest_A - $agent_host:8080 - - - 2 - - - - ParallelTest_B_Test - pnunit.tests.dll - TestLibraries.ParallelExample.ParallelTest_B - $agent_host:8080 - - 1 - - - - - - - - - Parallel_Barriers - - - Parallel_Barriers_TestA - pnunit.tests.dll - TestLibraries.ParallelExampleWithBarriers.ParallelTestWithBarriersA - $agent_host:8080 - - - - START_BARRIER - WAIT_BARRIER - - - - Parallel_Barriers_TestB - pnunit.tests.dll - TestLibraries.ParallelExampleWithBarriers.ParallelTestWithBarriersB - $agent_host:8081 - - - - START_BARRIER - WAIT_BARRIER - - - - - - - - \ No newline at end of file diff --git a/lib/tests/nunit.framework.dll b/lib/tests/nunit.framework.dll deleted file mode 100644 index 215767d2f..000000000 Binary files a/lib/tests/nunit.framework.dll and /dev/null differ diff --git a/lib/tests/nunit.mocks.dll b/lib/tests/nunit.mocks.dll deleted file mode 100644 index 33479511f..000000000 Binary files a/lib/tests/nunit.mocks.dll and /dev/null differ diff --git a/lib/tests/pnunit.framework.dll b/lib/tests/pnunit.framework.dll deleted file mode 100644 index f8cf2790d..000000000 Binary files a/lib/tests/pnunit.framework.dll and /dev/null differ diff --git a/lib/tests/protobuf-net.Extensions.dll b/lib/tests/protobuf-net.Extensions.dll deleted file mode 100644 index 5660967a6..000000000 Binary files a/lib/tests/protobuf-net.Extensions.dll and /dev/null differ diff --git a/lib/tests/protobuf-net.dll b/lib/tests/protobuf-net.dll deleted file mode 100644 index 99bbb956f..000000000 Binary files a/lib/tests/protobuf-net.dll and /dev/null differ diff --git a/license.txt b/license.txt new file mode 100644 index 000000000..228199f61 --- /dev/null +++ b/license.txt @@ -0,0 +1,70 @@ +ServiceStack +Copyright (c) 2013 ServiceStack +=============================================================================== + +This program 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, see +http://www.gnu.org/licenses/agpl-3.0.html. + +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. + + +FOSS License Exception +=============================================================================== + +This Exception applies to open source applications built with ServiceStack and +ServiceStack extensions ("The Software"), and to open source Derivative Works of +the Software, that use the Software under the terms of GNU Affero General +Public License, version 3 ("AGPLv3"). The Exception extends AGPLv3 by providing +additional grants that allows developers of FOSS applications to include ServiceStack +with their FOSS applications in combination with other software licensed under +the licenses from the "Open Source License List" below, provided that: + +You obey the AGPLv3 terms for the Software and the Derivative Work, except for +the separate parts of the Derivative Work ("Additions") which constitute independent +work and are not dervied from the Software. + + - All Additions are distributed subject to one of the licenses listed below. + - Your software distribution provides complete source code for the Additions. + - The Derivative Work and its Additions are intended for use in end-user applications + and do not constitute software intended for use by software developers, such as + software libraries, components, and development kits. + - If you violate any of the terms in this Exception, you lose all rights granted + to you by the Exception and revert to the terms of AGPLv3. + +Service Stack reserves all rights not expressly granted in these terms and conditions. + +Open Source License List + +Name Version +Academic Free License 2.0 +Apache Software License 2.0 +Apple Public Source License 2.0 +Artistic license From Perl 5.8.0 +BSD license July 22 1999 +Common Development and Distribution License (CDDL) 1.0 +Common Public License 1.0 +Eclipse Public License 1.0 +Educational Community License 2.0 +European Union Public License (EUPL) 1.1 +GNU General Public License (GPL) 2.0 +GNU Library or "Lesser" General Public License (LGPL) 3.0 +Jabber Open Source License 1.0 +MIT License (As listed in file MIT-License.txt) - +Mozilla Public License (MPL) 1.0/1.1 +Open Software License 2.0 +OpenSSL license (with original SSLeay license) 2003 (1998) +University of Illinois/NCSA Open Source License - +W3C License 2001 +X11 License 2001 +Zlib/libpng License - + + + +Commercial License +=========================================================================== +In addition to this license, ServiceStack is offered under a commerical license. +Contact team@servicestack.net for details. diff --git a/src/.nuget/NuGet.config b/src/.nuget/NuGet.config deleted file mode 100644 index 67f8ea046..000000000 --- a/src/.nuget/NuGet.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/.nuget/NuGet.exe b/src/.nuget/NuGet.exe deleted file mode 100644 index b5c8886d2..000000000 Binary files a/src/.nuget/NuGet.exe and /dev/null differ diff --git a/src/.nuget/NuGet.targets b/src/.nuget/NuGet.targets deleted file mode 100644 index 4cf24f51e..000000000 --- a/src/.nuget/NuGet.targets +++ /dev/null @@ -1,77 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) - $([System.IO.Path]::Combine($(SolutionDir), "packages")) - - - - - $(SolutionDir).nuget - packages.config - $(SolutionDir)packages - - - - - $(NuGetToolsPath)\nuget.exe - "$(NuGetExePath)" - mono --runtime=v4.0.30319 $(NuGetExePath) - - $(TargetDir.Trim('\\')) - - - "" - - - false - - - false - - - $(NuGetCommand) install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)" - $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..9a112079b --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,54 @@ + + + + 6.0.3 + ServiceStack + ServiceStack, Inc. + © 2008-2022 ServiceStack, Inc + true + https://github.com/ServiceStack/ServiceStack.Text + https://servicestack.net/terms + https://servicestack.net/img/logo-64.png + https://docs.servicestack.net/release-notes-history + git + https://github.com/ServiceStack/ServiceStack.Text.git + embedded + latest + true + true + false + + + + true + true + + + + $(DefineConstants);NETFX;NET45;NET472 + True + False + ../servicestack.snk + + + + $(DefineConstants);NETSTANDARD;NETSTANDARD2_0 + + + + $(DefineConstants);NET6_0;NET6_0_OR_GREATER + + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT + + + + + + + + DEBUG + + + diff --git a/src/ServiceStack.SL5.sln b/src/ServiceStack.SL5.sln deleted file mode 100644 index 19901cd0c..000000000 --- a/src/ServiceStack.SL5.sln +++ /dev/null @@ -1,20 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.SL5", "ServiceStack.Text.SL5\ServiceStack.Text.SL5.csproj", "{CC38D2CC-5E47-4218-9994-5DDEE36820FA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CC38D2CC-5E47-4218-9994-5DDEE36820FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC38D2CC-5E47-4218-9994-5DDEE36820FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC38D2CC-5E47-4218-9994-5DDEE36820FA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC38D2CC-5E47-4218-9994-5DDEE36820FA}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/ServiceStack.Text.4.5.resharper.user b/src/ServiceStack.Text.4.5.resharper.user deleted file mode 100644 index db0a8cf70..000000000 --- a/src/ServiceStack.Text.4.5.resharper.user +++ /dev/null @@ -1,461 +0,0 @@ - - - - - - True - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/ServiceStack.Text.Android/ServiceStack.Text.Android.sln b/src/ServiceStack.Text.Android/ServiceStack.Text.Android.sln deleted file mode 100644 index d1737a20e..000000000 --- a/src/ServiceStack.Text.Android/ServiceStack.Text.Android.sln +++ /dev/null @@ -1,20 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.Android", "ServiceStack.Text.Android\ServiceStack.Text.Android.csproj", "{56838CD4-8EBC-4860-A3E1-1697875BBC61}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {56838CD4-8EBC-4860-A3E1-1697875BBC61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56838CD4-8EBC-4860-A3E1-1697875BBC61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56838CD4-8EBC-4860-A3E1-1697875BBC61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56838CD4-8EBC-4860-A3E1-1697875BBC61}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/ServiceStack.Text.Android/ServiceStack.Text.Android/Properties/AssemblyInfo.cs b/src/ServiceStack.Text.Android/ServiceStack.Text.Android/Properties/AssemblyInfo.cs deleted file mode 100644 index 17794a683..000000000 --- a/src/ServiceStack.Text.Android/ServiceStack.Text.Android/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Text.Android")] -[assembly: AssemblyProduct("ServiceStack.Text.Android")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyCompany("ServiceStack")] -[assembly: AssemblyCopyright("Copyright © ServiceStack 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. Only Windows -// assemblies support COM. -[assembly: ComVisible(false)] - -// On Windows, the following GUID is for the ID of the typelib if this -// project is exposed to COM. On other platforms, it unique identifies the -// title storage container when deploying this assembly to the device. -[assembly: Guid("bec96c96-54be-440b-a791-2c0e1c12d6f7")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -[assembly: AssemblyVersion("1.0.0.0")] diff --git a/src/ServiceStack.Text.Android/ServiceStack.Text.Android/ServiceStack.Text.Android.csproj b/src/ServiceStack.Text.Android/ServiceStack.Text.Android/ServiceStack.Text.Android.csproj deleted file mode 100644 index 96c4c2c39..000000000 --- a/src/ServiceStack.Text.Android/ServiceStack.Text.Android/ServiceStack.Text.Android.csproj +++ /dev/null @@ -1,273 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {56838CD4-8EBC-4860-A3E1-1697875BBC61} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - ServiceStack.Text - ServiceStack.Text - 512 - Off - - - True - full - False - bin\Debug\ - TRACE;DEBUG;ANDROID - prompt - 4 - None - - - pdbonly - True - bin\Release\ - TRACE;ANDROID - prompt - 4 - SdkOnly - False - - - - - - - - - - - - - AssemblyUtils.cs - - - CollectionExtensions.cs - - - Common\DateTimeSerializer.cs - - - Common\DeserializeArray.cs - - - Common\DeserializeBuiltin.cs - - - Common\DeserializeCollection.cs - - - Common\DeserializeDictionary.cs - - - Common\DeserializeKeyValuePair.cs - - - Common\DeserializeListWithElements.cs - - - Common\DeserializeSpecializedCollections.cs - - - Common\DeserializeType.cs - - - Common\DeserializeTypeRef.cs - - - Common\DeserializeTypeRefJson.cs - - - Common\DeserializeTypeRefJsv.cs - - - Common\DeserializeTypeUtils.cs - - - Common\ITypeSerializer.cs - - - Common\JsDelegates.cs - - - Common\JsReader.cs - - - Common\JsState.cs - - - Common\JsWriter.cs - - - Common\ParseUtils.cs - - - Common\StaticParseMethod.cs - - - Common\WriteDictionary.cs - - - Common\WriteLists.cs - - - Common\WriteType.cs - - - Controller\CommandProcessor.cs - - - Controller\PathInfo.cs - - - CsvAttribute.cs - - - CsvConfig.cs - - - CsvSerializer.cs - - - CsvStreamExtensions.cs - - - CsvWriter.cs - - - DateTimeExtensions.cs - - - Env.cs - - - ITracer.cs - - - ITypeSerializer.Generic.cs - - - JsConfig.cs - - - JsonObject.cs - - - JsonSerializer.cs - - - JsonSerializer.Generic.cs - - - Json\JsonReader.Generic.cs - - - Json\JsonTypeSerializer.cs - - - Json\JsonUtils.cs - - - Json\JsonWriter.Generic.cs - - - JsvFormatter.cs - - - Jsv\JsvDeserializeType.cs - - - Jsv\JsvReader.Generic.cs - - - Jsv\JsvSerializer.Generic.cs - - - Jsv\JsvTypeSerializer.cs - - - Jsv\JsvWriter.Generic.cs - - - ListExtensions.cs - - - MapExtensions.cs - - - Marc\Link.cs - - - Marc\ObjectAccessor.cs - - - Marc\TypeAccessor.cs - - - QueryStringSerializer.cs - - - ReflectionExtensions.cs - - - Reflection\StaticAccessors.cs - - - StreamExtensions.cs - - - StringExtensions.cs - - - Support\AssemblyTypeDefinition.cs - - - Support\DoubleConverter.cs - - - Support\TypePair.cs - - - SystemTime.cs - - - TextExtensions.cs - - - Tracer.cs - - - TranslateListWithElements.cs - - - TypeConfig.cs - - - TypeSerializer.cs - - - TypeSerializer.Generic.cs - - - XmlSerializer.cs - - - - DynamicProxy.cs - - - - - \ No newline at end of file diff --git a/src/ServiceStack.Text.Core.sln b/src/ServiceStack.Text.Core.sln new file mode 100644 index 000000000..259fa0116 --- /dev/null +++ b/src/ServiceStack.Text.Core.sln @@ -0,0 +1,58 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26114.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ServiceStack.Text.Tests", "..\tests\ServiceStack.Text.Tests\ServiceStack.Text.Tests.xproj", "{93AC6B4C-73DB-413D-BD87-EF874DCC71A2}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Northwind.Common", "..\tests\Northwind.Common\Northwind.Common.xproj", "{4EB719DB-92CF-49A1-BDEF-30D4596DB26C}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ServiceStack.Client", "..\tests\ServiceStack.Client\ServiceStack.Client.xproj", "{E61EB05D-7BBE-47A4-A0FC-5CD4C5FA46A8}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ServiceStack.Common", "..\tests\ServiceStack.Common\ServiceStack.Common.xproj", "{FC7F3ABA-0A65-4030-A4BB-7AB45BDAA633}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ServiceStack.Interfaces", "..\tests\ServiceStack.Interfaces\ServiceStack.Interfaces.xproj", "{065DB7A1-3161-4798-AEA6-78023618A4E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text", "ServiceStack.Text\ServiceStack.Text.csproj", "{8F56DF61-B041-4B95-B658-EB51EBEF4EF6}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NetCore.Console.Tests", "..\tests\NetCore.Console.Tests\NetCore.Console.Tests.xproj", "{2C558857-B489-44BF-945A-CF8E0F286F3B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {93AC6B4C-73DB-413D-BD87-EF874DCC71A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93AC6B4C-73DB-413D-BD87-EF874DCC71A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93AC6B4C-73DB-413D-BD87-EF874DCC71A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93AC6B4C-73DB-413D-BD87-EF874DCC71A2}.Release|Any CPU.Build.0 = Release|Any CPU + {4EB719DB-92CF-49A1-BDEF-30D4596DB26C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EB719DB-92CF-49A1-BDEF-30D4596DB26C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EB719DB-92CF-49A1-BDEF-30D4596DB26C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EB719DB-92CF-49A1-BDEF-30D4596DB26C}.Release|Any CPU.Build.0 = Release|Any CPU + {E61EB05D-7BBE-47A4-A0FC-5CD4C5FA46A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E61EB05D-7BBE-47A4-A0FC-5CD4C5FA46A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E61EB05D-7BBE-47A4-A0FC-5CD4C5FA46A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E61EB05D-7BBE-47A4-A0FC-5CD4C5FA46A8}.Release|Any CPU.Build.0 = Release|Any CPU + {FC7F3ABA-0A65-4030-A4BB-7AB45BDAA633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC7F3ABA-0A65-4030-A4BB-7AB45BDAA633}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC7F3ABA-0A65-4030-A4BB-7AB45BDAA633}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC7F3ABA-0A65-4030-A4BB-7AB45BDAA633}.Release|Any CPU.Build.0 = Release|Any CPU + {065DB7A1-3161-4798-AEA6-78023618A4E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {065DB7A1-3161-4798-AEA6-78023618A4E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {065DB7A1-3161-4798-AEA6-78023618A4E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {065DB7A1-3161-4798-AEA6-78023618A4E1}.Release|Any CPU.Build.0 = Release|Any CPU + {8F56DF61-B041-4B95-B658-EB51EBEF4EF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F56DF61-B041-4B95-B658-EB51EBEF4EF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F56DF61-B041-4B95-B658-EB51EBEF4EF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F56DF61-B041-4B95-B658-EB51EBEF4EF6}.Release|Any CPU.Build.0 = Release|Any CPU + {2C558857-B489-44BF-945A-CF8E0F286F3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C558857-B489-44BF-945A-CF8E0F286F3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C558857-B489-44BF-945A-CF8E0F286F3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C558857-B489-44BF-945A-CF8E0F286F3B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ServiceStack.Text.MonoTouch/ServiceStack.Text.MonoTouch.sln b/src/ServiceStack.Text.MonoTouch/ServiceStack.Text.MonoTouch.sln deleted file mode 100644 index 6a0a3c19d..000000000 --- a/src/ServiceStack.Text.MonoTouch/ServiceStack.Text.MonoTouch.sln +++ /dev/null @@ -1,59 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.MonoTouch", "ServiceStack.Text.MonoTouch\ServiceStack.Text.MonoTouch.csproj", "{1137B5AC-2259-413C-A473-93721D2A7551}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.Tests.MonoTouch", "..\..\tests\ServiceStack.Text.Tests.MonoTouch\ServiceStack.Text.Tests.MonoTouch.csproj", "{C7844556-6217-4D62-868F-9FF8968401BA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - Debug|iPhoneSimulator = Debug|iPhoneSimulator - Release|iPhoneSimulator = Release|iPhoneSimulator - Debug|iPhone = Debug|iPhone - Release|iPhone = Release|iPhone - Ad-Hoc|iPhone = Ad-Hoc|iPhone - AppStore|iPhone = AppStore|iPhone - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1137B5AC-2259-413C-A473-93721D2A7551}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.AppStore|iPhone.Build.0 = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Debug|iPhone.Build.0 = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Release|Any CPU.Build.0 = Release|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Release|iPhone.ActiveCfg = Release|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Release|iPhone.Build.0 = Release|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {1137B5AC-2259-413C-A473-93721D2A7551}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {C7844556-6217-4D62-868F-9FF8968401BA}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone - {C7844556-6217-4D62-868F-9FF8968401BA}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone - {C7844556-6217-4D62-868F-9FF8968401BA}.AppStore|iPhone.ActiveCfg = AppStore|iPhone - {C7844556-6217-4D62-868F-9FF8968401BA}.AppStore|iPhone.Build.0 = AppStore|iPhone - {C7844556-6217-4D62-868F-9FF8968401BA}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {C7844556-6217-4D62-868F-9FF8968401BA}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator - {C7844556-6217-4D62-868F-9FF8968401BA}.Debug|iPhone.ActiveCfg = Debug|iPhone - {C7844556-6217-4D62-868F-9FF8968401BA}.Debug|iPhone.Build.0 = Debug|iPhone - {C7844556-6217-4D62-868F-9FF8968401BA}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {C7844556-6217-4D62-868F-9FF8968401BA}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {C7844556-6217-4D62-868F-9FF8968401BA}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator - {C7844556-6217-4D62-868F-9FF8968401BA}.Release|Any CPU.Build.0 = Release|iPhoneSimulator - {C7844556-6217-4D62-868F-9FF8968401BA}.Release|iPhone.ActiveCfg = Release|iPhone - {C7844556-6217-4D62-868F-9FF8968401BA}.Release|iPhone.Build.0 = Release|iPhone - {C7844556-6217-4D62-868F-9FF8968401BA}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {C7844556-6217-4D62-868F-9FF8968401BA}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = ..\..\tests\ServiceStack.Text.Tests.MonoTouch\ServiceStack.Text.Tests.MonoTouch.csproj - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/ServiceStack.Text.MonoTouch/ServiceStack.Text.MonoTouch/ServiceStack.Text.MonoTouch.csproj b/src/ServiceStack.Text.MonoTouch/ServiceStack.Text.MonoTouch/ServiceStack.Text.MonoTouch.csproj deleted file mode 100644 index 7d87fafc8..000000000 --- a/src/ServiceStack.Text.MonoTouch/ServiceStack.Text.MonoTouch/ServiceStack.Text.MonoTouch.csproj +++ /dev/null @@ -1,282 +0,0 @@ - - - - Debug - AnyCPU - 10.0.0 - 2.0 - {1137B5AC-2259-413C-A473-93721D2A7551} - {6BC8ED88-2882-458C-8E55-DFD12B67127B};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - ServiceStack.Text.MonoTouch - ServiceStack.Text.MonoTouch - - - True - full - False - bin\Debug - DEBUG;MONOTOUCH; - prompt - 4 - False - - - none - True - bin\Release - prompt - 4 - False - MONOTOUCH - - - - - - - - - - - - AssemblyUtils.cs - - - CollectionExtensions.cs - - - CsvConfig.cs - - - CsvSerializer.cs - - - CsvStreamExtensions.cs - - - CsvWriter.cs - - - DateTimeExtensions.cs - - - Env.cs - - - HashSet.cs - - - ITracer.cs - - - ITypeSerializer.Generic.cs - - - JsConfig.cs - - - JsonObject.cs - - - JsonSerializer.Generic.cs - - - JsonSerializer.cs - - - JsvFormatter.cs - - - ListExtensions.cs - - - MapExtensions.cs - - - QueryStringSerializer.cs - - - ReflectionExtensions.cs - - - StreamExtensions.cs - - - StringExtensions.cs - - - SystemTime.cs - - - TextExtensions.cs - - - Tracer.cs - - - TranslateListWithElements.cs - - - TypeConfig.cs - - - TypeSerializer.Generic.cs - - - TypeSerializer.cs - - - WebRequestExtensions.cs - - - Common\DateTimeSerializer.cs - - - Common\DeserializeArray.cs - - - Common\DeserializeKeyValuePair.cs - - - Common\DeserializeBuiltin.cs - - - Common\DeserializeCollection.cs - - - Common\DeserializeDictionary.cs - - - Common\DeserializeListWithElements.cs - - - Common\DeserializeSpecializedCollections.cs - - - Common\DeserializeType.cs - - - Common\DeserializeTypeRef.cs - - - Common\DeserializeTypeRefJson.cs - - - Common\DeserializeTypeRefJsv.cs - - - Common\DeserializeTypeUtils.cs - - - Common\ITypeSerializer.cs - - - Common\JsDelegates.cs - - - Common\JsReader.cs - - - Common\JsState.cs - - - Common\JsWriter.cs - - - Common\ParseUtils.cs - - - Common\StaticParseMethod.cs - - - Common\WriteDictionary.cs - - - Common\WriteLists.cs - - - Common\WriteType.cs - - - Controller\CommandProcessor.cs - - - Controller\PathInfo.cs - - - Json\JsonReader.Generic.cs - - - Json\JsonTypeSerializer.cs - - - Json\JsonUtils.cs - - - Json\JsonWriter.Generic.cs - - - Jsv\JsvDeserializeType.cs - - - Jsv\JsvReader.Generic.cs - - - Jsv\JsvSerializer.Generic.cs - - - Jsv\JsvTypeSerializer.cs - - - Jsv\JsvWriter.Generic.cs - - - Marc\Link.cs - - - Marc\ObjectAccessor.cs - - - Marc\TypeAccessor.cs - - - Properties\AssemblyInfo.cs - - - Reflection\StaticAccessors.cs - - - Support\AssemblyTypeDefinition.cs - - - Support\DoubleConverter.cs - - - Support\TypePair.cs - - - XmlSerializer.cs - - - DynamicProxy.cs - - - CsvAttribute.cs - - - JsConfigScope.cs - - - - - ServiceStack.Text.XBox360.csproj - - - ServiceStack.Text.csproj - - - ServiceStack.Text.suo - - - \ No newline at end of file diff --git a/src/ServiceStack.Text.Net40/Properties/AssemblyInfo.cs b/src/ServiceStack.Text.Net40/Properties/AssemblyInfo.cs deleted file mode 100644 index a31e9f876..000000000 --- a/src/ServiceStack.Text.Net40/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Text.Net40")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ServiceStack.Text.Net40")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7ad65246-68d3-4f17-9544-dc2102c10dfd")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/ServiceStack.Text.Net40/ServiceStack.Text.Net40.csproj b/src/ServiceStack.Text.Net40/ServiceStack.Text.Net40.csproj deleted file mode 100644 index 9effb788e..000000000 --- a/src/ServiceStack.Text.Net40/ServiceStack.Text.Net40.csproj +++ /dev/null @@ -1,287 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {15BF26C8-92F8-445D-8FFC-7882A519B67D} - Library - Properties - ServiceStack.Text - ServiceStack.Text - v4.0 - 512 - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET40 - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - AssemblyUtils.cs - - - CollectionExtensions.cs - - - Common\DateTimeSerializer.cs - - - Common\DeserializeArray.cs - - - Common\DeserializeBuiltin.cs - - - Common\DeserializeCollection.cs - - - Common\DeserializeDictionary.cs - - - Common\DeserializeDynamic.cs - - - Common\DeserializeListWithElements.cs - - - Common\DeserializeSpecializedCollections.cs - - - Common\DeserializeType.cs - - - Common\DeserializeTypeRef.cs - - - Common\DeserializeKeyValuePair.cs - - - Common\DeserializeTypeRefJson.cs - - - Common\DeserializeTypeRefJsv.cs - - - Common\DeserializeTypeUtils.cs - - - Common\ITypeSerializer.cs - - - Common\JsDelegates.cs - - - Common\JsReader.cs - - - Common\JsState.cs - - - Common\JsWriter.cs - - - Common\ParseUtils.cs - - - Common\StaticParseMethod.cs - - - Common\WriteDictionary.cs - - - Common\WriteLists.cs - - - Common\WriteType.cs - - - Controller\CommandProcessor.cs - - - Controller\PathInfo.cs - - - CsvConfig.cs - - - CsvAttribute.cs - - - CsvSerializer.cs - - - CsvStreamExtensions.cs - - - CsvWriter.cs - - - DateTimeExtensions.cs - - - DynamicJson.cs - - - DynamicProxy.cs - - - Env.cs - - - HashSet.cs - - - ITracer.cs - - - ITypeSerializer.Generic.cs - - - JsConfig.cs - - - JsConfigScope.cs - - - JsonObject.cs - - - JsonSerializer.cs - - - JsonSerializer.Generic.cs - - - Json\JsonReader.Generic.cs - - - Json\JsonTypeSerializer.cs - - - Json\JsonUtils.cs - - - Json\JsonWriter.Generic.cs - - - JsvFormatter.cs - - - Jsv\JsvDeserializeType.cs - - - Jsv\JsvReader.Generic.cs - - - Jsv\JsvSerializer.Generic.cs - - - Jsv\JsvTypeSerializer.cs - - - Jsv\JsvWriter.Generic.cs - - - ListExtensions.cs - - - MapExtensions.cs - - - Marc\Link.cs - - - Marc\ObjectAccessor.cs - - - Marc\TypeAccessor.cs - - - QueryStringSerializer.cs - - - ReflectionExtensions.cs - - - Reflection\StaticAccessors.cs - - - StreamExtensions.cs - - - StringExtensions.cs - - - Support\AssemblyTypeDefinition.cs - - - Support\DoubleConverter.cs - - - Support\TypePair.cs - - - SystemTime.cs - - - TextExtensions.cs - - - Tracer.cs - - - TranslateListWithElements.cs - - - TypeConfig.cs - - - TypeSerializer.cs - - - TypeSerializer.Generic.cs - - - WebRequestExtensions.cs - - - XmlSerializer.cs - - - - - - - \ No newline at end of file diff --git a/src/ServiceStack.Text.Net40/ServiceStack.Text.Net40.sln b/src/ServiceStack.Text.Net40/ServiceStack.Text.Net40.sln deleted file mode 100644 index 33f551cd7..000000000 --- a/src/ServiceStack.Text.Net40/ServiceStack.Text.Net40.sln +++ /dev/null @@ -1,26 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.Net40", "ServiceStack.Text.Net40.csproj", "{15BF26C8-92F8-445D-8FFC-7882A519B67D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.Net40.Tests", "..\..\tests\ServiceStack.Text.Net40.Tests\ServiceStack.Text.Net40.Tests.csproj", "{BAE532C7-F01D-4ADC-A0A9-A104F4C2F3BD}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {15BF26C8-92F8-445D-8FFC-7882A519B67D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15BF26C8-92F8-445D-8FFC-7882A519B67D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15BF26C8-92F8-445D-8FFC-7882A519B67D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15BF26C8-92F8-445D-8FFC-7882A519B67D}.Release|Any CPU.Build.0 = Release|Any CPU - {BAE532C7-F01D-4ADC-A0A9-A104F4C2F3BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BAE532C7-F01D-4ADC-A0A9-A104F4C2F3BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BAE532C7-F01D-4ADC-A0A9-A104F4C2F3BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BAE532C7-F01D-4ADC-A0A9-A104F4C2F3BD}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/ServiceStack.Text.SL4/Properties/AssemblyInfo.cs b/src/ServiceStack.Text.SL4/Properties/AssemblyInfo.cs deleted file mode 100644 index 6fb36fa31..000000000 --- a/src/ServiceStack.Text.SL4/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Text.SL4")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ServiceStack.Text.SL4")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7ad65246-68d3-4f17-9544-dc2102c10dfd")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/ServiceStack.Text.SL4/ServiceStack.Text.SL4.csproj b/src/ServiceStack.Text.SL4/ServiceStack.Text.SL4.csproj deleted file mode 100644 index abc818b3b..000000000 --- a/src/ServiceStack.Text.SL4/ServiceStack.Text.SL4.csproj +++ /dev/null @@ -1,305 +0,0 @@ - - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {CC38D2CC-5E47-4218-9994-5DDEE36820FA} - {A1591282-1198-4647-A2B1-27E5FF5F6F3B};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - ServiceStack.Text - ServiceStack.Text - Silverlight - v4.0 - $(TargetFrameworkVersion) - false - true - true - - - - - v3.5 - - - true - full - false - Bin\Debug - TRACE;DEBUG;SILVERLIGHT SILVERLIGHT4 - true - true - prompt - 4 - - - pdbonly - true - Bin\Release - TRACE;SILVERLIGHT SILVERLIGHT4 - true - true - prompt - 4 - Bin\Release\ServiceStack.Text.XML - - - - - - - - - - - - - - - AssemblyUtils.cs - - - CollectionExtensions.cs - - - Common\DateTimeSerializer.cs - - - Common\DeserializeArray.cs - - - Common\DeserializeBuiltin.cs - - - Common\DeserializeCollection.cs - - - Common\DeserializeDictionary.cs - - - Common\DeserializeKeyValuePair.cs - - - Common\DeserializeListWithElements.cs - - - Common\DeserializeSpecializedCollections.cs - - - Common\DeserializeType.cs - - - Common\DeserializeTypeRef.cs - - - Common\DeserializeTypeRefJson.cs - - - Common\DeserializeTypeRefJsv.cs - - - Common\DeserializeTypeUtils.cs - - - Common\ITypeSerializer.cs - - - Common\JsDelegates.cs - - - Common\JsReader.cs - - - Common\JsState.cs - - - Common\JsWriter.cs - - - Common\ParseUtils.cs - - - Common\StaticParseMethod.cs - - - Common\WriteDictionary.cs - - - Common\WriteLists.cs - - - Common\WriteType.cs - - - Controller\CommandProcessor.cs - - - Controller\PathInfo.cs - - - CsvAttribute.cs - - - CsvConfig.cs - - - CsvSerializer.cs - - - CsvStreamExtensions.cs - - - CsvWriter.cs - - - DateTimeExtensions.cs - - - Env.cs - - - ITracer.cs - - - ITypeSerializer.Generic.cs - - - JsConfig.cs - - - JsConfigScope.cs - - - JsonObject.cs - - - JsonSerializer.cs - - - JsonSerializer.Generic.cs - - - Json\JsonReader.Generic.cs - - - Json\JsonTypeSerializer.cs - - - Json\JsonUtils.cs - - - Json\JsonWriter.Generic.cs - - - JsvFormatter.cs - - - Jsv\JsvDeserializeType.cs - - - Jsv\JsvReader.Generic.cs - - - Jsv\JsvSerializer.Generic.cs - - - Jsv\JsvTypeSerializer.cs - - - Jsv\JsvWriter.Generic.cs - - - ListExtensions.cs - - - MapExtensions.cs - - - Marc\Link.cs - - - Marc\ObjectAccessor.cs - - - Marc\TypeAccessor.cs - - - QueryStringSerializer.cs - - - ReflectionExtensions.cs - - - Reflection\StaticAccessors.cs - - - StreamExtensions.cs - - - StringExtensions.cs - - - Support\AssemblyTypeDefinition.cs - - - Support\DoubleConverter.cs - - - Support\TypePair.cs - - - SystemTime.cs - - - TextExtensions.cs - - - Tracer.cs - - - TranslateListWithElements.cs - - - TypeConfig.cs - - - TypeSerializer.cs - - - TypeSerializer.Generic.cs - - - XmlSerializer.cs - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/ServiceStack.Text.SL5/Properties/AssemblyInfo.cs b/src/ServiceStack.Text.SL5/Properties/AssemblyInfo.cs deleted file mode 100644 index a59af2bbd..000000000 --- a/src/ServiceStack.Text.SL5/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Text.SL5")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ServiceStack.Text.SL5")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("b058736d-b210-47e1-b16f-ba72d92e7c4e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/ServiceStack.Text.SL5/ServiceStack.Text.SL5.csproj b/src/ServiceStack.Text.SL5/ServiceStack.Text.SL5.csproj deleted file mode 100644 index 51fd5c791..000000000 --- a/src/ServiceStack.Text.SL5/ServiceStack.Text.SL5.csproj +++ /dev/null @@ -1,297 +0,0 @@ - - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {CC38D2CC-5E47-4218-9994-5DDEE36820FA} - {A1591282-1198-4647-A2B1-27E5FF5F6F3B};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - ServiceStack.Text - ServiceStack.Text - Silverlight - v5.0 - $(TargetFrameworkVersion) - false - true - true - - - - v3.5 - - - true - full - false - Bin\Debug - DEBUG;TRACE;SILVERLIGHT - true - true - prompt - 4 - - - pdbonly - true - Bin\Release - TRACE;SILVERLIGHT - true - true - prompt - 4 - Bin\Release\ServiceStack.Text.XML - - - - - - - - - - - - - - AssemblyUtils.cs - - - CollectionExtensions.cs - - - Common\DateTimeSerializer.cs - - - Common\DeserializeArray.cs - - - Common\DeserializeBuiltin.cs - - - Common\DeserializeCollection.cs - - - Common\DeserializeDictionary.cs - - - Common\DeserializeListWithElements.cs - - - Common\DeserializeSpecializedCollections.cs - - - Common\DeserializeKeyValuePair.cs - - - Common\DeserializeType.cs - - - Common\DeserializeTypeRef.cs - - - Common\DeserializeTypeRefJson.cs - - - Common\DeserializeTypeRefJsv.cs - - - Common\DeserializeTypeUtils.cs - - - Common\ITypeSerializer.cs - - - Common\JsDelegates.cs - - - Common\JsReader.cs - - - Common\JsState.cs - - - Common\JsWriter.cs - - - Common\ParseUtils.cs - - - Common\StaticParseMethod.cs - - - Common\WriteDictionary.cs - - - Common\WriteLists.cs - - - Common\WriteType.cs - - - Controller\CommandProcessor.cs - - - Controller\PathInfo.cs - - - CsvConfig.cs - - - CsvAttribute.cs - - - CsvSerializer.cs - - - CsvStreamExtensions.cs - - - CsvWriter.cs - - - DateTimeExtensions.cs - - - Env.cs - - - ITracer.cs - - - ITypeSerializer.Generic.cs - - - JsConfig.cs - - - JsonObject.cs - - - JsonSerializer.cs - - - JsonSerializer.Generic.cs - - - Json\JsonReader.Generic.cs - - - Json\JsonTypeSerializer.cs - - - Json\JsonUtils.cs - - - Json\JsonWriter.Generic.cs - - - JsvFormatter.cs - - - Jsv\JsvDeserializeType.cs - - - Jsv\JsvReader.Generic.cs - - - Jsv\JsvSerializer.Generic.cs - - - Jsv\JsvTypeSerializer.cs - - - Jsv\JsvWriter.Generic.cs - - - ListExtensions.cs - - - MapExtensions.cs - - - Marc\Link.cs - - - Marc\ObjectAccessor.cs - - - Marc\TypeAccessor.cs - - - QueryStringSerializer.cs - - - ReflectionExtensions.cs - - - Reflection\StaticAccessors.cs - - - StreamExtensions.cs - - - StringExtensions.cs - - - Support\AssemblyTypeDefinition.cs - - - Support\DoubleConverter.cs - - - Support\TypePair.cs - - - SystemTime.cs - - - TextExtensions.cs - - - Tracer.cs - - - TranslateListWithElements.cs - - - TypeConfig.cs - - - TypeSerializer.cs - - - TypeSerializer.Generic.cs - - - XmlSerializer.cs - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/ServiceStack.Text.WP/Properties/AssemblyInfo.cs b/src/ServiceStack.Text.WP/Properties/AssemblyInfo.cs deleted file mode 100644 index 00adb6b4f..000000000 --- a/src/ServiceStack.Text.WP/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Resources; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Text.WP")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ServiceStack.Text.WP")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8ca3569c-1483-4469-aacd-2994c5248974")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/src/ServiceStack.Text.WP/ServiceStack.Text.WP.csproj b/src/ServiceStack.Text.WP/ServiceStack.Text.WP.csproj deleted file mode 100644 index a96b87952..000000000 --- a/src/ServiceStack.Text.WP/ServiceStack.Text.WP.csproj +++ /dev/null @@ -1,276 +0,0 @@ - - - - Debug - AnyCPU - 10.0.20506 - 2.0 - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49} - {C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - ServiceStack.Text.WP - ServiceStack.Text.WP - v4.0 - $(TargetFrameworkVersion) - WindowsPhone71 - Silverlight - false - true - true - - - true - full - false - Bin\Debug - DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE - true - true - prompt - 4 - - - pdbonly - true - Bin\Release - TRACE;SILVERLIGHT;WINDOWS_PHONE - true - true - prompt - 4 - Bin\Release\ServiceStack.Text.WP.XML - - - - - - - - - - - - - AssemblyUtils.cs - - - CollectionExtensions.cs - - - Common\DateTimeSerializer.cs - - - Common\DeserializeArray.cs - - - Common\DeserializeBuiltin.cs - - - Common\DeserializeCollection.cs - - - Common\DeserializeDictionary.cs - - - Common\DeserializeListWithElements.cs - - - Common\DeserializeSpecializedCollections.cs - - - Common\DeserializeType.cs - - - Common\DeserializeTypeRef.cs - - - Common\DeserializeTypeRefJson.cs - - - Common\DeserializeTypeRefJsv.cs - - - Common\DeserializeTypeUtils.cs - - - Common\ITypeSerializer.cs - - - Common\JsDelegates.cs - - - Common\JsReader.cs - - - Common\JsState.cs - - - Common\JsWriter.cs - - - Common\ParseUtils.cs - - - Common\StaticParseMethod.cs - - - Common\WriteDictionary.cs - - - Common\WriteLists.cs - - - Common\WriteType.cs - - - Controller\CommandProcessor.cs - - - Controller\PathInfo.cs - - - CsvAttribute.cs - - - CsvConfig.cs - - - CsvSerializer.cs - - - CsvStreamExtensions.cs - - - CsvWriter.cs - - - DateTimeExtensions.cs - - - Env.cs - - - HashSet.cs - - - ITracer.cs - - - ITypeSerializer.Generic.cs - - - JsConfig.cs - - - JsonObject.cs - - - JsonSerializer.cs - - - JsonSerializer.Generic.cs - - - Json\JsonReader.Generic.cs - - - Json\JsonTypeSerializer.cs - - - Json\JsonUtils.cs - - - Json\JsonWriter.Generic.cs - - - JsvFormatter.cs - - - Jsv\JsvDeserializeType.cs - - - Jsv\JsvReader.Generic.cs - - - Jsv\JsvSerializer.Generic.cs - - - Jsv\JsvTypeSerializer.cs - - - Jsv\JsvWriter.Generic.cs - - - ListExtensions.cs - - - MapExtensions.cs - - - Marc\Link.cs - - - Marc\ObjectAccessor.cs - - - Marc\TypeAccessor.cs - - - QueryStringSerializer.cs - - - ReflectionExtensions.cs - - - Reflection\StaticAccessors.cs - - - StreamExtensions.cs - - - StringExtensions.cs - - - Support\AssemblyTypeDefinition.cs - - - Support\DoubleConverter.cs - - - Support\TypePair.cs - - - TextExtensions.cs - - - Tracer.cs - - - TranslateListWithElements.cs - - - TypeConfig.cs - - - TypeSerializer.cs - - - TypeSerializer.Generic.cs - - - XmlSerializer.cs - - - - - - - - \ No newline at end of file diff --git a/src/ServiceStack.Text.WP/ServiceStack.Text.WP.sln b/src/ServiceStack.Text.WP/ServiceStack.Text.WP.sln deleted file mode 100644 index bf4a1f88b..000000000 --- a/src/ServiceStack.Text.WP/ServiceStack.Text.WP.sln +++ /dev/null @@ -1,30 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.WP", "ServiceStack.Text.WP.csproj", "{0F6EB1D6-D49D-4D55-8F63-500666DA2E49}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|Xbox 360 = Debug|Xbox 360 - Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|Xbox 360 = Release|Xbox 360 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Debug|Xbox 360.ActiveCfg = Debug|Any CPU - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Release|Any CPU.Build.0 = Release|Any CPU - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0F6EB1D6-D49D-4D55-8F63-500666DA2E49}.Release|Xbox 360.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/ServiceStack.Text.XBox360/Properties/AssemblyInfo.cs b/src/ServiceStack.Text.XBox360/Properties/AssemblyInfo.cs deleted file mode 100644 index bad11785e..000000000 --- a/src/ServiceStack.Text.XBox360/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Text.XBox360")] -[assembly: AssemblyProduct("ServiceStack.Text.XBox360")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. Only Windows -// assemblies support COM. -[assembly: ComVisible(false)] - -// On Windows, the following GUID is for the ID of the typelib if this -// project is exposed to COM. On other platforms, it unique identifies the -// title storage container when deploying this assembly to the device. -[assembly: Guid("bec96c96-54be-440b-a791-2c0e1c12d6f7")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -[assembly: AssemblyVersion("1.0.0.0")] diff --git a/src/ServiceStack.Text.XBox360/ServiceStack.Text.XBox360.csproj b/src/ServiceStack.Text.XBox360/ServiceStack.Text.XBox360.csproj deleted file mode 100644 index 97014c874..000000000 --- a/src/ServiceStack.Text.XBox360/ServiceStack.Text.XBox360.csproj +++ /dev/null @@ -1,78 +0,0 @@ - - - - {E2B0C358-6CC5-4D15-AD73-41730FBF5530} - {6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Debug - Xbox 360 - Library - Properties - ServiceStack.Text.XBox360 - ServiceStack.Text.XBox360 - v4.0 - Client - v4.0 - Xbox 360 - HiDef - 0ed135ea-1216-457e-bc3e-9a191a60dafa - Library - - - true - full - false - bin\Xbox 360\Debug - DEBUG;TRACE;XBOX;XBOX360 - prompt - 4 - true - false - true - - - pdbonly - true - bin\Xbox 360\Release - TRACE;XBOX;XBOX360 - prompt - 4 - true - false - true - - - - - - - - - - - - - - - - 4.0 - - - 4.0 - - - - - - - - - - - \ No newline at end of file diff --git a/src/ServiceStack.Text.sln b/src/ServiceStack.Text.sln index 38d3211eb..a2c32f26d 100644 --- a/src/ServiceStack.Text.sln +++ b/src/ServiceStack.Text.sln @@ -1,84 +1,62 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{F7FB50ED-EAFF-4839-935A-5BB4A4158245}" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2009 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{F7FB50ED-EAFF-4839-935A-5BB4A4158245}" ProjectSection(SolutionItems) = preProject ..\build\build.bat = ..\build\build.bat - ..\build.cmd = ..\build.cmd - ..\build\NuGetPack.cmd = ..\build\NuGetPack.cmd - ..\NuGet\servicestack.text.nuspec = ..\NuGet\servicestack.text.nuspec + ..\build\build.proj = ..\build\build.proj + ..\build\build.tasks = ..\build\build.tasks + ..\README.md = ..\README.md + Directory.Build.props = Directory.Build.props + ServiceStack.Text\ServiceStack.Text.Core.csproj = ServiceStack.Text\ServiceStack.Text.Core.csproj + ..\build\build-core.proj = ..\build\build-core.proj + ..\tests\Directory.Build.props = ..\tests\Directory.Build.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text", "ServiceStack.Text\ServiceStack.Text.csproj", "{579B3FDB-CDAD-44E1-8417-885C38E49A0E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Text", "ServiceStack.Text\ServiceStack.Text.csproj", "{579B3FDB-CDAD-44E1-8417-885C38E49A0E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.Tests", "..\tests\ServiceStack.Text.Tests\ServiceStack.Text.Tests.csproj", "{9770BD40-AA3B-4785-B5E0-F4C470F9F14E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Text.Tests", "..\tests\ServiceStack.Text.Tests\ServiceStack.Text.Tests.csproj", "{9770BD40-AA3B-4785-B5E0-F4C470F9F14E}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{EEDB2902-813A-4277-837B-74F1E67E9759}" - ProjectSection(SolutionItems) = preProject - .nuget\NuGet.Config = .nuget\NuGet.Config - .nuget\NuGet.exe = .nuget\NuGet.exe - .nuget\NuGet.targets = .nuget\NuGet.targets - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Text.TestsConsole", "..\tests\ServiceStack.Text.TestsConsole\ServiceStack.Text.TestsConsole.csproj", "{DD3BEB33-2509-423A-8545-CE1A83684530}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.Benchmarks", "..\tests\ServiceStack.Text.Benchmarks\ServiceStack.Text.Benchmarks.csproj", "{FF1E5617-FD95-496F-B591-17DA6E0B364F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Northwind.Common", "..\tests\Northwind.Common\Northwind.Common.csproj", "{573926E5-F332-462D-A740-31706708A032}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|x86 = Debug|x86 - Debug|Xbox 360 = Debug|Xbox 360 - MonoTouch|Any CPU = MonoTouch|Any CPU - MonoTouch|Mixed Platforms = MonoTouch|Mixed Platforms - MonoTouch|x86 = MonoTouch|x86 - MonoTouch|Xbox 360 = MonoTouch|Xbox 360 Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|x86 = Release|x86 - Release|Xbox 360 = Release|Xbox 360 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Debug|x86.ActiveCfg = Debug|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Debug|Xbox 360.ActiveCfg = Debug|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.MonoTouch|Any CPU.ActiveCfg = MonoTouch|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.MonoTouch|Any CPU.Build.0 = MonoTouch|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.MonoTouch|Mixed Platforms.ActiveCfg = MonoTouch|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.MonoTouch|Mixed Platforms.Build.0 = MonoTouch|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.MonoTouch|x86.ActiveCfg = MonoTouch|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.MonoTouch|Xbox 360.ActiveCfg = MonoTouch|Any CPU {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Release|Any CPU.Build.0 = Release|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Release|x86.ActiveCfg = Release|Any CPU - {579B3FDB-CDAD-44E1-8417-885C38E49A0E}.Release|Xbox 360.ActiveCfg = Release|Any CPU {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Debug|Mixed Platforms.Build.0 = Debug|x86 - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Debug|x86.ActiveCfg = Debug|x86 - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Debug|x86.Build.0 = Debug|x86 - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Debug|Xbox 360.ActiveCfg = Debug|Any CPU - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.MonoTouch|Any CPU.ActiveCfg = MonoTouch|Any CPU - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.MonoTouch|Any CPU.Build.0 = MonoTouch|Any CPU - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.MonoTouch|Mixed Platforms.ActiveCfg = MonoTouch|Any CPU - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.MonoTouch|Mixed Platforms.Build.0 = MonoTouch|Any CPU - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.MonoTouch|x86.ActiveCfg = MonoTouch|x86 - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.MonoTouch|x86.Build.0 = MonoTouch|x86 - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.MonoTouch|Xbox 360.ActiveCfg = MonoTouch|Any CPU {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Release|Any CPU.ActiveCfg = Release|Any CPU {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Release|Any CPU.Build.0 = Release|Any CPU - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Release|x86.ActiveCfg = Release|x86 - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Release|x86.Build.0 = Release|x86 - {9770BD40-AA3B-4785-B5E0-F4C470F9F14E}.Release|Xbox 360.ActiveCfg = Release|Any CPU + {DD3BEB33-2509-423A-8545-CE1A83684530}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD3BEB33-2509-423A-8545-CE1A83684530}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD3BEB33-2509-423A-8545-CE1A83684530}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD3BEB33-2509-423A-8545-CE1A83684530}.Release|Any CPU.Build.0 = Release|Any CPU + {FF1E5617-FD95-496F-B591-17DA6E0B364F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF1E5617-FD95-496F-B591-17DA6E0B364F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF1E5617-FD95-496F-B591-17DA6E0B364F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF1E5617-FD95-496F-B591-17DA6E0B364F}.Release|Any CPU.Build.0 = Release|Any CPU + {573926E5-F332-462D-A740-31706708A032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {573926E5-F332-462D-A740-31706708A032}.Release|Any CPU.ActiveCfg = Release|Any CPU + {573926E5-F332-462D-A740-31706708A032}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {850F0B33-9062-4EE4-9C6D-4200AFA546D6} + EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = ServiceStack.Text\ServiceStack.Text.csproj Policies = $0 @@ -86,7 +64,6 @@ Global $1.DirectoryNamespaceAssociation = None $1.ResourceNamePolicy = FileFormatDefault $0.StandardHeader = $2 - $2.Text = $2.IncludeInNewFiles = True EndGlobalSection EndGlobal diff --git a/src/ServiceStack.Text/AssemblyUtils.cs b/src/ServiceStack.Text/AssemblyUtils.cs index 413eddc69..e22e260e5 100644 --- a/src/ServiceStack.Text/AssemblyUtils.cs +++ b/src/ServiceStack.Text/AssemblyUtils.cs @@ -4,9 +4,6 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; -#if SILVERLIGHT - -#endif using System.Threading; using ServiceStack.Common.Support; @@ -18,13 +15,10 @@ namespace ServiceStack.Text public static class AssemblyUtils { private const string FileUri = "file:///"; - private const string DllExt = "dll"; - private const string ExeExt = "dll"; private const char UriSeperator = '/'; private static Dictionary TypeCache = new Dictionary(); -#if !XBOX /// /// Find the type from the name supplied /// @@ -32,12 +26,9 @@ public static class AssemblyUtils /// public static Type FindType(string typeName) { - Type type = null; - if (TypeCache.TryGetValue(typeName, out type)) return type; + if (TypeCache.TryGetValue(typeName, out var type)) return type; -#if !SILVERLIGHT type = Type.GetType(typeName); -#endif if (type == null) { var typeDef = new AssemblyTypeDefinition(typeName); @@ -50,31 +41,28 @@ public static Type FindType(string typeName) do { snapshot = TypeCache; - newCache = new Dictionary(TypeCache); - newCache[typeName] = type; + newCache = new Dictionary(TypeCache) { [typeName] = type }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref TypeCache, newCache, snapshot), snapshot)); return type; } -#endif - -#if !XBOX - - /// - /// The top-most interface of the given type, if any. - /// - public static Type MainInterface() { - var t = typeof(T); - if (t.BaseType == typeof(object)) { - // on Windows, this can be just "t.GetInterfaces()" but Mono doesn't return in order. - var interfaceType = t.GetInterfaces().FirstOrDefault(i => !t.GetInterfaces().Any(i2 => i2.GetInterfaces().Contains(i))); - if (interfaceType != null) return interfaceType; - } - return t; // not safe to use interface, as it might be a superclass's one. - } + /// + /// The top-most interface of the given type, if any. + /// + public static Type MainInterface() + { + var t = typeof(T); + if (t.BaseType == typeof(object)) + { + // on Windows, this can be just "t.GetInterfaces()" but Mono doesn't return in order. + var interfaceType = t.GetInterfaces().FirstOrDefault(i => !t.GetInterfaces().Any(i2 => i2.GetInterfaces().Contains(i))); + if (interfaceType != null) return interfaceType; + } + return t; // not safe to use interface, as it might be a superclass one. + } /// /// Find type if it exists @@ -89,30 +77,13 @@ public static Type FindType(string typeName, string assemblyName) { return type; } - var binPath = GetAssemblyBinPath(Assembly.GetExecutingAssembly()); - Assembly assembly = null; - var assemblyDllPath = binPath + String.Format("{0}.{1}", assemblyName, DllExt); - if (File.Exists(assemblyDllPath)) - { - assembly = LoadAssembly(assemblyDllPath); - } - var assemblyExePath = binPath + String.Format("{0}.{1}", assemblyName, ExeExt); - if (File.Exists(assemblyExePath)) - { - assembly = LoadAssembly(assemblyExePath); - } - return assembly != null ? assembly.GetType(typeName) : null; + + return PclExport.Instance.FindType(typeName, assemblyName); } -#endif -#if !XBOX public static Type FindTypeFromLoadedAssemblies(string typeName) { -#if SILVERLIGHT4 - var assemblies = ((dynamic) AppDomain.CurrentDomain).GetAssemblies() as Assembly[]; -#else - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); -#endif + var assemblies = PclExport.Instance.GetAllAssemblies(); foreach (var assembly in assemblies) { var type = assembly.GetType(typeName); @@ -123,52 +94,25 @@ public static Type FindTypeFromLoadedAssemblies(string typeName) } return null; } -#endif -#if !SILVERLIGHT -private static Assembly LoadAssembly(string assemblyPath) - { - return Assembly.LoadFrom(assemblyPath); - } -#elif WINDOWS_PHONE - private static Assembly LoadAssembly(string assemblyPath) - { - return Assembly.LoadFrom(assemblyPath); - } -#else - private static Assembly LoadAssembly(string assemblyPath) + public static Assembly LoadAssembly(string assemblyPath) { - var sri = System.Windows.Application.GetResourceStream(new Uri(assemblyPath, UriKind.Relative)); - var myPart = new System.Windows.AssemblyPart(); - var assembly = myPart.Load(sri.Stream); - return assembly; + return PclExport.Instance.LoadAssembly(assemblyPath); } -#endif -#if !XBOX public static string GetAssemblyBinPath(Assembly assembly) { -#if WINDOWS_PHONE - var codeBase = assembly.GetName().CodeBase; -#else - var codeBase = assembly.CodeBase; -#endif - + var codeBase = PclExport.Instance.GetAssemblyCodeBase(assembly); var binPathPos = codeBase.LastIndexOf(UriSeperator); var assemblyPath = codeBase.Substring(0, binPathPos + 1); - if (assemblyPath.StartsWith(FileUri)) + if (assemblyPath.StartsWith(FileUri, StringComparison.OrdinalIgnoreCase)) { assemblyPath = assemblyPath.Remove(0, FileUri.Length); } return assemblyPath; } -#endif -#if !SILVERLIGHT - static readonly Regex versionRegEx = new Regex(", Version=[^\\]]+", RegexOptions.Compiled); -#else - static readonly Regex versionRegEx = new Regex(", Version=[^\\]]+"); -#endif + static readonly Regex versionRegEx = new Regex(", Version=[^\\]]+", PclExport.Instance.RegexOptions); public static string ToTypeString(this Type type) { return versionRegEx.Replace(type.AssemblyQualifiedName, ""); diff --git a/src/ServiceStack.Text/AutoMappingUtils.cs b/src/ServiceStack.Text/AutoMappingUtils.cs new file mode 100644 index 000000000..15e15bd2b --- /dev/null +++ b/src/ServiceStack.Text/AutoMappingUtils.cs @@ -0,0 +1,1252 @@ +// Copyright (c) ServiceStack, Inc. All Rights Reserved. +// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Threading; +using ServiceStack.Text; +using ServiceStack.Text.Common; + +namespace ServiceStack +{ + [DataContract(Namespace = "http://schemas.servicestack.net/types")] + public class CustomHttpResult { } + + /// + /// Customize ServiceStack AutoMapping Behavior + /// + public static class AutoMapping + { + /// + /// Register Type to Type AutoMapping converter + /// + public static void RegisterConverter(Func converter) + { + JsConfig.InitStatics(); + AutoMappingUtils.converters[Tuple.Create(typeof(From), typeof(To))] = x => converter((From)x); + } + + /// + /// Ignore Type to Type Mapping (including collections containing them) + /// + public static void IgnoreMapping() => IgnoreMapping(typeof(From), typeof(To)); + + /// + /// Ignore Type to Type Mapping (including collections containing them) + /// + public static void IgnoreMapping(Type fromType, Type toType) + { + JsConfig.InitStatics(); + AutoMappingUtils.ignoreMappings[Tuple.Create(fromType, toType)] = true; + } + + public static void RegisterPopulator(Action populator) + { + JsConfig.InitStatics(); + AutoMappingUtils.populators[Tuple.Create(typeof(Target), typeof(Source))] = (a,b) => populator((Target)a,(Source)b); + } + } + + public static class AutoMappingUtils + { + internal static readonly ConcurrentDictionary, GetMemberDelegate> converters + = new ConcurrentDictionary, GetMemberDelegate>(); + + internal static readonly ConcurrentDictionary, PopulateMemberDelegate> populators + = new ConcurrentDictionary, PopulateMemberDelegate>(); + + internal static readonly ConcurrentDictionary,bool> ignoreMappings + = new ConcurrentDictionary,bool>(); + + public static void Reset() + { + converters.Clear(); + populators.Clear(); + ignoreMappings.Clear(); + AssignmentDefinitionCache.Clear(); + } + + public static bool ShouldIgnoreMapping(Type fromType, Type toType) => + ignoreMappings.ContainsKey(Tuple.Create(fromType, toType)); + + public static GetMemberDelegate GetConverter(Type fromType, Type toType) + { + if (converters.IsEmpty) + return null; + + var key = Tuple.Create(fromType, toType); + return converters.TryGetValue(key, out var converter) + ? converter + : null; + } + + public static PopulateMemberDelegate GetPopulator(Type targetType, Type sourceType) + { + if (populators.IsEmpty) + return null; + + var key = Tuple.Create(targetType, sourceType); + return populators.TryGetValue(key, out var populator) + ? populator + : null; + } + + public static T ConvertTo(this object from, T defaultValue) => + from == null || (from is string s && s == string.Empty) + ? defaultValue + : from.ConvertTo(); + + public static T ConvertTo(this object from) => from.ConvertTo(skipConverters:false); + public static T ConvertTo(this object from, bool skipConverters) + { + if (from == null) + return default; + + if (from is T t) + return t; + + return (T)ConvertTo(from, typeof(T), skipConverters); + } + + public static T CreateCopy(this T from) + { + if (typeof(T).IsValueType) + return (T)ChangeValueType(from, typeof(T)); + + if (typeof(IEnumerable).IsAssignableFrom(typeof(T)) && typeof(T) != typeof(string)) + { + var listResult = TranslateListWithElements.TryTranslateCollections(from.GetType(), typeof(T), from); + return (T)listResult; + } + + var to = typeof(T).CreateInstance(); + return to.PopulateWith(from); + } + + public static To ThenDo(this To to, Action fn) + { + fn(to); + return to; + } + + public static object ConvertTo(this object from, Type toType) => from.ConvertTo(toType, skipConverters: false); + public static object ConvertTo(this object from, Type toType, bool skipConverters) + { + if (from == null) + return null; + + var fromType = from.GetType(); + if (ShouldIgnoreMapping(fromType, toType)) + return null; + + if (!skipConverters) + { + var converter = GetConverter(fromType, toType); + if (converter != null) + return converter(from); + } + + if (fromType == toType || toType == typeof(object)) + return from; + + if (fromType.IsValueType || toType.IsValueType) + return ChangeValueType(from, toType); + + var mi = GetImplicitCastMethod(fromType, toType); + if (mi != null) + return mi.Invoke(null, new[] { from }); + + if (from is string str) + return TypeSerializer.DeserializeFromString(str, toType); + if (from is ReadOnlyMemory rom) + return TypeSerializer.DeserializeFromSpan(toType, rom.Span); + + if (toType == typeof(string)) + return from.ToJsv(); + + if (typeof(IEnumerable).IsAssignableFrom(toType)) + { + var listResult = TryConvertCollections(fromType, toType, from); + return listResult; + } + + if (from is IEnumerable> objDict) + return objDict.FromObjectDictionary(toType); + + if (from is IEnumerable> strDict) + return strDict.ToObjectDictionary().FromObjectDictionary(toType); + + var to = toType.CreateInstance(); + return to.PopulateWith(from); + } + + public static MethodInfo GetImplicitCastMethod(Type fromType, Type toType) + { + foreach (var mi in fromType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (mi.Name == "op_Implicit" && mi.ReturnType == toType && + mi.GetParameters().FirstOrDefault()?.ParameterType == fromType) + { + return mi; + } + } + foreach (var mi in toType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (mi.Name == "op_Implicit" && mi.ReturnType == toType && + mi.GetParameters().FirstOrDefault()?.ParameterType == fromType) + { + return mi; + } + } + return null; + } + + public static MethodInfo GetExplicitCastMethod(Type fromType, Type toType) + { + foreach (var mi in toType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (mi.Name == "op_Explicit" && mi.ReturnType == toType && + mi.GetParameters().FirstOrDefault()?.ParameterType == fromType) + { + return mi; + } + } + foreach (var mi in fromType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (mi.Name == "op_Explicit" && mi.ReturnType == toType && + mi.GetParameters().FirstOrDefault()?.ParameterType == fromType) + { + return mi; + } + } + return null; + } + + public static object ChangeValueType(object from, Type toType) + { + var s = from as string; + + var fromType = from.GetType(); + if (!fromType.IsEnum && !toType.IsEnum) + { + var toString = toType == typeof(string); + if (toType == typeof(char) && s != null) + return s.Length > 0 ? (object) s[0] : null; + if (toString && from is char c) + return c.ToString(); + if (toType == typeof(TimeSpan) && from is long ticks) + return new TimeSpan(ticks); + if (toType == typeof(long) && from is TimeSpan time) + return time.Ticks; + + var destNumberType = DynamicNumber.GetNumber(toType); + if (destNumberType != null) + { + if (s != null && s == string.Empty) + return destNumberType.DefaultValue; + + var value = destNumberType.ConvertFrom(from); + if (value != null) + { + return toType == typeof(char) + ? value.ToString()[0] + : value; + } + } + + if (toString) + { + var srcNumberType = DynamicNumber.GetNumber(from.GetType()); + if (srcNumberType != null) + return srcNumberType.ToString(from); + } + } + + var mi = GetImplicitCastMethod(fromType, toType); + if (mi != null) + return mi.Invoke(null, new[] { from }); + + mi = GetExplicitCastMethod(fromType, toType); + if (mi != null) + return mi.Invoke(null, new[] { from }); + + if (s != null) + return TypeSerializer.DeserializeFromString(s, toType); + + if (toType == typeof(string)) + return from.ToJsv(); + + if (toType.HasInterface(typeof(IConvertible))) + { + return Convert.ChangeType(from, toType, provider: null); + } + + var fromKvpType = fromType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + if (fromKvpType != null) + { + var fromProps = TypeProperties.Get(fromKvpType); + var fromKey = fromProps.GetPublicGetter("Key")(from); + var fromValue = fromProps.GetPublicGetter("Value")(from); + + var toKvpType = toType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + if (toKvpType != null) + { + + var toKvpArgs = toKvpType.GetGenericArguments(); + var toCtor = toKvpType.GetConstructor(toKvpArgs); + var to = toCtor.Invoke(new[] { fromKey.ConvertTo(toKvpArgs[0]), fromValue.ConvertTo(toKvpArgs[1]) }); + return to; + } + + if (typeof(IDictionary).IsAssignableFrom(toType)) + { + var genericDef = toType.GetTypeWithGenericTypeDefinitionOf(typeof(IDictionary<,>)); + var toArgs = genericDef.GetGenericArguments(); + var toKeyType = toArgs[0]; + var toValueType = toArgs[1]; + + var to = (IDictionary)toType.CreateInstance(); + to["Key"] = fromKey.ConvertTo(toKeyType); + to["Value"] = fromValue.ConvertTo(toValueType); + return to; + } + } + + return TypeSerializer.DeserializeFromString(from.ToJsv(), toType); + } + + public static object ChangeTo(this string strValue, Type type) + { + if (type.IsValueType && !type.IsEnum && type.HasInterface(typeof(IConvertible))) + { + try + { + return Convert.ChangeType(strValue, type, provider: null); + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + } + } + return TypeSerializer.DeserializeFromString(strValue, type); + } + + private static readonly Dictionary> TypePropertyNamesMap = new Dictionary>(); + + public static List GetPropertyNames(this Type type) + { + lock (TypePropertyNamesMap) + { + if (!TypePropertyNamesMap.TryGetValue(type, out var propertyNames)) + { + propertyNames = type.Properties().ToList().ConvertAll(x => x.Name); + TypePropertyNamesMap[type] = propertyNames; + } + return propertyNames; + } + } + + public static string GetAssemblyPath(this Type source) + { + return PclExport.Instance.GetAssemblyPath(source); + } + + public static bool IsDebugBuild(this Assembly assembly) + { + return PclExport.Instance.IsDebugBuild(assembly); + } + + /// + /// Populate an object with Example data. + /// + /// + /// + public static object PopulateWith(object obj) + { + if (obj == null) return null; + var isHttpResult = obj.GetType().GetInterfaces().Any(x => x.Name == "IHttpResult"); // No coupling FTW! + if (isHttpResult) + { + obj = new CustomHttpResult(); + } + + var type = obj.GetType(); + if (type.IsArray || type.IsValueType || type.IsGenericType) + { + var value = CreateDefaultValue(type, new Dictionary(20)); + return value; + } + + return PopulateObjectInternal(obj, new Dictionary(20)); + } + + /// + /// Populates the object with example data. + /// + /// + /// Tracks how deeply nested we are + /// + private static object PopulateObjectInternal(object obj, Dictionary recursionInfo) + { + if (obj == null) return null; + if (obj is string) return obj; // prevents it from dropping into the char[] Chars property. + var type = obj.GetType(); + + var members = type.GetPublicMembers(); + foreach (var info in members) + { + var fieldInfo = info as FieldInfo; + var propertyInfo = info as PropertyInfo; + if (fieldInfo != null || propertyInfo != null) + { + var memberType = fieldInfo != null ? fieldInfo.FieldType : propertyInfo.PropertyType; + var value = CreateDefaultValue(memberType, recursionInfo); + SetValue(fieldInfo, propertyInfo, obj, value); + } + } + return obj; + } + + private static Dictionary DefaultValueTypes = new Dictionary(); + + public static object GetDefaultValue(this Type type) + { + if (!type.IsValueType) + return null; + + if (DefaultValueTypes.TryGetValue(type, out var defaultValue)) + return defaultValue; + + defaultValue = Activator.CreateInstance(type); + + Dictionary snapshot, newCache; + do + { + snapshot = DefaultValueTypes; + newCache = new Dictionary(DefaultValueTypes) { [type] = defaultValue }; + + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref DefaultValueTypes, newCache, snapshot), snapshot)); + + return defaultValue; + } + + public static bool IsDefaultValue(object value) => IsDefaultValue(value, value?.GetType()); + public static bool IsDefaultValue(object value, Type valueType) => value == null + || (valueType.IsValueType && value.Equals(valueType.GetDefaultValue())); + + private static readonly ConcurrentDictionary AssignmentDefinitionCache + = new ConcurrentDictionary(); + + internal static AssignmentDefinition GetAssignmentDefinition(Type toType, Type fromType) + { + var cacheKey = CreateCacheKey(fromType, toType); + + return AssignmentDefinitionCache.GetOrAdd(cacheKey, delegate + { + var definition = new AssignmentDefinition + { + ToType = toType, + FromType = fromType, + }; + + var readMap = GetMembers(fromType, isReadable: true); + var writeMap = GetMembers(toType, isReadable: false); + + foreach (var assignmentMember in readMap) + { + if (writeMap.TryGetValue(assignmentMember.Key, out var writeMember)) + { + definition.AddMatch(assignmentMember.Key, assignmentMember.Value, writeMember); + } + } + + return definition; + }); + } + + internal static string CreateCacheKey(Type fromType, Type toType) + { + var cacheKey = fromType.FullName + ">" + toType.FullName; + return cacheKey; + } + + private static Dictionary GetMembers(Type type, bool isReadable) + { + var map = new Dictionary(); + + var members = type.GetAllPublicMembers(); + foreach (var info in members) + { + if (info.DeclaringType == typeof(object)) continue; + + var propertyInfo = info as PropertyInfo; + if (propertyInfo != null) + { + if (isReadable) + { + if (propertyInfo.CanRead) + { + map[info.Name] = new AssignmentMember(propertyInfo.PropertyType, propertyInfo); + continue; + } + } + else + { + if (propertyInfo.CanWrite && propertyInfo.GetSetMethod(nonPublic:true) != null) + { + map[info.Name] = new AssignmentMember(propertyInfo.PropertyType, propertyInfo); + continue; + } + } + } + + var fieldInfo = info as FieldInfo; + if (fieldInfo != null) + { + map[info.Name] = new AssignmentMember(fieldInfo.FieldType, fieldInfo); + continue; + } + } + return map; + } + + public static To PopulateWith(this To to, From from) + { + if (Equals(to, default(To)) || Equals(from, default(From))) return default(To); + + var assignmentDefinition = GetAssignmentDefinition(to.GetType(), from.GetType()); + + assignmentDefinition.Populate(to, from); + + return to; + } + + public static To PopulateWithNonDefaultValues(this To to, From from) + { + if (Equals(to, default(To)) || Equals(from, default(From))) return default(To); + + var assignmentDefinition = GetAssignmentDefinition(to.GetType(), from.GetType()); + + assignmentDefinition.PopulateWithNonDefaultValues(to, from); + + return to; + } + + public static To PopulateFromPropertiesWithAttribute(this To to, From from, + Type attributeType) + { + if (Equals(to, default(To)) || Equals(from, default(From))) return default(To); + + var assignmentDefinition = GetAssignmentDefinition(to.GetType(), from.GetType()); + + assignmentDefinition.PopulateFromPropertiesWithAttribute(to, from, attributeType); + + return to; + } + + public static To PopulateFromPropertiesWithoutAttribute(this To to, From from, + Type attributeType) + { + if (Equals(to, default(To)) || Equals(from, default(From))) return default(To); + + var assignmentDefinition = GetAssignmentDefinition(to.GetType(), from.GetType()); + + assignmentDefinition.PopulateFromPropertiesWithoutAttribute(to, from, attributeType); + + return to; + } + + public static void SetProperty(this PropertyInfo propertyInfo, object obj, object value) + { + if (!propertyInfo.CanWrite) + { + Tracer.Instance.WriteWarning("Attempted to set read only property '{0}'", propertyInfo.Name); + return; + } + + var propertySetMethodInfo = propertyInfo.GetSetMethod(nonPublic:true); + if (propertySetMethodInfo != null) + { + propertySetMethodInfo.Invoke(obj, new[] { value }); + } + } + + public static object GetProperty(this PropertyInfo propertyInfo, object obj) + { + if (propertyInfo == null || !propertyInfo.CanRead) + return null; + + var getMethod = propertyInfo.GetGetMethod(nonPublic:true); + return getMethod != null ? getMethod.Invoke(obj, TypeConstants.EmptyObjectArray) : null; + } + + public static void SetValue(FieldInfo fieldInfo, PropertyInfo propertyInfo, object obj, object value) + { + try + { + if (IsUnsettableValue(fieldInfo, propertyInfo)) return; + if (fieldInfo != null && !fieldInfo.IsLiteral) + { + fieldInfo.SetValue(obj, value); + } + else + { + SetProperty(propertyInfo, obj, value); + } + } + catch (Exception ex) + { + var name = (fieldInfo != null) ? fieldInfo.Name : propertyInfo.Name; + Tracer.Instance.WriteDebug("Could not set member: {0}. Error: {1}", name, ex.Message); + } + } + + public static bool IsUnsettableValue(FieldInfo fieldInfo, PropertyInfo propertyInfo) + { + // Properties on non-user defined classes should not be set + // Currently we define those properties as properties declared on + // types defined in mscorlib + + if (propertyInfo != null && propertyInfo.ReflectedType != null) + { + return PclExport.Instance.InSameAssembly(propertyInfo.DeclaringType, typeof(object)); + } + + return false; + } + + public static object[] CreateDefaultValues(IEnumerable types, Dictionary recursionInfo) + { + var values = new List(); + foreach (var type in types) + { + values.Add(CreateDefaultValue(type, recursionInfo)); + } + return values.ToArray(); + } + + private const int MaxRecursionLevelForDefaultValues = 2; // do not nest a single type more than this deep. + + public static object CreateDefaultValue(Type type, Dictionary recursionInfo) + { + if (type == typeof(string)) + { + return type.Name; + } + + if (type.IsEnum) + { + return Enum.GetValues(type).GetValue(0); + } + + if (type.IsAbstract) + return null; + + // If we have hit our recursion limit for this type, then return null + recursionInfo.TryGetValue(type, out var recurseLevel); + if (recurseLevel > MaxRecursionLevelForDefaultValues) return null; + + recursionInfo[type] = recurseLevel + 1; // increase recursion level for this type + try // use a try/finally block to make sure we decrease the recursion level for this type no matter which code path we take, + { + + //when using KeyValuePair, TKey must be non-default to stuff in a Dictionary + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + var genericTypes = type.GetGenericArguments(); + var valueType = Activator.CreateInstance(type, CreateDefaultValue(genericTypes[0], recursionInfo), CreateDefaultValue(genericTypes[1], recursionInfo)); + return PopulateObjectInternal(valueType, recursionInfo); + } + + if (type.IsValueType) + { + return type.CreateInstance(); + } + + if (type.IsArray) + { + return PopulateArray(type, recursionInfo); + } + + var constructorInfo = type.GetConstructor(Type.EmptyTypes); + var hasEmptyConstructor = constructorInfo != null; + + if (hasEmptyConstructor) + { + var value = constructorInfo.Invoke(TypeConstants.EmptyObjectArray); + + var genericCollectionType = PclExport.Instance.GetGenericCollectionType(type); + if (genericCollectionType != null) + { + SetGenericCollection(genericCollectionType, value, recursionInfo); + } + + //when the object might have nested properties such as enums with non-0 values, etc + return PopulateObjectInternal(value, recursionInfo); + } + return null; + } + finally + { + recursionInfo[type] = recurseLevel; + } + } + + public static void SetGenericCollection(Type realizedListType, object genericObj, Dictionary recursionInfo) + { + var args = realizedListType.GetGenericArguments(); + if (args.Length != 1) + { + Tracer.Instance.WriteError("Found a generic list that does not take one generic argument: {0}", realizedListType); + + return; + } + + var methodInfo = realizedListType.GetMethodInfo("Add"); + if (methodInfo != null) + { + var argValues = CreateDefaultValues(args, recursionInfo); + + methodInfo.Invoke(genericObj, argValues); + } + } + + public static Array PopulateArray(Type type, Dictionary recursionInfo) + { + var elementType = type.GetElementType(); + var objArray = Array.CreateInstance(elementType, 1); + var objElementType = CreateDefaultValue(elementType, recursionInfo); + objArray.SetValue(objElementType, 0); + + return objArray; + } + + //TODO: replace with InAssignableFrom + public static bool CanCast(Type toType, Type fromType) + { + if (toType.IsInterface) + { + var interfaceList = fromType.GetInterfaces().ToList(); + if (interfaceList.Contains(toType)) return true; + } + else + { + Type baseType = fromType; + bool areSameTypes; + do + { + areSameTypes = baseType == toType; + } + while (!areSameTypes && (baseType = fromType.BaseType) != null); + + if (areSameTypes) return true; + } + + return false; + } + + public static IEnumerable> GetPropertyAttributes(Type fromType) + { + var attributeType = typeof(T); + var baseType = fromType; + do + { + var propertyInfos = baseType.AllProperties(); + foreach (var propertyInfo in propertyInfos) + { + var attributes = propertyInfo.GetCustomAttributes(attributeType, true); + foreach (var attribute in attributes) + { + yield return new KeyValuePair(propertyInfo, (T)(object)attribute); + } + } + } + while ((baseType = baseType.BaseType) != null); + } + + public static object TryConvertCollections(Type fromType, Type toType, object fromValue) + { + if (fromValue is IEnumerable values) + { + var toEnumObjs = toType == typeof(IEnumerable); + if (typeof(IList).IsAssignableFrom(toType) || toEnumObjs) + { + var to = (IList) (toType.IsArray || toEnumObjs ? new List() : toType.CreateInstance()); + var elType = toType.GetCollectionType(); + foreach (var item in values) + { + to.Add(elType != null ? item.ConvertTo(elType) : item); + } + if (elType != null && toType.IsArray) + { + var arr = Array.CreateInstance(elType, to.Count); + to.CopyTo(arr, 0); + return arr; + } + + return to; + } + + if (fromValue is IDictionary d) + { + var obj = toType.CreateInstance(); + switch (obj) + { + case List> toList: { + foreach (var key in d.Keys) + { + toList.Add(new KeyValuePair(key.ConvertTo(), d[key].ConvertTo())); + } + return toList; + } + case List> toObjList: { + foreach (var key in d.Keys) + { + toObjList.Add(new KeyValuePair(key.ConvertTo(), d[key])); + } + return toObjList; + } + case IDictionary toDict: { + if (toType.GetKeyValuePairsTypes(out var toKeyType, out var toValueType)) + { + foreach (var key in d.Keys) + { + var toKey = toKeyType != null + ? key.ConvertTo(toKeyType) + : key; + var toValue = d[key].ConvertTo(toValueType); + toDict[toKey] = toValue; + } + return toDict; + } + else + { + var from = fromValue.ToObjectDictionary(); + var to = from.FromObjectDictionary(toType); + return to; + } + } + } + } + + var genericDef = fromType.GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + if (genericDef != null) + { + var genericEnumType = genericDef.GetGenericArguments()[0]; + var genericKvps = genericEnumType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + if (genericKvps != null) + { + // Improve perf with Specialized handling of common KVP combinations + var obj = toType.CreateInstance(); + if (fromValue is IEnumerable> sKvps) + { + switch (obj) { + case IDictionary toDict: { + toType.GetKeyValuePairsTypes(out var toKeyType, out var toValueType); + foreach (var entry in sKvps) + { + var toKey = toKeyType != null + ? entry.Key.ConvertTo(toKeyType) + : entry.Key; + toDict[toKey] = toValueType != null + ? entry.Value.ConvertTo(toValueType) + : entry.Value; + } + return toDict; + } + case List> toList: { + foreach (var entry in sKvps) + { + toList.Add(new KeyValuePair(entry.Key, entry.Value)); + } + return toList; + } + case List> toObjList: { + foreach (var entry in sKvps) + { + toObjList.Add(new KeyValuePair(entry.Key, entry.Value)); + } + return toObjList; + } + } + } + else if (fromValue is IEnumerable> oKvps) + { + switch (obj) { + case IDictionary toDict: + { + toType.GetKeyValuePairsTypes(out var toKeyType, out var toValueType); + foreach (var entry in oKvps) + { + var toKey = entry.Key.ConvertTo(); + toDict[toKey] = toValueType != null + ? entry.Value.ConvertTo(toValueType) + : entry.Value; + } + return toDict; + } + case List> toList: { + foreach (var entry in oKvps) + { + toList.Add(new KeyValuePair(entry.Key, entry.Value.ConvertTo())); + } + return toList; + } + case List> toObjList: { + foreach (var entry in oKvps) + { + toObjList.Add(new KeyValuePair(entry.Key, entry.Value)); + } + return toObjList; + } + } + } + + + // Fallback for handling any KVP combo + var toKvpDefType = toType.GetKeyValuePairsTypeDef(); + switch (obj) { + case IDictionary toDict: + { + var keyProp = TypeProperties.Get(toKvpDefType).GetPublicGetter("Key"); + var valueProp = TypeProperties.Get(toKvpDefType).GetPublicGetter("Value"); + + foreach (var entry in values) + { + var toKvp = entry.ConvertTo(toKvpDefType); + var toKey = keyProp(toKvp); + var toValue = valueProp(toKvp); + toDict[toKey] = toValue; + } + return toDict; + } + case List> toStringList: { + foreach (var entry in values) + { + var toEntry = entry.ConvertTo(toKvpDefType); + toStringList.Add((KeyValuePair) toEntry); + } + return toStringList; + } + case List> toObjList: { + foreach (var entry in values) + { + var toEntry = entry.ConvertTo(toKvpDefType); + toObjList.Add((KeyValuePair) toEntry); + } + return toObjList; + } + case IEnumerable toList: + { + var addMethod = toType.GetMethod(nameof(IList.Add), new[] {toKvpDefType}); + if (addMethod != null) + { + foreach (var entry in values) + { + var toEntry = entry.ConvertTo(toKvpDefType); + addMethod.Invoke(toList, new[] { toEntry }); + } + return toList; + } + break; + } + } + } + } + + var fromElementType = fromType.GetCollectionType(); + var toElementType = toType.GetCollectionType(); + + if (fromElementType != null && toElementType != null && fromElementType != toElementType && + !(typeof(IDictionary).IsAssignableFrom(fromElementType) || typeof(IDictionary).IsAssignableFrom(toElementType))) + { + var to = new List(); + foreach (var item in values) + { + var toItem = item.ConvertTo(toElementType); + to.Add(toItem); + } + var ret = TranslateListWithElements.TryTranslateCollections(to.GetType(), toType, to); + return ret ?? fromValue; + } + } + else if (fromType.IsClass && + (typeof(IDictionary).IsAssignableFrom(toType) || + typeof(IEnumerable>).IsAssignableFrom(toType) || + typeof(IEnumerable>).IsAssignableFrom(toType))) + { + var fromDict = fromValue.ToObjectDictionary(); + return TryConvertCollections(fromType.GetType(), toType, fromDict); + } + + var listResult = TranslateListWithElements.TryTranslateCollections(fromType, toType, fromValue); + return listResult ?? fromValue; + } + } + + public class AssignmentEntry + { + public string Name; + public AssignmentMember From; + public AssignmentMember To; + public GetMemberDelegate GetValueFn; + public SetMemberDelegate SetValueFn; + public GetMemberDelegate ConvertValueFn; + + public AssignmentEntry(string name, AssignmentMember @from, AssignmentMember to) + { + Name = name; + From = @from; + To = to; + + GetValueFn = From.CreateGetter(); + SetValueFn = To.CreateSetter(); + ConvertValueFn = TypeConverter.CreateTypeConverter(From.Type, To.Type); + } + } + + public class AssignmentMember + { + public AssignmentMember(Type type, PropertyInfo propertyInfo) + { + Type = type; + PropertyInfo = propertyInfo; + } + + public AssignmentMember(Type type, FieldInfo fieldInfo) + { + Type = type; + FieldInfo = fieldInfo; + } + + public AssignmentMember(Type type, MethodInfo methodInfo) + { + Type = type; + MethodInfo = methodInfo; + } + + public Type Type; + public PropertyInfo PropertyInfo; + public FieldInfo FieldInfo; + public MethodInfo MethodInfo; + + public GetMemberDelegate CreateGetter() + { + if (PropertyInfo != null) + return PropertyInfo.CreateGetter(); + if (FieldInfo != null) + return FieldInfo.CreateGetter(); + return (GetMemberDelegate) MethodInfo?.CreateDelegate(typeof(GetMemberDelegate)); + } + + public SetMemberDelegate CreateSetter() + { + if (PropertyInfo != null) + return PropertyInfo.CreateSetter(); + if (FieldInfo != null) + return FieldInfo.CreateSetter(); + return (SetMemberDelegate) MethodInfo?.MakeDelegate(typeof(SetMemberDelegate)); + } + } + + internal class AssignmentDefinition + { + public AssignmentDefinition() + { + this.AssignmentMemberMap = new Dictionary(); + } + + public Type FromType { get; set; } + public Type ToType { get; set; } + + public Dictionary AssignmentMemberMap { get; set; } + + public void AddMatch(string name, AssignmentMember readMember, AssignmentMember writeMember) + { + if (AutoMappingUtils.ShouldIgnoreMapping(readMember.Type,writeMember.Type)) + return; + + // Ignore mapping collections if Element Types are ignored + if (typeof(IEnumerable).IsAssignableFrom(readMember.Type) && typeof(IEnumerable).IsAssignableFrom(writeMember.Type)) + { + var fromGenericDef = readMember.Type.GetTypeWithGenericTypeDefinitionOf(typeof(IDictionary<,>)); + var toGenericDef = writeMember.Type.GetTypeWithGenericTypeDefinitionOf(typeof(IDictionary<,>)); + if (fromGenericDef != null && toGenericDef != null) + { + // Check if to/from Key or Value Types are ignored + var fromArgs = fromGenericDef.GetGenericArguments(); + var toArgs = toGenericDef.GetGenericArguments(); + if (AutoMappingUtils.ShouldIgnoreMapping(fromArgs[0],toArgs[0])) + return; + if (AutoMappingUtils.ShouldIgnoreMapping(fromArgs[1],toArgs[1])) + return; + } + else if (readMember.Type != typeof(string) && writeMember.Type != typeof(string)) + { + var elFromType = readMember.Type.GetCollectionType(); + var elToType = writeMember.Type.GetCollectionType(); + + if (AutoMappingUtils.ShouldIgnoreMapping(elFromType,elToType)) + return; + } + } + + this.AssignmentMemberMap[name] = new AssignmentEntry(name, readMember, writeMember); + } + + public void PopulateFromPropertiesWithAttribute(object to, object from, Type attributeType) + { + var hasAttributePredicate = (Func) + (x => x.AllAttributes(attributeType).Length > 0); + Populate(to, from, hasAttributePredicate, null); + } + + public void PopulateFromPropertiesWithoutAttribute(object to, object from, Type attributeType) + { + var hasAttributePredicate = (Func) + (x => x.AllAttributes(attributeType).Length == 0); + Populate(to, from, hasAttributePredicate, null); + } + + public void PopulateWithNonDefaultValues(object to, object from) + { + var nonDefaultPredicate = (Func)((x, t) => + x != null && !Equals(x, t.GetDefaultValue()) + ); + + Populate(to, from, null, nonDefaultPredicate); + } + + public void Populate(object to, object from) + { + Populate(to, from, null, null); + } + + public void Populate(object to, object from, + Func propertyInfoPredicate, + Func valuePredicate) + { + foreach (var assignmentEntryMap in AssignmentMemberMap) + { + var assignmentEntry = assignmentEntryMap.Value; + var fromMember = assignmentEntry.From; + var toMember = assignmentEntry.To; + + if (fromMember.PropertyInfo != null && propertyInfoPredicate != null) + { + if (!propertyInfoPredicate(fromMember.PropertyInfo)) + continue; + } + + var fromType = fromMember.Type; + var toType = toMember.Type; + try + { + + var fromValue = assignmentEntry.GetValueFn(from); + + if (valuePredicate != null + && fromType == toType) // don't short-circuit nullable <-> non-null values + { + if (!valuePredicate(fromValue, fromMember.PropertyInfo.PropertyType)) + continue; + } + + if (assignmentEntry.ConvertValueFn != null) + { + fromValue = assignmentEntry.ConvertValueFn(fromValue); + } + + var setterFn = assignmentEntry.SetValueFn; + setterFn(to, fromValue); + } + catch (Exception ex) + { + Tracer.Instance.WriteWarning("Error trying to set properties {0}.{1} > {2}.{3}:\n{4}", + FromType.FullName, fromType.Name, + ToType.FullName, toType.Name, ex); + } + } + + var populator = AutoMappingUtils.GetPopulator(to.GetType(), from.GetType()); + populator?.Invoke(to, from); + } + } + + public delegate object GetMemberDelegate(object instance); + public delegate object GetMemberDelegate(T instance); + + public delegate void PopulateMemberDelegate(object target, object source); + + public delegate void SetMemberDelegate(object instance, object value); + public delegate void SetMemberDelegate(T instance, object value); + public delegate void SetMemberRefDelegate(ref object instance, object propertyValue); + public delegate void SetMemberRefDelegate(ref T instance, object value); + + internal static class TypeConverter + { + public static GetMemberDelegate CreateTypeConverter(Type fromType, Type toType) + { + if (fromType == toType) + return null; + + var converter = AutoMappingUtils.GetConverter(fromType, toType); + if (converter != null) + return converter; + + if (fromType == typeof(string)) + return fromValue => TypeSerializer.DeserializeFromString((string)fromValue, toType); + + if (toType == typeof(string)) + return o => TypeSerializer.SerializeToString(o).StripQuotes(); + + var underlyingToType = Nullable.GetUnderlyingType(toType) ?? toType; + var underlyingFromType = Nullable.GetUnderlyingType(fromType) ?? fromType; + + if (underlyingToType.IsEnum) + { + if (underlyingFromType.IsEnum || fromType == typeof(string)) + return fromValue => Enum.Parse(underlyingToType, fromValue.ToString(), ignoreCase: true); + + if (underlyingFromType.IsIntegerType()) + return fromValue => Enum.ToObject(underlyingToType, fromValue); + } + else if (underlyingFromType.IsEnum) + { + if (underlyingToType.IsIntegerType()) + return fromValue => Convert.ChangeType(fromValue, underlyingToType, null); + } + else if (typeof(IEnumerable).IsAssignableFrom(fromType) && underlyingToType != typeof(string)) + { + return fromValue => AutoMappingUtils.TryConvertCollections(fromType, underlyingToType, fromValue); + } + else if (underlyingToType.IsValueType) + { + return fromValue => AutoMappingUtils.ChangeValueType(fromValue, underlyingToType); + } + else + { + return fromValue => + { + if (fromValue == null) + return fromValue; + if (toType == typeof(string)) + return fromValue.ToJsv(); + + var toValue = toType.CreateInstance(); + toValue.PopulateWith(fromValue); + return toValue; + }; + } + + return null; + } + } + +} diff --git a/src/ServiceStack.Text/CachedTypeInfo.cs b/src/ServiceStack.Text/CachedTypeInfo.cs new file mode 100644 index 000000000..4c24d6241 --- /dev/null +++ b/src/ServiceStack.Text/CachedTypeInfo.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using System.Threading; + +namespace ServiceStack.Text +{ + public class CachedTypeInfo + { + static Dictionary CacheMap = new Dictionary(); + + public static CachedTypeInfo Get(Type type) + { + if (CacheMap.TryGetValue(type, out var value)) + return value; + + var instance = new CachedTypeInfo(type); + + Dictionary snapshot, newCache; + do + { + snapshot = CacheMap; + newCache = new Dictionary(CacheMap) + { + [type] = instance + }; + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref CacheMap, newCache, snapshot), snapshot)); + + return instance; + } + + public CachedTypeInfo(Type type) + { + EnumInfo = EnumInfo.GetEnumInfo(type); + } + + public EnumInfo EnumInfo { get; } + } + + public class EnumInfo + { + public static EnumInfo GetEnumInfo(Type type) + { + if (type.IsEnum) + return new EnumInfo(type); + + var nullableType = Nullable.GetUnderlyingType(type); + if (nullableType?.IsEnum == true) + return new EnumInfo(nullableType); + + return null; + } + + private readonly Type enumType; + private EnumInfo(Type enumType) + { + this.enumType = enumType; + enumMemberReverseLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var enumMembers = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); + foreach (var fi in enumMembers) + { + var enumValue = fi.GetValue(null); + var strEnum = fi.Name; + var enumMemberAttr = fi.FirstAttribute(); + if (enumMemberAttr?.Value != null) + { + if (enumMemberValues == null) + { + enumMemberValues = new Dictionary(); + } + enumMemberValues[enumValue] = enumMemberAttr.Value; + enumMemberReverseLookup[enumMemberAttr.Value] = enumValue; + } + else + { + enumMemberReverseLookup[strEnum] = enumValue; + } + } + isEnumFlag = enumType.IsEnumFlags(); + } + + private readonly bool isEnumFlag; + private readonly Dictionary enumMemberValues; + private readonly Dictionary enumMemberReverseLookup; + + public object GetSerializedValue(object enumValue) + { + if (enumMemberValues != null && enumMemberValues.TryGetValue(enumValue, out var memberValue)) + return memberValue; + if (isEnumFlag || JsConfig.TreatEnumAsInteger) + return enumValue; + return enumValue.ToString(); + } + + public object Parse(string serializedValue) + { + if (enumMemberReverseLookup.TryGetValue(serializedValue, out var enumValue)) + return enumValue; + + return Enum.Parse(enumType, serializedValue, ignoreCase: true); //Also parses quoted int values, e.g. "1" + } + } + +} \ No newline at end of file diff --git a/src/ServiceStack.Text/CharMemoryExtensions.cs b/src/ServiceStack.Text/CharMemoryExtensions.cs new file mode 100644 index 000000000..75b13f8b9 --- /dev/null +++ b/src/ServiceStack.Text/CharMemoryExtensions.cs @@ -0,0 +1,412 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace ServiceStack.Text +{ + public static class CharMemoryExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullOrEmpty(this ReadOnlyMemory value) => value.IsEmpty; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWhiteSpace(this ReadOnlyMemory value) + { + var span = value.Span; + for (int i = 0; i < span.Length; i++) + { + if (!char.IsWhiteSpace(span[i])) + return false; + } + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullOrWhiteSpace(this ReadOnlyMemory value) => value.IsEmpty || value.IsWhiteSpace(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory Advance(this ReadOnlyMemory text, int to) => text.Slice(to, text.Length - to); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory AdvancePastWhitespace(this ReadOnlyMemory literal) + { + var span = literal.Span; + var i = 0; + while (i < span.Length && char.IsWhiteSpace(span[i])) + i++; + + return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length); + } + + public static ReadOnlyMemory AdvancePastChar(this ReadOnlyMemory literal, char delim) + { + var i = 0; + var c = (char) 0; + var span = literal.Span; + while (i < span.Length && (c = span[i]) != delim) + i++; + + if (c == delim) + return literal.Slice(i + 1); + + return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ParseBoolean(this ReadOnlyMemory value) => MemoryProvider.Instance.ParseBoolean(value.Span); + + public static bool TryParseBoolean(this ReadOnlyMemory value, out bool result) => + MemoryProvider.Instance.TryParseBoolean(value.Span, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseDecimal(this ReadOnlyMemory value, out decimal result) => + MemoryProvider.Instance.TryParseDecimal(value.Span, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseFloat(this ReadOnlyMemory value, out float result) => + MemoryProvider.Instance.TryParseFloat(value.Span, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseDouble(this ReadOnlyMemory value, out double result) => + MemoryProvider.Instance.TryParseDouble(value.Span, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static decimal ParseDecimal(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseDecimal(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ParseFloat(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseFloat(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ParseDouble(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseDouble(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte ParseSByte(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseSByte(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ParseByte(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseByte(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ParseInt16(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseInt16(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ParseUInt16(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseUInt16(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ParseInt32(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseInt32(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ParseUInt32(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseUInt32(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ParseInt64(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseInt64(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ParseUInt64(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseUInt64(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Guid ParseGuid(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseGuid(value.Span); + + public static ReadOnlyMemory LeftPart(this ReadOnlyMemory strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlyMemory LeftPart(this ReadOnlyMemory strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlyMemory RightPart(this ReadOnlyMemory strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + 1); + } + + public static ReadOnlyMemory RightPart(this ReadOnlyMemory strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + needle.Length); + } + + public static ReadOnlyMemory LastLeftPart(this ReadOnlyMemory strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlyMemory LastLeftPart(this ReadOnlyMemory strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlyMemory LastRightPart(this ReadOnlyMemory strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + 1); + } + + public static ReadOnlyMemory LastRightPart(this ReadOnlyMemory strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + needle.Length); + } + + public static bool TryReadLine(this ReadOnlyMemory text, out ReadOnlyMemory line, ref int startIndex) + { + if (startIndex >= text.Length) + { + line = TypeConstants.NullStringMemory; + return false; + } + + text = text.Slice(startIndex); + + var nextLinePos = text.Span.IndexOfAny('\r', '\n'); + if (nextLinePos == -1) + { + var nextLine = text.Slice(0, text.Length); + startIndex += text.Length; + line = nextLine; + return true; + } + else + { + var nextLine = text.Slice(0, nextLinePos); + + startIndex += nextLinePos + 1; + + var span = text.Span; + if (span[nextLinePos] == '\r' && span.Length > nextLinePos + 1 && span[nextLinePos + 1] == '\n') + startIndex += 1; + + line = nextLine; + return true; + } + } + + public static bool TryReadPart(this ReadOnlyMemory text, ReadOnlyMemory needle, out ReadOnlyMemory part, ref int startIndex) + { + if (startIndex >= text.Length) + { + part = TypeConstants.NullStringMemory; + return false; + } + + text = text.Slice(startIndex); + var nextPartPos = text.Span.IndexOf(needle.Span); + if (nextPartPos == -1) + { + var nextPart = text.Slice(0, text.Length); + startIndex += text.Length; + part = nextPart; + return true; + } + else + { + var nextPart = text.Slice(0, nextPartPos); + startIndex += nextPartPos + needle.Length; + part = nextPart; + return true; + } + } + + public static void SplitOnFirst(this ReadOnlyMemory strVal, char needle, out ReadOnlyMemory first, out ReadOnlyMemory last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.Span.IndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + 1); + } + } + + public static void SplitOnFirst(this ReadOnlyMemory strVal, ReadOnlyMemory needle, out ReadOnlyMemory first, out ReadOnlyMemory last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.Span.IndexOf(needle.Span); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + needle.Length); + } + } + + public static void SplitOnLast(this ReadOnlyMemory strVal, char needle, out ReadOnlyMemory first, out ReadOnlyMemory last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.Span.LastIndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + 1); + } + } + + public static void SplitOnLast(this ReadOnlyMemory strVal, ReadOnlyMemory needle, out ReadOnlyMemory first, out ReadOnlyMemory last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.Span.LastIndexOf(needle.Span); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + needle.Length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlyMemory value, char needle) => value.Span.IndexOf(needle); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlyMemory value, string needle) => value.Span.IndexOf(needle.AsSpan()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlyMemory value, char needle, int start) + { + var pos = value.Slice(start).Span.IndexOf(needle); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlyMemory value, string needle, int start) + { + var pos = value.Slice(start).Span.IndexOf(needle.AsSpan()); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlyMemory value, char needle) => value.Span.LastIndexOf(needle); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlyMemory value, string needle) => value.Span.LastIndexOf(needle.AsSpan()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlyMemory value, char needle, int start) + { + var pos = value.Slice(start).Span.LastIndexOf(needle); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlyMemory value, string needle, int start) + { + var pos = value.Slice(start).Span.LastIndexOf(needle.AsSpan()); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlyMemory value, string other) => value.Span.StartsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlyMemory value, string other, StringComparison comparison) => value.Span.StartsWith(other.AsSpan(), comparison); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWith(this ReadOnlyMemory value, string other) => value.Span.EndsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWith(this ReadOnlyMemory value, string other, StringComparison comparison) => value.Span.EndsWith(other.AsSpan(), comparison); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsOrdinal(this ReadOnlyMemory value, string other) => value.Span.Equals(other.AsSpan(), StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsOrdinal(this ReadOnlyMemory value, ReadOnlyMemory other) => value.Span.Equals(other.Span, StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory SafeSlice(this ReadOnlyMemory value, int startIndex) => SafeSlice(value, startIndex, value.Length); + + public static ReadOnlyMemory SafeSlice(this ReadOnlyMemory value, int startIndex, int length) + { + if (value.IsEmpty) return TypeConstants.NullStringMemory; + if (startIndex < 0) startIndex = 0; + if (value.Length >= startIndex + length) + return value.Slice(startIndex, length); + + return value.Length > startIndex ? value.Slice(startIndex) : TypeConstants.NullStringMemory; + } + + public static string SubstringWithEllipsis(this ReadOnlyMemory value, int startIndex, int length) + { + if (value.IsEmpty) return string.Empty; + var str = value.Slice(startIndex, length); + return str.Length == length + ? str + "..." + : str.ToString(); + } + + public static ReadOnlyMemory ToUtf8(this ReadOnlyMemory chars) => + MemoryProvider.Instance.ToUtf8(chars.Span); + + public static ReadOnlyMemory FromUtf8(this ReadOnlyMemory bytes) => + MemoryProvider.Instance.FromUtf8(bytes.Span); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/CollectionExtensions.cs b/src/ServiceStack.Text/CollectionExtensions.cs index bec1a9b28..b450d33d4 100644 --- a/src/ServiceStack.Text/CollectionExtensions.cs +++ b/src/ServiceStack.Text/CollectionExtensions.cs @@ -1,23 +1,27 @@ using System; using System.Collections.Generic; -namespace ServiceStack.Text +namespace ServiceStack { public static class CollectionExtensions { public static ICollection CreateAndPopulate(Type ofCollectionType, T[] withItems) { - if (ofCollectionType == null) return new List(withItems); + if (withItems == null) + return null; - var genericType = ofCollectionType.GetGenericType(); + if (ofCollectionType == null) + return new List(withItems); + + var genericType = ofCollectionType.FirstGenericType(); var genericTypeDefinition = genericType != null ? genericType.GetGenericTypeDefinition() : null; -#if !XBOX - if (genericTypeDefinition == typeof(HashSet)) + + if (genericTypeDefinition == typeof(HashSet<>)) return new HashSet(withItems); -#endif - if (genericTypeDefinition == typeof(LinkedList)) + + if (genericTypeDefinition == typeof(LinkedList<>)) return new LinkedList(withItems); var collection = (ICollection)ofCollectionType.CreateInstance(); diff --git a/src/ServiceStack.Text/Common/DateTimeSerializer.cs b/src/ServiceStack.Text/Common/DateTimeSerializer.cs index 63770af6c..ae511d6ef 100644 --- a/src/ServiceStack.Text/Common/DateTimeSerializer.cs +++ b/src/ServiceStack.Text/Common/DateTimeSerializer.cs @@ -5,133 +5,400 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.Globalization; -using System.Xml; +using System.IO; +using System.Text; using ServiceStack.Text.Json; +using ServiceStack.Text.Support; +using System.Text.RegularExpressions; +using ServiceStack.Text.Pools; namespace ServiceStack.Text.Common { - public static class DateTimeSerializer - { - public const string ShortDateTimeFormat = "yyyy-MM-dd"; //11 - public const string DefaultDateTimeFormat = "dd/MM/yyyy HH:mm:ss"; //20 - public const string DefaultDateTimeFormatWithFraction = "dd/MM/yyyy HH:mm:ss.fff"; //24 - public const string XsdDateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffffffZ"; //29 - public const string XsdDateTimeFormat3F = "yyyy-MM-ddTHH:mm:ss.fffZ"; //25 - public const string XsdDateTimeFormatSeconds = "yyyy-MM-ddTHH:mm:ssZ"; //21 - - public const string EscapedWcfJsonPrefix = "\\/Date("; - public const string EscapedWcfJsonSuffix = ")\\/"; - public const string WcfJsonPrefix = "/Date("; - public const char WcfJsonSuffix = ')'; + public static class DateTimeSerializer + { + public const string CondensedDateTimeFormat = "yyyyMMdd"; //8 + public const string ShortDateTimeFormat = "yyyy-MM-dd"; //11 + public const string DefaultDateTimeFormat = "dd/MM/yyyy HH:mm:ss"; //20 + public const string DefaultDateTimeFormatWithFraction = "dd/MM/yyyy HH:mm:ss.fff"; //24 + public const string XsdDateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffffffZ"; //29 + public const string XsdDateTimeFormat3F = "yyyy-MM-ddTHH:mm:ss.fffZ"; //25 + public const string XsdDateTimeFormatSeconds = "yyyy-MM-ddTHH:mm:ssZ"; //21 + public const string DateTimeFormatSecondsUtcOffset = "yyyy-MM-ddTHH:mm:sszzz"; //22 + public const string DateTimeFormatSecondsNoOffset = "yyyy-MM-ddTHH:mm:ss"; + public const string DateTimeFormatTicksUtcOffset = "yyyy-MM-ddTHH:mm:ss.fffffffzzz"; //30 + public const string DateTimeFormatTicksNoUtcOffset = "yyyy-MM-ddTHH:mm:ss.fffffff"; + + public const string EscapedWcfJsonPrefix = "\\/Date("; + public const string EscapedWcfJsonSuffix = ")\\/"; + public const string WcfJsonPrefix = "/Date("; + public const char WcfJsonSuffix = ')'; + public const string UnspecifiedOffset = "-0000"; + public const string UtcOffset = "+0000"; + + private const char XsdTimeSeparator = 'T'; + private static readonly int XsdTimeSeparatorIndex = XsdDateTimeFormat.IndexOf(XsdTimeSeparator); + private const string XsdUtcSuffix = "Z"; + private static readonly char[] DateTimeSeparators = { '-', '/' }; + private static readonly Regex UtcOffsetInfoRegex = new Regex("([+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])", PclExport.Instance.RegexOptions); + public static Func OnParseErrorFn { get; set; } /// - /// If AlwaysUseUtc is set to true then convert all DateTime to UTC. + /// If AlwaysUseUtc is set to true then convert all DateTime to UTC. If PreserveUtc is set to true then UTC dates will not convert to local /// /// /// - private static DateTime Prepare(this DateTime dateTime) + public static DateTime Prepare(this DateTime dateTime, bool parsedAsUtc = false) { - if (JsConfig.AlwaysUseUtc && dateTime.Kind != DateTimeKind.Utc) - return dateTime.ToStableUniversalTime(); - else + var config = JsConfig.GetConfig(); + if (config.SkipDateTimeConversion) return dateTime; + + if (config.AlwaysUseUtc) + return dateTime.Kind != DateTimeKind.Utc ? dateTime.ToStableUniversalTime() : dateTime; + + return parsedAsUtc ? dateTime.ToLocalTime() : dateTime; } public static DateTime? ParseShortestNullableXsdDateTime(string dateTimeStr) { - if (dateTimeStr == null) + if (string.IsNullOrEmpty(dateTimeStr)) return null; return ParseShortestXsdDateTime(dateTimeStr); } - public static DateTime ParseShortestXsdDateTime(string dateTimeStr) - { - if (string.IsNullOrEmpty(dateTimeStr)) - return DateTime.MinValue; + public static DateTime ParseRFC1123DateTime(string dateTimeStr) + { + return DateTime.ParseExact(dateTimeStr, "r", CultureInfo.InvariantCulture); + } + + public static DateTime ParseShortestXsdDateTime(string dateTimeStr) + { + try + { + if (string.IsNullOrEmpty(dateTimeStr)) + return DateTime.MinValue; + + if (dateTimeStr.StartsWith(EscapedWcfJsonPrefix, StringComparison.Ordinal) || dateTimeStr.StartsWith(WcfJsonPrefix, StringComparison.Ordinal)) + return ParseWcfJsonDate(dateTimeStr).Prepare(); + + var config = JsConfig.GetConfig(); + if (dateTimeStr.Length == DefaultDateTimeFormat.Length) + { + var unspecifiedDate = DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture); + + if (config.AssumeUtc) + unspecifiedDate = DateTime.SpecifyKind(unspecifiedDate, DateTimeKind.Utc); + + return unspecifiedDate.Prepare(); + } + + var hasUtcSuffix = dateTimeStr.EndsWith(XsdUtcSuffix); + if (!hasUtcSuffix && dateTimeStr.Length == DefaultDateTimeFormatWithFraction.Length) + { + var unspecifiedDate = config.AssumeUtc + ? DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal) + : DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture); + + return unspecifiedDate.Prepare(); + } + + var kind = hasUtcSuffix + ? DateTimeKind.Utc + : DateTimeKind.Unspecified; + switch (config.DateHandler) + { + case DateHandler.UnixTime: + if (int.TryParse(dateTimeStr, out var unixTime)) + return unixTime.FromUnixTime(); + break; + case DateHandler.UnixTimeMs: + if (long.TryParse(dateTimeStr, out var unixTimeMs)) + return unixTimeMs.FromUnixTimeMs(); + break; + case DateHandler.ISO8601: + case DateHandler.ISO8601DateOnly: + case DateHandler.ISO8601DateTime: + if (config.SkipDateTimeConversion) + dateTimeStr = RemoveUtcOffsets(dateTimeStr, out kind); + break; + } + + dateTimeStr = RepairXsdTimeSeparator(dateTimeStr); + + if (dateTimeStr.Length == XsdDateTimeFormatSeconds.Length) + return DateTime.ParseExact(dateTimeStr, XsdDateTimeFormatSeconds, null, DateTimeStyles.AdjustToUniversal).Prepare(parsedAsUtc: true); + + if (dateTimeStr.Length >= XsdDateTimeFormat3F.Length + && dateTimeStr.Length <= XsdDateTimeFormat.Length + && hasUtcSuffix) + { + var dateTime = Env.IsMono ? ParseManual(dateTimeStr) : null; + if (dateTime != null) + return dateTime.Value; + + return PclExport.Instance.ParseXsdDateTimeAsUtc(dateTimeStr); + } + + if (dateTimeStr.Length == CondensedDateTimeFormat.Length && dateTimeStr.IndexOfAny(DateTimeSeparators) == -1) + { + return DateTime.ParseExact(dateTimeStr, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None); + } + + if (dateTimeStr.Length == ShortDateTimeFormat.Length) + { + try + { + var manualDate = ParseManual(dateTimeStr); + if (manualDate != null) + return manualDate.Value; + } + catch { } + } + + try + { + if (config.SkipDateTimeConversion) + { + var dateTimeStyle = kind == DateTimeKind.Unspecified + ? DateTimeStyles.None + : kind == DateTimeKind.Local + ? DateTimeStyles.AssumeLocal + : DateTimeStyles.AssumeUniversal; + if (config.AlwaysUseUtc) + dateTimeStyle |= DateTimeStyles.AdjustToUniversal; + return DateTime.Parse(dateTimeStr, null, dateTimeStyle); + } + + var assumeKind = config.AssumeUtc ? DateTimeStyles.AssumeUniversal : DateTimeStyles.AssumeLocal; + var dateTime = DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture, assumeKind); + return dateTime.Prepare(); + } + catch (FormatException) + { + var manualDate = ParseManual(dateTimeStr); + if (manualDate != null) + return manualDate.Value; + + throw; + } + } + catch (Exception ex) + { + if (OnParseErrorFn != null) + return OnParseErrorFn(dateTimeStr, ex); + + throw; + } + } + + private static string RemoveUtcOffsets(string dateTimeStr, out DateTimeKind kind) + { + var startOfTz = UtcOffsetInfoRegex.Match(dateTimeStr); + if (startOfTz.Index > 0) + { + kind = DateTimeKind.Local; + return dateTimeStr.Substring(0, startOfTz.Index); + } + kind = dateTimeStr.Contains("Z") ? DateTimeKind.Utc : DateTimeKind.Unspecified; + return dateTimeStr; + } + + /// + /// Repairs an out-of-spec XML date/time string which incorrectly uses a space instead of a 'T' to separate the date from the time. + /// These string are occasionally generated by SQLite and can cause errors in OrmLite when reading these columns from the DB. + /// + /// The XML date/time string to repair + /// The repaired string. If no repairs were made, the original string is returned. + private static string RepairXsdTimeSeparator(string dateTimeStr) + { + if ((dateTimeStr.Length > XsdTimeSeparatorIndex) && (dateTimeStr[XsdTimeSeparatorIndex] == ' ') && dateTimeStr.EndsWith(XsdUtcSuffix)) + { + dateTimeStr = dateTimeStr.Substring(0, XsdTimeSeparatorIndex) + XsdTimeSeparator + + dateTimeStr.Substring(XsdTimeSeparatorIndex + 1); + } + + return dateTimeStr; + } + + public static DateTime? ParseManual(string dateTimeStr) + { + var config = JsConfig.GetConfig(); + var dateKind = config.AssumeUtc || config.AlwaysUseUtc + ? DateTimeKind.Utc + : DateTimeKind.Local; + + var date = ParseManual(dateTimeStr, dateKind); + if (date == null) + return null; + + return dateKind == DateTimeKind.Local + ? date.Value.ToLocalTime().Prepare() + : date; + } - if (dateTimeStr.StartsWith(EscapedWcfJsonPrefix) || dateTimeStr.StartsWith(WcfJsonPrefix)) - return ParseWcfJsonDate(dateTimeStr).Prepare(); + public static DateTime? ParseManual(string dateTimeStr, DateTimeKind dateKind) + { + if (dateTimeStr == null || dateTimeStr.Length < ShortDateTimeFormat.Length) + return null; - if (dateTimeStr.Length == DefaultDateTimeFormat.Length - || dateTimeStr.Length == DefaultDateTimeFormatWithFraction.Length) + if (dateTimeStr.EndsWith(XsdUtcSuffix)) { - return DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture).Prepare(); + dateTimeStr = dateTimeStr.Substring(0, dateTimeStr.Length - 1); + dateKind = JsConfig.SkipDateTimeConversion ? DateTimeKind.Utc : dateKind; } - if (dateTimeStr.Length == XsdDateTimeFormatSeconds.Length) - return DateTime.ParseExact(dateTimeStr, XsdDateTimeFormatSeconds, null, - DateTimeStyles.AdjustToUniversal); + var parts = dateTimeStr.Split('T'); + if (parts.Length == 1) + parts = dateTimeStr.SplitOnFirst(' '); + + var dateParts = parts[0].Split('-', '/'); + int hh = 0, min = 0, ss = 0, ms = 0; + double subMs = 0; + int offsetMultiplier = 0; - if (dateTimeStr.Length >= XsdDateTimeFormat3F.Length - && dateTimeStr.Length <= XsdDateTimeFormat.Length) + if (parts.Length == 1) { - var dateTimeType = JsConfig.DateHandler != JsonDateHandler.ISO8601 - ? XmlDateTimeSerializationMode.Local - : XmlDateTimeSerializationMode.RoundtripKind; + return dateParts.Length == 3 && dateParts[2].Length == "YYYY".Length + ? new DateTime(int.Parse(dateParts[2]), int.Parse(dateParts[1]), int.Parse(dateParts[0]), 0, 0, 0, 0, + dateKind) + : new DateTime(int.Parse(dateParts[0]), int.Parse(dateParts[1]), int.Parse(dateParts[2]), 0, 0, 0, 0, + dateKind); + } + else if (parts.Length == 2) + { + var timeStringParts = parts[1].Split('+'); + if (timeStringParts.Length == 2) + { + offsetMultiplier = -1; + } + else + { + timeStringParts = parts[1].Split('-'); + if (timeStringParts.Length == 2) + { + offsetMultiplier = 1; + } + } + + var timeOffset = timeStringParts.Length == 2 ? timeStringParts[1] : null; + var timeParts = timeStringParts[0].Split(':'); + + if (timeParts.Length == 3) + { + int.TryParse(timeParts[0], out hh); + int.TryParse(timeParts[1], out min); + + var secParts = timeParts[2].Split('.'); + int.TryParse(secParts[0], out ss); + if (secParts.Length == 2) + { + var msStr = secParts[1].PadRight(3, '0'); + ms = int.Parse(msStr.Substring(0, 3)); + + if (msStr.Length > 3) + { + var subMsStr = msStr.Substring(3); + subMs = double.Parse(subMsStr) / Math.Pow(10, subMsStr.Length); + } + } + } + + var dateTime = new DateTime(int.Parse(dateParts[0]), int.Parse(dateParts[1]), int.Parse(dateParts[2]), hh, min, + ss, ms, dateKind); + if (subMs != 0) + { + dateTime = dateTime.AddMilliseconds(subMs); + } + + if (offsetMultiplier != 0 && timeOffset != null) + { + timeParts = timeOffset.Split(':'); + if (timeParts.Length == 2) + { + hh = int.Parse(timeParts[0]); + min = int.Parse(timeParts[1]); + } + else + { + hh = int.Parse(timeOffset.Substring(0, 2)); + min = int.Parse(timeOffset.Substring(2)); + } + + dateTime = dateTime.AddHours(offsetMultiplier * hh); + dateTime = dateTime.AddMinutes(offsetMultiplier * min); + } - return XmlConvert.ToDateTime(dateTimeStr, dateTimeType).Prepare(); + return dateTime; } - return DateTime.Parse(dateTimeStr, null, DateTimeStyles.AssumeLocal).Prepare(); + return null; } - public static string ToDateTimeString(DateTime dateTime) - { - return dateTime.ToStableUniversalTime().ToString(XsdDateTimeFormat); - } + public static string ToDateTimeString(DateTime dateTime) + { + return dateTime.ToStableUniversalTime().ToString(XsdDateTimeFormat, CultureInfo.InvariantCulture); + } - public static DateTime ParseDateTime(string dateTimeStr) - { - return DateTime.ParseExact(dateTimeStr, XsdDateTimeFormat, null); - } + public static DateTime ParseDateTime(string dateTimeStr) + { + return DateTime.ParseExact(dateTimeStr, XsdDateTimeFormat, null); + } - public static DateTimeOffset ParseDateTimeOffset(string dateTimeOffsetStr) - { + public static DateTimeOffset ParseDateTimeOffset(string dateTimeOffsetStr) + { if (string.IsNullOrEmpty(dateTimeOffsetStr)) return default(DateTimeOffset); // for interop, do not assume format based on config // format: prefer TimestampOffset, DCJSCompatible - if (dateTimeOffsetStr.StartsWith(EscapedWcfJsonPrefix) || - dateTimeOffsetStr.StartsWith(WcfJsonPrefix)) + if (dateTimeOffsetStr.StartsWith(EscapedWcfJsonPrefix, StringComparison.Ordinal) || + dateTimeOffsetStr.StartsWith(WcfJsonPrefix, StringComparison.Ordinal)) { return ParseWcfJsonDateOffset(dateTimeOffsetStr); } // format: next preference ISO8601 - // assume utc when no offset specified + // assume utc when no offset specified if (dateTimeOffsetStr.LastIndexOfAny(TimeZoneChars) < 10) { - if (!dateTimeOffsetStr.EndsWith("Z")) dateTimeOffsetStr += "Z"; -#if __MonoCS__ - // Without that Mono uses a Local timezone)) - dateTimeOffsetStr = dateTimeOffsetStr.Substring(0, dateTimeOffsetStr.Length - 1) + "+00:00"; -#endif + if (!dateTimeOffsetStr.EndsWith(XsdUtcSuffix)) dateTimeOffsetStr += XsdUtcSuffix; + if (Env.IsMono) + { + // Without that Mono uses a Local timezone)) + dateTimeOffsetStr = dateTimeOffsetStr.Substring(0, dateTimeOffsetStr.Length - 1) + "+00:00"; + } } return DateTimeOffset.Parse(dateTimeOffsetStr, CultureInfo.InvariantCulture); - } + } + + public static DateTimeOffset? ParseNullableDateTimeOffset(string dateTimeOffsetStr) + { + if (string.IsNullOrEmpty(dateTimeOffsetStr)) return null; + + return ParseDateTimeOffset(dateTimeOffsetStr); + } + + public static string ToXsdDateTimeString(DateTime dateTime) + { + return PclExport.Instance.ToXsdDateTimeString(dateTime); + } - public static string ToXsdDateTimeString(DateTime dateTime) - { - return XmlConvert.ToString(dateTime.ToStableUniversalTime(), XmlDateTimeSerializationMode.Utc); - } + public static string ToLocalXsdDateTimeString(DateTime dateTime) + { + return PclExport.Instance.ToLocalXsdDateTimeString(dateTime); + } public static string ToXsdTimeSpanString(TimeSpan timeSpan) { - var r = XmlConvert.ToString(timeSpan); -#if __MonoCS__ - // Mono returns DT even if time is 00:00:00 - if (r.EndsWith("DT")) return r.Substring(0, r.Length - 1); -#endif - return r; + return TimeSpanConverter.ToXsdDuration(timeSpan); } public static string ToXsdTimeSpanString(TimeSpan? timeSpan) @@ -139,170 +406,299 @@ public static string ToXsdTimeSpanString(TimeSpan? timeSpan) return (timeSpan != null) ? ToXsdTimeSpanString(timeSpan.Value) : null; } - public static DateTime ParseXsdDateTime(string dateTimeStr) - { - return XmlConvert.ToDateTime(dateTimeStr, XmlDateTimeSerializationMode.Utc); - } + public static DateTime ParseXsdDateTime(string dateTimeStr) + { + dateTimeStr = RepairXsdTimeSeparator(dateTimeStr); + return PclExport.Instance.ParseXsdDateTime(dateTimeStr); + } public static TimeSpan ParseTimeSpan(string dateTimeStr) { - return dateTimeStr.StartsWith("P") || dateTimeStr.StartsWith("-P") + return dateTimeStr.StartsWith("P", StringComparison.Ordinal) || dateTimeStr.StartsWith("-P", StringComparison.Ordinal) ? ParseXsdTimeSpan(dateTimeStr) - : TimeSpan.Parse(dateTimeStr); + : dateTimeStr.Contains(":") + ? TimeSpan.Parse(dateTimeStr) + : ParseNSTimeInterval(dateTimeStr); } - public static TimeSpan ParseXsdTimeSpan(string dateTimeStr) + public static TimeSpan ParseNSTimeInterval(string doubleInSecs) { - return XmlConvert.ToTimeSpan(dateTimeStr); + var secs = double.Parse(doubleInSecs, CultureInfo.InvariantCulture); + return TimeSpan.FromSeconds(secs); } public static TimeSpan? ParseNullableTimeSpan(string dateTimeStr) { - return string.IsNullOrEmpty(dateTimeStr) - ? (TimeSpan?) null + return string.IsNullOrEmpty(dateTimeStr) + ? (TimeSpan?)null : ParseTimeSpan(dateTimeStr); } + public static TimeSpan ParseXsdTimeSpan(string dateTimeStr) + { + return TimeSpanConverter.FromXsdDuration(dateTimeStr); + } + public static TimeSpan? ParseXsdNullableTimeSpan(string dateTimeStr) { - return String.IsNullOrEmpty(dateTimeStr) ? + return string.IsNullOrEmpty(dateTimeStr) ? null : - new TimeSpan?(XmlConvert.ToTimeSpan(dateTimeStr)); + new TimeSpan?(ParseXsdTimeSpan(dateTimeStr)); } - public static string ToShortestXsdDateTimeString(DateTime dateTime) - { - var timeOfDay = dateTime.TimeOfDay; - - if (timeOfDay.Ticks == 0) - return dateTime.ToString(ShortDateTimeFormat); - - if (timeOfDay.Milliseconds == 0) - return dateTime.ToStableUniversalTime().ToString(XsdDateTimeFormatSeconds); - - return ToXsdDateTimeString(dateTime); - } - - - static readonly char[] TimeZoneChars = new[] { '+', '-' }; - - /// - /// WCF Json format: /Date(unixts+0000)/ - /// - /// - /// - public static DateTimeOffset ParseWcfJsonDateOffset(string wcfJsonDate) - { - if (wcfJsonDate[0] == '\\') - { - wcfJsonDate = wcfJsonDate.Substring(1); - } - - var suffixPos = wcfJsonDate.IndexOf(WcfJsonSuffix); - var timeString = (suffixPos < 0) ? wcfJsonDate : wcfJsonDate.Substring(WcfJsonPrefix.Length, suffixPos - WcfJsonPrefix.Length); - - // for interop, do not assume format based on config - if (!wcfJsonDate.StartsWith(WcfJsonPrefix)) - { - return DateTimeOffset.Parse(timeString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); - } - - var timeZonePos = timeString.LastIndexOfAny(TimeZoneChars); - var timeZone = timeZonePos <= 0 ? string.Empty : timeString.Substring(timeZonePos); - var unixTimeString = timeString.Substring(0, timeString.Length - timeZone.Length); - - var unixTime = long.Parse(unixTimeString); - - if (timeZone == string.Empty) - { - // when no timezone offset is supplied, then treat the time as UTC - return unixTime.FromUnixTimeMs(); - } - - if (JsConfig.DateHandler == JsonDateHandler.DCJSCompatible) - { - // DCJS ignores the offset and considers it local time if any offset exists - // REVIEW: DCJS shoves offset in a separate field 'offsetMinutes', we have the offset in the format, so shouldn't we use it? - return unixTime.FromUnixTimeMs().ToLocalTime(); - } - - var offset = timeZone.FromTimeOffsetString(); - var date = unixTime.FromUnixTimeMs(); - return new DateTimeOffset(date.Ticks, offset); - } - - /// - /// WCF Json format: /Date(unixts+0000)/ - /// - /// - /// - public static DateTime ParseWcfJsonDate(string wcfJsonDate) - { - if (wcfJsonDate[0] == JsonUtils.EscapeChar) - { - wcfJsonDate = wcfJsonDate.Substring(1); - } - - var suffixPos = wcfJsonDate.IndexOf(WcfJsonSuffix); - var timeString = wcfJsonDate.Substring(WcfJsonPrefix.Length, suffixPos - WcfJsonPrefix.Length); + public static string ToShortestXsdDateTimeString(DateTime dateTime) + { + var config = JsConfig.GetConfig(); + + dateTime = dateTime.UseConfigSpecifiedSetting(); + if (!string.IsNullOrEmpty(config.DateTimeFormat)) + { + return dateTime.ToString(config.DateTimeFormat, CultureInfo.InvariantCulture); + } + + var timeOfDay = dateTime.TimeOfDay; + var isStartOfDay = timeOfDay.Ticks == 0; + if (isStartOfDay && !config.SkipDateTimeConversion) + return dateTime.ToString(ShortDateTimeFormat, CultureInfo.InvariantCulture); + + var hasFractionalSecs = (timeOfDay.Milliseconds != 0) + || (timeOfDay.Ticks % TimeSpan.TicksPerMillisecond != 0); + + if (config.SkipDateTimeConversion) + { + if (!hasFractionalSecs) + return dateTime.Kind == DateTimeKind.Local + ? dateTime.ToString(DateTimeFormatSecondsUtcOffset, CultureInfo.InvariantCulture) + : dateTime.Kind == DateTimeKind.Unspecified + ? dateTime.ToString(DateTimeFormatSecondsNoOffset, CultureInfo.InvariantCulture) + : dateTime.ToStableUniversalTime().ToString(XsdDateTimeFormatSeconds, CultureInfo.InvariantCulture); + + return dateTime.Kind == DateTimeKind.Local + ? dateTime.ToString(DateTimeFormatTicksUtcOffset, CultureInfo.InvariantCulture) + : dateTime.Kind == DateTimeKind.Unspecified + ? dateTime.ToString(DateTimeFormatTicksNoUtcOffset, CultureInfo.InvariantCulture) + : PclExport.Instance.ToXsdDateTimeString(dateTime); + } + + if (!hasFractionalSecs) + return dateTime.Kind != DateTimeKind.Utc + ? dateTime.ToString(DateTimeFormatSecondsUtcOffset, CultureInfo.InvariantCulture) + : dateTime.ToStableUniversalTime().ToString(XsdDateTimeFormatSeconds, CultureInfo.InvariantCulture); + + return dateTime.Kind != DateTimeKind.Utc + ? dateTime.ToString(DateTimeFormatTicksUtcOffset, CultureInfo.InvariantCulture) + : PclExport.Instance.ToXsdDateTimeString(dateTime); + } + + static readonly char[] TimeZoneChars = new[] { '+', '-' }; + + private const string MinDateTimeOffsetWcfValue = "\\/Date(-62135596800000)\\/"; + private const string MaxDateTimeOffsetWcfValue = "\\/Date(253402300799999)\\/"; + + /// + /// WCF Json format: /Date(unixts+0000)/ + /// + /// + /// + public static DateTimeOffset ParseWcfJsonDateOffset(string wcfJsonDate) + { + if (wcfJsonDate == MinDateTimeOffsetWcfValue) + return DateTimeOffset.MinValue; + if (wcfJsonDate == MaxDateTimeOffsetWcfValue) + return DateTimeOffset.MaxValue; + + if (wcfJsonDate[0] == '\\') + { + wcfJsonDate = wcfJsonDate.Substring(1); + } + + var suffixPos = wcfJsonDate.IndexOf(WcfJsonSuffix); + var timeString = (suffixPos < 0) ? wcfJsonDate : wcfJsonDate.Substring(WcfJsonPrefix.Length, suffixPos - WcfJsonPrefix.Length); // for interop, do not assume format based on config - if (!wcfJsonDate.StartsWith(WcfJsonPrefix)) + if (!wcfJsonDate.StartsWith(WcfJsonPrefix, StringComparison.Ordinal)) { - return DateTime.Parse(timeString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); - } + return DateTimeOffset.Parse(timeString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + } - var timeZonePos = timeString.LastIndexOfAny(TimeZoneChars); - var timeZone = timeZonePos <= 0 ? string.Empty : timeString.Substring(timeZonePos); - var unixTimeString = timeString.Substring(0, timeString.Length - timeZone.Length); + var timeZonePos = timeString.LastIndexOfAny(TimeZoneChars); + var timeZone = timeZonePos <= 0 ? string.Empty : timeString.Substring(timeZonePos); + var unixTimeString = timeString.Substring(0, timeString.Length - timeZone.Length); - var unixTime = long.Parse(unixTimeString); + var unixTime = long.Parse(unixTimeString); - if (timeZone == string.Empty) - { + if (timeZone == string.Empty) + { // when no timezone offset is supplied, then treat the time as UTC - return unixTime.FromUnixTimeMs(); - } + return unixTime.FromUnixTimeMs(); + } - if (JsConfig.DateHandler == JsonDateHandler.DCJSCompatible) - { - // DCJS ignores the offset and considers it local time if any offset exists - return unixTime.FromUnixTimeMs().ToLocalTime(); - } + // DCJS ignores the offset and considers it local time if any offset exists + // REVIEW: DCJS shoves offset in a separate field 'offsetMinutes', we have the offset in the format, so shouldn't we use it? + if (JsConfig.DateHandler == DateHandler.DCJSCompatible || timeZone == UnspecifiedOffset) + { + return unixTime.FromUnixTimeMs().ToLocalTime(); + } + + var offset = timeZone.FromTimeOffsetString(); + var date = unixTime.FromUnixTimeMs(); + return new DateTimeOffset(date.Ticks, offset); + } + + /// + /// WCF Json format: /Date(unixts+0000)/ + /// + /// + /// + public static DateTime ParseWcfJsonDate(string wcfJsonDate) + { + if (wcfJsonDate[0] == JsonUtils.EscapeChar) + { + wcfJsonDate = wcfJsonDate.Substring(1); + } + + var suffixPos = wcfJsonDate.IndexOf(WcfJsonSuffix); + var timeString = wcfJsonDate.Substring(WcfJsonPrefix.Length, suffixPos - WcfJsonPrefix.Length); + + // for interop, do not assume format based on config + if (!wcfJsonDate.StartsWith(WcfJsonPrefix, StringComparison.Ordinal)) + { + return DateTime.Parse(timeString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + } + + var timeZonePos = timeString.LastIndexOfAny(TimeZoneChars); + var timeZone = timeZonePos <= 0 ? string.Empty : timeString.Substring(timeZonePos); + var unixTimeString = timeString.Substring(0, timeString.Length - timeZone.Length); + + var unixTime = long.Parse(unixTimeString); + + if (timeZone == string.Empty) + { + // when no timezone offset is supplied, then treat the time as UTC + return unixTime.FromUnixTimeMs(); + } + + // DCJS ignores the offset and considers it local time if any offset exists + if (JsConfig.DateHandler == DateHandler.DCJSCompatible || timeZone == UnspecifiedOffset) + { + return unixTime.FromUnixTimeMs().ToLocalTime(); + } var offset = timeZone.FromTimeOffsetString(); var date = unixTime.FromUnixTimeMs(offset); - return new DateTimeOffset(date, offset).DateTime; - } - - public static string ToWcfJsonDate(DateTime dateTime) - { - if (JsConfig.DateHandler == JsonDateHandler.ISO8601) - { - return dateTime.ToString("o", CultureInfo.InvariantCulture); - } - - var timestamp = dateTime.ToUnixTimeMs(); - var offset = dateTime.Kind == DateTimeKind.Utc - ? string.Empty - : TimeZoneInfo.Local.GetUtcOffset(dateTime).ToTimeOffsetString(); - - return EscapedWcfJsonPrefix + timestamp + offset + EscapedWcfJsonSuffix; - } - - public static string ToWcfJsonDateTimeOffset(DateTimeOffset dateTimeOffset) - { - if (JsConfig.DateHandler == JsonDateHandler.ISO8601) - { - return dateTimeOffset.ToString("o", CultureInfo.InvariantCulture); - } - - var timestamp = dateTimeOffset.Ticks.ToUnixTimeMs(); - var offset = dateTimeOffset.Offset == TimeSpan.Zero - ? string.Empty - : dateTimeOffset.Offset.ToTimeOffsetString(); - - return EscapedWcfJsonPrefix + timestamp + offset + EscapedWcfJsonSuffix; - } - } -} \ No newline at end of file + return date; + } + + public static TimeZoneInfo GetLocalTimeZoneInfo() + { + try + { + return TimeZoneInfo.Local; + } + catch (Exception) + { + return TimeZoneInfo.Utc; //Fallback for Mono on Windows. + } + } + + internal static TimeZoneInfo LocalTimeZone = GetLocalTimeZoneInfo(); + + private static DateTime UseConfigSpecifiedSetting(this DateTime dateTime) + { + if (JsConfig.AssumeUtc && dateTime.Kind == DateTimeKind.Unspecified) + { + return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); + } + return dateTime; + } + + public static void WriteWcfJsonDate(TextWriter writer, DateTime dateTime) + { + var config = JsConfig.GetConfig(); + + dateTime = dateTime.UseConfigSpecifiedSetting(); + switch (config.DateHandler) + { + case DateHandler.ISO8601: + writer.Write(dateTime.ToString("o", CultureInfo.InvariantCulture)); + return; + case DateHandler.ISO8601DateOnly: + writer.Write(dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + return; + case DateHandler.ISO8601DateTime: + writer.Write(dateTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + return; + case DateHandler.RFC1123: + writer.Write(dateTime.ToUniversalTime().ToString("R", CultureInfo.InvariantCulture)); + return; + } + + var timestamp = dateTime.ToUnixTimeMs(); + string offset = null; + if (dateTime.Kind != DateTimeKind.Utc) + { + if (config.DateHandler == DateHandler.TimestampOffset && dateTime.Kind == DateTimeKind.Unspecified) + offset = UnspecifiedOffset; + else + offset = LocalTimeZone.GetUtcOffset(dateTime).ToTimeOffsetString(); + } + else + { + // Normally the JsonDateHandler.TimestampOffset doesn't append an offset for Utc dates, but if + // the config.AppendUtcOffset is set then we will + if (config.DateHandler == DateHandler.TimestampOffset && config.AppendUtcOffset) + offset = UtcOffset; + } + + writer.Write(EscapedWcfJsonPrefix); + writer.Write(timestamp); + if (offset != null) + { + writer.Write(offset); + } + writer.Write(EscapedWcfJsonSuffix); + } + + public static string ToWcfJsonDate(DateTime dateTime) + { + var sb = StringBuilderThreadStatic.Allocate(); + using (var writer = new StringWriter(sb)) + { + WriteWcfJsonDate(writer, dateTime); + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + } + + public static void WriteWcfJsonDateTimeOffset(TextWriter writer, DateTimeOffset dateTimeOffset) + { + if (JsConfig.DateHandler == DateHandler.ISO8601) + { + writer.Write(dateTimeOffset.ToString("o", CultureInfo.InvariantCulture)); + return; + } + + var timestamp = dateTimeOffset.Ticks.ToUnixTimeMs(); + var offset = dateTimeOffset.Offset == TimeSpan.Zero + ? null + : dateTimeOffset.Offset.ToTimeOffsetString(); + + writer.Write(EscapedWcfJsonPrefix); + writer.Write(timestamp); + if (offset != null) + { + writer.Write(offset); + } + writer.Write(EscapedWcfJsonSuffix); + } + + public static string ToWcfJsonDateTimeOffset(DateTimeOffset dateTimeOffset) + { + var sb = StringBuilderThreadStatic.Allocate(); + using (var writer = new StringWriter(sb)) + { + WriteWcfJsonDateTimeOffset(writer, dateTimeOffset); + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + } + } +} diff --git a/src/ServiceStack.Text/Common/DeserializeArray.cs b/src/ServiceStack.Text/Common/DeserializeArray.cs index fea595055..af392953d 100644 --- a/src/ServiceStack.Text/Common/DeserializeArray.cs +++ b/src/ServiceStack.Text/Common/DeserializeArray.cs @@ -5,34 +5,40 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.Collections.Generic; -using System.Reflection; using System.Threading; namespace ServiceStack.Text.Common { - internal static class DeserializeArrayWithElements - where TSerializer : ITypeSerializer - { - private static Dictionary ParseDelegateCache - = new Dictionary(); + public static class DeserializeArrayWithElements + where TSerializer : ITypeSerializer + { + private static Dictionary ParseDelegateCache + = new Dictionary(); - private delegate object ParseArrayOfElementsDelegate(string value, ParseStringDelegate parseFn); + public delegate object ParseArrayOfElementsDelegate(ReadOnlySpan value, ParseStringSpanDelegate parseFn); - public static Func GetParseFn(Type type) - { - ParseArrayOfElementsDelegate parseFn; - if (ParseDelegateCache.TryGetValue(type, out parseFn)) return parseFn.Invoke; + public static Func GetParseFn(Type type) + { + var func = GetParseStringSpanFn(type); + return (s, d) => func(s.AsSpan(), v => d(v.ToString())); + } + + private static readonly Type[] signature = {typeof(ReadOnlySpan), typeof(ParseStringSpanDelegate)}; + + public static ParseArrayOfElementsDelegate GetParseStringSpanFn(Type type) + { + if (ParseDelegateCache.TryGetValue(type, out var parseFn)) return parseFn.Invoke; var genericType = typeof(DeserializeArrayWithElements<,>).MakeGenericType(type, typeof(TSerializer)); - var mi = genericType.GetMethod("ParseGenericArray", BindingFlags.Public | BindingFlags.Static); - parseFn = (ParseArrayOfElementsDelegate)Delegate.CreateDelegate(typeof(ParseArrayOfElementsDelegate), mi); + var mi = genericType.GetStaticMethod("ParseGenericArray", signature); + parseFn = (ParseArrayOfElementsDelegate)mi.CreateDelegate(typeof(ParseArrayOfElementsDelegate)); Dictionary snapshot, newCache; do @@ -44,145 +50,155 @@ public static Func GetParseFn(Type type) } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseDelegateCache, newCache, snapshot), snapshot)); - return parseFn.Invoke; - } - } - - internal static class DeserializeArrayWithElements - where TSerializer : ITypeSerializer - { - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - - public static T[] ParseGenericArray(string value, ParseStringDelegate elementParseFn) - { - if ((value = DeserializeListWithElements.StripList(value)) == null) return null; - if (value == string.Empty) return new T[0]; - - if (value[0] == JsWriter.MapStartChar) - { - var itemValues = new List(); - var i = 0; - do - { - itemValues.Add(Serializer.EatTypeValue(value, ref i)); - Serializer.EatItemSeperatorOrMapEndChar(value, ref i); - } while (i < value.Length); - - var results = new T[itemValues.Count]; - for (var j=0; j < itemValues.Count; j++) - { - results[j] = (T)elementParseFn(itemValues[j]); - } - return results; - } - else - { - var to = new List(); - var valueLength = value.Length; - - var i = 0; - while (i < valueLength) - { - var elementValue = Serializer.EatValue(value, ref i); - var listValue = elementValue; - to.Add((T)elementParseFn(listValue)); - if(Serializer.EatItemSeperatorOrMapEndChar(value, ref i) + return parseFn.Invoke; + } + } + + public static class DeserializeArrayWithElements + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + public static T[] ParseGenericArray(string value, ParseStringDelegate elementParseFn) => + ParseGenericArray(value.AsSpan(), v => elementParseFn(v.ToString())); + + public static T[] ParseGenericArray(ReadOnlySpan value, ParseStringSpanDelegate elementParseFn) + { + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new T[0]; + + if (value[0] == JsWriter.MapStartChar) + { + var itemValues = new List(); + var i = 0; + do + { + var spanValue = Serializer.EatTypeValue(value, ref i); + itemValues.Add((T)elementParseFn(spanValue)); + Serializer.EatItemSeperatorOrMapEndChar(value, ref i); + } while (i < value.Length); + + return itemValues.ToArray(); + } + else + { + var to = new List(); + var valueLength = value.Length; + + var i = 0; + while (i < valueLength) + { + var elementValue = Serializer.EatValue(value, ref i); + var listValue = elementValue; + to.Add((T)elementParseFn(listValue)); + if (Serializer.EatItemSeperatorOrMapEndChar(value, ref i) && i == valueLength) - { + { // If we ate a separator and we are at the end of the value, // it means the last element is empty => add default to.Add(default(T)); - } - } + } + } + + return to.ToArray(); + } + } + } - return to.ToArray(); - } - } - } + internal static class DeserializeArray + where TSerializer : ITypeSerializer + { + private static Dictionary ParseDelegateCache = new Dictionary(); - internal static class DeserializeArray - where TSerializer : ITypeSerializer - { - private static Dictionary ParseDelegateCache = new Dictionary(); + public static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSpanFn(type)(v.AsSpan()); - public static ParseStringDelegate GetParseFn(Type type) - { - ParseStringDelegate parseFn; - if (ParseDelegateCache.TryGetValue(type, out parseFn)) return parseFn; + public static ParseStringSpanDelegate GetParseStringSpanFn(Type type) + { + if (ParseDelegateCache.TryGetValue(type, out var parseFn)) return parseFn; var genericType = typeof(DeserializeArray<,>).MakeGenericType(type, typeof(TSerializer)); - var mi = genericType.GetMethod("GetParseFn", BindingFlags.Public | BindingFlags.Static); - var parseFactoryFn = (Func)Delegate.CreateDelegate( - typeof(Func), mi); + + var mi = genericType.GetStaticMethod("GetParseStringSpanFn"); + var parseFactoryFn = (Func)mi.MakeDelegate( + typeof(Func)); parseFn = parseFactoryFn(); - - Dictionary snapshot, newCache; + + Dictionary snapshot, newCache; do { snapshot = ParseDelegateCache; - newCache = new Dictionary(ParseDelegateCache); - newCache[type] = parseFn; + newCache = new Dictionary(ParseDelegateCache) {[type] = parseFn}; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseDelegateCache, newCache, snapshot), snapshot)); - return parseFn; - } - } - - internal static class DeserializeArray - where TSerializer : ITypeSerializer - { - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - - private static readonly ParseStringDelegate CacheFn; - - static DeserializeArray() - { - CacheFn = GetParseFn(); - } - - public static ParseStringDelegate Parse - { - get { return CacheFn; } - } - - public static ParseStringDelegate GetParseFn() - { - var type = typeof (T); - if (!type.IsArray) - throw new ArgumentException(string.Format("Type {0} is not an Array type", type.FullName)); - - if (type == typeof(string[])) - return ParseStringArray; - if (type == typeof(byte[])) - return ParseByteArray; - - var elementType = type.GetElementType(); - var elementParseFn = Serializer.GetParseFn(elementType); - if (elementParseFn != null) - { - var parseFn = DeserializeArrayWithElements.GetParseFn(elementType); - return value => parseFn(value, elementParseFn); - } - return null; - } - - public static string[] ParseStringArray(string value) - { - if ((value = DeserializeListWithElements.StripList(value)) == null) return null; - return value == string.Empty - ? new string[0] - : DeserializeListWithElements.ParseStringList(value).ToArray(); - } - - public static byte[] ParseByteArray(string value) - { - if ((value = DeserializeListWithElements.StripList(value)) == null) return null; - if ((value = Serializer.UnescapeSafeString(value)) == null) return null; - return value == string.Empty - ? new byte[0] - : Convert.FromBase64String(value); - } - } + return parseFn; + } + } + + internal static class DeserializeArray + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + private static readonly ParseStringSpanDelegate CacheFn; + + static DeserializeArray() + { + CacheFn = GetParseStringSpanFn(); + } + + public static ParseStringDelegate Parse => v => CacheFn(v.AsSpan()); + + public static ParseStringSpanDelegate ParseStringSpan => CacheFn; + + public static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); + + public static ParseStringSpanDelegate GetParseStringSpanFn() + { + var type = typeof(T); + if (!type.IsArray) + throw new ArgumentException($"Type {type.FullName} is not an Array type"); + + if (type == typeof(string[])) + return ParseStringArray; + if (type == typeof(byte[])) + return v => ParseByteArray(v.ToString()); + + var elementType = type.GetElementType(); + var elementParseFn = Serializer.GetParseStringSpanFn(elementType); + if (elementParseFn != null) + { + var parseFn = DeserializeArrayWithElements.GetParseStringSpanFn(elementType); + return value => parseFn(value, elementParseFn); + } + return null; + } + + public static string[] ParseStringArray(ReadOnlySpan value) + { + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : TypeConstants.EmptyStringArray; + return DeserializeListWithElements.ParseStringList(value).ToArray(); + } + + public static string[] ParseStringArray(string value) => ParseStringArray(value.AsSpan()); + + public static byte[] ParseByteArray(string value) => ParseByteArray(value.AsSpan()); + + public static byte[] ParseByteArray(ReadOnlySpan value) + { + var isArray = value.Length > 1 && value[0] == '['; + + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : TypeConstants.EmptyByteArray; + + if ((value = Serializer.UnescapeString(value)).IsNullOrEmpty()) + return TypeConstants.EmptyByteArray; + + return !isArray + ? value.ParseBase64() + : DeserializeListWithElements.ParseByteList(value).ToArray(); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeBuiltin.cs b/src/ServiceStack.Text/Common/DeserializeBuiltin.cs index c3827afc8..78471e323 100644 --- a/src/ServiceStack.Text/Common/DeserializeBuiltin.cs +++ b/src/ServiceStack.Text/Common/DeserializeBuiltin.cs @@ -5,116 +5,135 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; -using System.Globalization; +using ServiceStack.Text.Json; namespace ServiceStack.Text.Common { - public static class DeserializeBuiltin - { - private static readonly ParseStringDelegate CachedParseFn; - static DeserializeBuiltin() - { - CachedParseFn = GetParseFn(); - } + public static class DeserializeBuiltin + { + private static readonly ParseStringSpanDelegate CachedParseFn; + static DeserializeBuiltin() + { + CachedParseFn = GetParseStringSpanFn(); + } - public static ParseStringDelegate Parse - { - get { return CachedParseFn; } - } + public static ParseStringDelegate Parse => v => CachedParseFn(v.AsSpan()); - private static ParseStringDelegate GetParseFn() - { - //Note the generic typeof(T) is faster than using var type = typeof(T) - if (typeof(T) == typeof(bool)) - return value => bool.Parse(value); - if (typeof(T) == typeof(byte)) - return value => byte.Parse(value); - if (typeof(T) == typeof(sbyte)) - return value => sbyte.Parse(value); - if (typeof(T) == typeof(short)) - return value => short.Parse(value); - if (typeof(T) == typeof(int)) - return value => int.Parse(value); - if (typeof(T) == typeof(long)) - return value => long.Parse(value); - if (typeof(T) == typeof(float)) - return value => float.Parse(value, CultureInfo.InvariantCulture); - if (typeof(T) == typeof(double)) - return value => double.Parse(value, CultureInfo.InvariantCulture); - if (typeof(T) == typeof(decimal)) - return value => decimal.Parse(value, CultureInfo.InvariantCulture); + public static ParseStringSpanDelegate ParseStringSpan => CachedParseFn; - if (typeof(T) == typeof(Guid)) - return value => new Guid(value); - if (typeof(T) == typeof(DateTime?)) - return value => DateTimeSerializer.ParseShortestNullableXsdDateTime(value); - if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?)) - return value => DateTimeSerializer.ParseShortestXsdDateTime(value); - if (typeof(T) == typeof(DateTimeOffset) || typeof(T) == typeof(DateTimeOffset?)) - return value => DateTimeSerializer.ParseDateTimeOffset(value); - if (typeof(T) == typeof(TimeSpan)) - return value => DateTimeSerializer.ParseTimeSpan(value); - if (typeof(T) == typeof(TimeSpan?)) - return value => DateTimeSerializer.ParseNullableTimeSpan(value); -#if !MONOTOUCH && !SILVERLIGHT && !XBOX && !ANDROID - if (typeof(T) == typeof(System.Data.Linq.Binary)) - return value => new System.Data.Linq.Binary(Convert.FromBase64String(value)); -#endif - if (typeof(T) == typeof(char)) - { - char cValue; - return value => char.TryParse(value, out cValue) ? cValue : '\0'; - } - if (typeof(T) == typeof(ushort)) - return value => ushort.Parse(value); - if (typeof(T) == typeof(uint)) - return value => uint.Parse(value); - if (typeof(T) == typeof(ulong)) - return value => ulong.Parse(value); + private static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); - if (typeof(T) == typeof(bool?)) - return value => string.IsNullOrEmpty(value) ? (bool?)null : bool.Parse(value); - if (typeof(T) == typeof(byte?)) - return value => string.IsNullOrEmpty(value) ? (byte?)null : byte.Parse(value); - if (typeof(T) == typeof(sbyte?)) - return value => string.IsNullOrEmpty(value) ? (sbyte?)null : sbyte.Parse(value); - if (typeof(T) == typeof(short?)) - return value => string.IsNullOrEmpty(value) ? (short?)null : short.Parse(value); - if (typeof(T) == typeof(int?)) - return value => string.IsNullOrEmpty(value) ? (int?)null : int.Parse(value); - if (typeof(T) == typeof(long?)) - return value => string.IsNullOrEmpty(value) ? (long?)null : long.Parse(value); - if (typeof(T) == typeof(float?)) - return value => string.IsNullOrEmpty(value) ? (float?)null : float.Parse(value, CultureInfo.InvariantCulture); - if (typeof(T) == typeof(double?)) - return value => string.IsNullOrEmpty(value) ? (double?)null : double.Parse(value, CultureInfo.InvariantCulture); - if (typeof(T) == typeof(decimal?)) - return value => string.IsNullOrEmpty(value) ? (decimal?)null : decimal.Parse(value, CultureInfo.InvariantCulture); - - if (typeof(T) == typeof(TimeSpan?)) - return value => string.IsNullOrEmpty(value) ? (TimeSpan?)null : TimeSpan.Parse(value); - if (typeof(T) == typeof(Guid?)) - return value => string.IsNullOrEmpty(value) ? (Guid?)null : new Guid(value); - if (typeof(T) == typeof(ushort?)) - return value => string.IsNullOrEmpty(value) ? (ushort?)null : ushort.Parse(value); - if (typeof(T) == typeof(uint?)) - return value => string.IsNullOrEmpty(value) ? (uint?)null : uint.Parse(value); - if (typeof(T) == typeof(ulong?)) - return value => string.IsNullOrEmpty(value) ? (ulong?)null : ulong.Parse(value); - - if (typeof(T) == typeof(char?)) - { - char cValue; - return value => string.IsNullOrEmpty(value) ? (char?)null : char.TryParse(value, out cValue) ? cValue : '\0'; - } - - return null; - } - } + private static ParseStringSpanDelegate GetParseStringSpanFn() + { + var nullableType = Nullable.GetUnderlyingType(typeof(T)); + if (nullableType == null) + { + var typeCode = typeof(T).GetTypeCode(); + switch (typeCode) + { + case TypeCode.Boolean: + return value => value.ParseBoolean(); + case TypeCode.SByte: + return SignedInteger.ParseObject; + case TypeCode.Byte: + return UnsignedInteger.ParseObject; + case TypeCode.Int16: + return SignedInteger.ParseObject; + case TypeCode.UInt16: + return UnsignedInteger.ParseObject; + case TypeCode.Int32: + return SignedInteger.ParseObject; + case TypeCode.UInt32: + return UnsignedInteger.ParseObject; + case TypeCode.Int64: + return SignedInteger.ParseObject; + case TypeCode.UInt64: + return UnsignedInteger.ParseObject; + + case TypeCode.Single: + return value => MemoryProvider.Instance.ParseFloat(value); + case TypeCode.Double: + return value => MemoryProvider.Instance.ParseDouble(value); + case TypeCode.Decimal: + return value => MemoryProvider.Instance.ParseDecimal(value); + case TypeCode.DateTime: + return value => DateTimeSerializer.ParseShortestXsdDateTime(value.ToString()); + case TypeCode.Char: + return value => value.Length == 0 ? (char)0 : value.Length == 1 ? value[0] : JsonTypeSerializer.Unescape(value)[0]; + } + + if (typeof(T) == typeof(Guid)) + return value => value.ParseGuid(); + if (typeof(T) == typeof(DateTimeOffset)) + return value => DateTimeSerializer.ParseDateTimeOffset(value.ToString()); + if (typeof(T) == typeof(TimeSpan)) + return value => DateTimeSerializer.ParseTimeSpan(value.ToString()); +#if NET6_0 + if (typeof(T) == typeof(DateOnly)) + return value => DateOnly.FromDateTime(DateTimeSerializer.ParseShortestXsdDateTime(value.ToString())); + if (typeof(T) == typeof(TimeOnly)) + return value => TimeOnly.FromTimeSpan(DateTimeSerializer.ParseTimeSpan(value.ToString())); +#endif + } + else + { + var typeCode = nullableType.GetTypeCode(); + switch (typeCode) + { + case TypeCode.Boolean: + return value => value.IsNullOrEmpty() + ? (bool?)null + : value.ParseBoolean(); + case TypeCode.SByte: + return SignedInteger.ParseNullableObject; + case TypeCode.Byte: + return UnsignedInteger.ParseNullableObject; + case TypeCode.Int16: + return SignedInteger.ParseNullableObject; + case TypeCode.UInt16: + return UnsignedInteger.ParseNullableObject; + case TypeCode.Int32: + return SignedInteger.ParseNullableObject; + case TypeCode.UInt32: + return UnsignedInteger.ParseNullableObject; + case TypeCode.Int64: + return SignedInteger.ParseNullableObject; + case TypeCode.UInt64: + return UnsignedInteger.ParseNullableObject; + + case TypeCode.Single: + return value => value.IsNullOrEmpty() ? (float?)null : value.ParseFloat(); + case TypeCode.Double: + return value => value.IsNullOrEmpty() ? (double?)null : value.ParseDouble(); + case TypeCode.Decimal: + return value => value.IsNullOrEmpty() ? (decimal?)null : value.ParseDecimal(); + case TypeCode.DateTime: + return value => DateTimeSerializer.ParseShortestNullableXsdDateTime(value.ToString()); + case TypeCode.Char: + return value => value.IsEmpty ? (char?)null : value.Length == 1 ? value[0] : JsonTypeSerializer.Unescape(value)[0]; + } + + if (typeof(T) == typeof(TimeSpan?)) + return value => DateTimeSerializer.ParseNullableTimeSpan(value.ToString()); + if (typeof(T) == typeof(Guid?)) + return value => value.IsNullOrEmpty() ? (Guid?)null : value.ParseGuid(); + if (typeof(T) == typeof(DateTimeOffset?)) + return value => DateTimeSerializer.ParseNullableDateTimeOffset(value.ToString()); +#if NET6_0 + if (typeof(T) == typeof(DateOnly?)) + return value => value.IsNullOrEmpty() ? default : DateOnly.FromDateTime(DateTimeSerializer.ParseShortestXsdDateTime(value.ToString())); + if (typeof(T) == typeof(TimeOnly?)) + return value => value.IsNullOrEmpty() ? default : TimeOnly.FromTimeSpan(DateTimeSerializer.ParseTimeSpan(value.ToString())); +#endif + } + + return null; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeCollection.cs b/src/ServiceStack.Text/Common/DeserializeCollection.cs index 227a53ca9..40e99b0c0 100644 --- a/src/ServiceStack.Text/Common/DeserializeCollection.cs +++ b/src/ServiceStack.Text/Common/DeserializeCollection.cs @@ -5,18 +5,14 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.Collections.Generic; -using System.Reflection; using System.Threading; -#if WINDOWS_PHONE -using ServiceStack.Text.WP; -#endif namespace ServiceStack.Text.Common { @@ -25,7 +21,9 @@ internal static class DeserializeCollection { private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - public static ParseStringDelegate GetParseMethod(Type type) + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); + + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) { var collectionInterface = type.GetTypeWithGenericInterfaceOf(typeof(ICollection<>)); if (collectionInterface == null) @@ -38,13 +36,12 @@ public static ParseStringDelegate GetParseMethod(Type type) if (type.HasInterface(typeof(ICollection))) return value => ParseIntCollection(value, type); - var elementType = collectionInterface.GetGenericArguments()[0]; - - var supportedTypeParseMethod = Serializer.GetParseFn(elementType); + var elementType = collectionInterface.GetGenericArguments()[0]; + var supportedTypeParseMethod = Serializer.GetParseStringSpanFn(elementType); if (supportedTypeParseMethod != null) { var createCollectionType = type.HasAnyTypeDefinitionsOf(typeof(ICollection<>)) - ? null : type; + ? null : type; return value => ParseCollectionType(value, createCollectionType, elementType, supportedTypeParseMethod); } @@ -52,40 +49,52 @@ public static ParseStringDelegate GetParseMethod(Type type) return null; } - public static ICollection ParseStringCollection(string value, Type createType) + public static ICollection ParseStringCollection(string value, Type createType) => ParseStringCollection(value.AsSpan(), createType); + + public static ICollection ParseStringCollection(ReadOnlySpan value, Type createType) { var items = DeserializeArrayWithElements.ParseGenericArray(value, Serializer.ParseString); return CollectionExtensions.CreateAndPopulate(createType, items); } - public static ICollection ParseIntCollection(string value, Type createType) + public static ICollection ParseIntCollection(string value, Type createType) => ParseIntCollection(value.AsSpan(), createType); + + public static ICollection ParseIntCollection(ReadOnlySpan value, Type createType) { - var items = DeserializeArrayWithElements.ParseGenericArray(value, x => int.Parse(x)); + var items = DeserializeArrayWithElements.ParseGenericArray(value, x => x.ParseInt32()); return CollectionExtensions.CreateAndPopulate(createType, items); } - public static ICollection ParseCollection(string value, Type createType, ParseStringDelegate parseFn) + public static ICollection ParseCollection(string value, Type createType, ParseStringDelegate parseFn) => + ParseCollection(value.AsSpan(), createType, v => parseFn(v.ToString())); + + public static ICollection ParseCollection(ReadOnlySpan value, Type createType, ParseStringSpanDelegate parseFn) { - if (value == null) return null; + if (value.IsEmpty) return null; var items = DeserializeArrayWithElements.ParseGenericArray(value, parseFn); return CollectionExtensions.CreateAndPopulate(createType, items); } - private static Dictionary ParseDelegateCache - = new Dictionary(); + private static Dictionary ParseDelegateCache + = new Dictionary(); + + private delegate object ParseCollectionDelegate(ReadOnlySpan value, Type createType, ParseStringSpanDelegate parseFn); + + public static object ParseCollectionType(string value, Type createType, Type elementType, ParseStringDelegate parseFn) => + ParseCollectionType(value.AsSpan(), createType, elementType, v => parseFn(v.ToString())); - private delegate object ParseCollectionDelegate(string value, Type createType, ParseStringDelegate parseFn); - public static object ParseCollectionType(string value, Type createType, Type elementType, ParseStringDelegate parseFn) + static Type[] arguments = { typeof (ReadOnlySpan), typeof(Type), typeof(ParseStringSpanDelegate) }; + + public static object ParseCollectionType(ReadOnlySpan value, Type createType, Type elementType, ParseStringSpanDelegate parseFn) { - ParseCollectionDelegate parseDelegate; - if (ParseDelegateCache.TryGetValue(elementType, out parseDelegate)) + if (ParseDelegateCache.TryGetValue(elementType, out var parseDelegate)) return parseDelegate(value, createType, parseFn); - var mi = typeof(DeserializeCollection).GetMethod("ParseCollection", BindingFlags.Static | BindingFlags.Public); + var mi = typeof(DeserializeCollection).GetStaticMethod("ParseCollection", arguments); var genericMi = mi.MakeGenericMethod(new[] { elementType }); - parseDelegate = (ParseCollectionDelegate)Delegate.CreateDelegate(typeof(ParseCollectionDelegate), genericMi); + parseDelegate = (ParseCollectionDelegate)genericMi.MakeDelegate(typeof(ParseCollectionDelegate)); Dictionary snapshot, newCache; do diff --git a/src/ServiceStack.Text/Common/DeserializeCustomGenericType.cs b/src/ServiceStack.Text/Common/DeserializeCustomGenericType.cs new file mode 100644 index 000000000..1116e0e6f --- /dev/null +++ b/src/ServiceStack.Text/Common/DeserializeCustomGenericType.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using ServiceStack.Text.Json; + +namespace ServiceStack.Text.Common +{ + internal static class DeserializeCustomGenericType + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); + + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) + { + if (type.Name.IndexOf("Tuple`", StringComparison.Ordinal) >= 0) + return x => ParseTuple(type, x); + + return null; + } + + public static object ParseTuple(Type tupleType, string value) => ParseTuple(tupleType, value.AsSpan()); + + public static object ParseTuple(Type tupleType, ReadOnlySpan value) + { + var index = 0; + Serializer.EatMapStartChar(value, ref index); + if (JsonTypeSerializer.IsEmptyMap(value, index)) + return tupleType.CreateInstance(); + + var genericArgs = tupleType.GetGenericArguments(); + var argValues = new object[genericArgs.Length]; + var valueLength = value.Length; + while (index < valueLength) + { + var keyValue = Serializer.EatMapKey(value, ref index); + Serializer.EatMapKeySeperator(value, ref index); + var elementValue = Serializer.EatValue(value, ref index); + if (keyValue.IsEmpty) continue; + + var keyIndex = keyValue.Slice("Item".Length).ParseInt32() - 1; + var parseFn = Serializer.GetParseStringSpanFn(genericArgs[keyIndex]); + argValues[keyIndex] = parseFn(elementValue); + + Serializer.EatItemSeperatorOrMapEndChar(value, ref index); + } + + var ctor = tupleType.GetConstructors() + .First(x => x.GetParameters().Length == genericArgs.Length); + return ctor.Invoke(argValues); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeDictionary.cs b/src/ServiceStack.Text/Common/DeserializeDictionary.cs index aa11fa103..078362b50 100644 --- a/src/ServiceStack.Text/Common/DeserializeDictionary.cs +++ b/src/ServiceStack.Text/Common/DeserializeDictionary.cs @@ -5,77 +5,86 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.Collections; using System.Collections.Generic; -using System.Reflection; -using System.Text; using System.Threading; -using ServiceStack.Text.Json; namespace ServiceStack.Text.Common { - internal static class DeserializeDictionary - where TSerializer : ITypeSerializer - { - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - - const int KeyIndex = 0; - const int ValueIndex = 1; - - public static ParseStringDelegate GetParseMethod(Type type) - { - var mapInterface = type.GetTypeWithGenericInterfaceOf(typeof(IDictionary<,>)); - if (mapInterface == null) { -#if !SILVERLIGHT - if (type == typeof(Hashtable)) + public static class DeserializeDictionary + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + const int KeyIndex = 0; + const int ValueIndex = 1; + + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); + + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) + { + var mapInterface = type.GetTypeWithGenericInterfaceOf(typeof(IDictionary<,>)); + if (mapInterface == null) + { + var fn = PclExport.Instance.GetDictionaryParseStringSpanMethod(type); + if (fn != null) + return fn; + + if (type == typeof(IDictionary)) { - return ParseHashtable; + return GetParseStringSpanMethod(typeof(Dictionary)); } -#endif - if (type == typeof(IDictionary)) + if (typeof(IDictionary).IsAssignableFrom(type)) { - return GetParseMethod(typeof(Dictionary)); - } - throw new ArgumentException(string.Format("Type {0} is not of type IDictionary<,>", type.FullName)); - } + return s => ParseIDictionary(s, type); + } + + throw new ArgumentException($"Type {type.FullName} is not of type IDictionary<,>"); + } - //optimized access for regularly used types + //optimized access for regularly used types if (type == typeof(Dictionary)) - { return ParseStringDictionary; - } if (type == typeof(JsonObject)) - { return ParseJsonObject; + if (typeof(JsonObject).IsAssignableFrom(type)) + { + var method = typeof(DeserializeDictionary).GetMethod("ParseInheritedJsonObject"); + method = method.MakeGenericMethod(type); + return Delegate.CreateDelegate(typeof(ParseStringSpanDelegate), method) as ParseStringSpanDelegate; } var dictionaryArgs = mapInterface.GetGenericArguments(); + var keyTypeParseMethod = Serializer.GetParseStringSpanFn(dictionaryArgs[KeyIndex]); + if (keyTypeParseMethod == null) return null; - var keyTypeParseMethod = Serializer.GetParseFn(dictionaryArgs[KeyIndex]); - if (keyTypeParseMethod == null) return null; + var valueTypeParseMethod = Serializer.GetParseStringSpanFn(dictionaryArgs[ValueIndex]); + if (valueTypeParseMethod == null) return null; - var valueTypeParseMethod = Serializer.GetParseFn(dictionaryArgs[ValueIndex]); - if (valueTypeParseMethod == null) return null; + var createMapType = type.HasAnyTypeDefinitionsOf(typeof(Dictionary<,>), typeof(IDictionary<,>)) + ? null : type; - var createMapType = type.HasAnyTypeDefinitionsOf(typeof(Dictionary<,>), typeof(IDictionary<,>)) - ? null : type; - - return value => ParseDictionaryType(value, createMapType, dictionaryArgs, keyTypeParseMethod, valueTypeParseMethod); - } + return value => ParseDictionaryType(value, createMapType, dictionaryArgs, keyTypeParseMethod, valueTypeParseMethod); + } - public static JsonObject ParseJsonObject(string value) + public static JsonObject ParseJsonObject(string value) => ParseJsonObject(value.AsSpan()); + + public static T ParseInheritedJsonObject(ReadOnlySpan value) where T : JsonObject, new() { - var index = VerifyAndGetStartIndex(value, typeof(JsonObject)); + if (value.Length == 0) + return null; - var result = new JsonObject(); + var index = VerifyAndGetStartIndex(value, typeof(T)); + + var result = new T(); - if (JsonTypeSerializer.IsEmptyMap(value)) return result; + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return result; var valueLength = value.Length; while (index < valueLength) @@ -83,9 +92,10 @@ public static JsonObject ParseJsonObject(string value) var keyValue = Serializer.EatMapKey(value, ref index); Serializer.EatMapKeySeperator(value, ref index); var elementValue = Serializer.EatValue(value, ref index); + if (keyValue.IsEmpty) continue; - var mapKey = keyValue; - var mapValue = elementValue; + var mapKey = keyValue.ToString(); + var mapValue = elementValue.Value(); result[mapKey] = mapValue; @@ -95,14 +105,16 @@ public static JsonObject ParseJsonObject(string value) return result; } -#if !SILVERLIGHT - public static Hashtable ParseHashtable(string value) + public static JsonObject ParseJsonObject(ReadOnlySpan value) { - var index = VerifyAndGetStartIndex(value, typeof(Hashtable)); + if (value.Length == 0) + return null; - var result = new Hashtable(); + var index = VerifyAndGetStartIndex(value, typeof(JsonObject)); + + var result = new JsonObject(); - if (JsonTypeSerializer.IsEmptyMap(value)) return result; + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return result; var valueLength = value.Length; while (index < valueLength) @@ -110,9 +122,10 @@ public static Hashtable ParseHashtable(string value) var keyValue = Serializer.EatMapKey(value, ref index); Serializer.EatMapKeySeperator(value, ref index); var elementValue = Serializer.EatValue(value, ref index); + if (keyValue.IsEmpty) continue; - var mapKey = keyValue; - var mapValue = elementValue; + var mapKey = keyValue.ToString(); + var mapValue = elementValue.Value(); result[mapKey] = mapValue; @@ -121,15 +134,19 @@ public static Hashtable ParseHashtable(string value) return result; } -#endif - public static Dictionary ParseStringDictionary(string value) + public static Dictionary ParseStringDictionary(string value) => ParseStringDictionary(value.AsSpan()); + + public static Dictionary ParseStringDictionary(ReadOnlySpan value) { + if (value.IsEmpty) + return null; + var index = VerifyAndGetStartIndex(value, typeof(Dictionary)); var result = new Dictionary(); - if (JsonTypeSerializer.IsEmptyMap(value)) return result; + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return result; var valueLength = value.Length; while (index < valueLength) @@ -137,11 +154,12 @@ public static Dictionary ParseStringDictionary(string value) var keyValue = Serializer.EatMapKey(value, ref index); Serializer.EatMapKeySeperator(value, ref index); var elementValue = Serializer.EatValue(value, ref index); + if (keyValue.IsEmpty) continue; var mapKey = Serializer.UnescapeString(keyValue); var mapValue = Serializer.UnescapeString(elementValue); - result[mapKey] = mapValue; + result[mapKey.ToString()] = mapValue.Value(); Serializer.EatItemSeperatorOrMapEndChar(value, ref index); } @@ -149,127 +167,212 @@ public static Dictionary ParseStringDictionary(string value) return result; } - public static IDictionary ParseDictionary( - string value, Type createMapType, - ParseStringDelegate parseKeyFn, ParseStringDelegate parseValueFn) - { - if (value == null) return null; + public static IDictionary ParseIDictionary(string value, Type dictType) => ParseIDictionary(value.AsSpan(), dictType); - var tryToParseItemsAsDictionaries = - JsConfig.ConvertObjectTypesIntoStringDictionary && typeof(TValue) == typeof(object); - var tryToParseItemsAsPrimitiveTypes = - JsConfig.TryToParsePrimitiveTypeValues && typeof(TValue) == typeof(object); + public static IDictionary ParseIDictionary(ReadOnlySpan value, Type dictType) + { + if (value.IsEmpty) return null; + + var index = VerifyAndGetStartIndex(value, dictType); - var index = VerifyAndGetStartIndex(value, createMapType); + var valueParseMethod = Serializer.GetParseStringSpanFn(typeof(object)); + if (valueParseMethod == null) return null; - var to = (createMapType == null) - ? new Dictionary() - : (IDictionary)createMapType.CreateInstance(); + var to = (IDictionary)dictType.CreateInstance(); - if (JsonTypeSerializer.IsEmptyMap(value)) return to; + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return to; - var valueLength = value.Length; - while (index < valueLength) + var valueLength = value.Length; + while (index < valueLength) { - var keyValue = Serializer.EatMapKey(value, ref index); - Serializer.EatMapKeySeperator(value, ref index); - var elementStartIndex = index; - var elementValue = Serializer.EatTypeValue(value, ref index); + var keyValue = Serializer.EatMapKey(value, ref index); + Serializer.EatMapKeySeperator(value, ref index); + var elementStartIndex = index; + var elementValue = Serializer.EatTypeValue(value, ref index); + if (keyValue.IsEmpty) continue; - var mapKey = (TKey)parseKeyFn(keyValue); + var mapKey = valueParseMethod(keyValue); - if (tryToParseItemsAsDictionaries) - { + if (elementStartIndex < valueLength) + { Serializer.EatWhitespace(value, ref elementStartIndex); - if (elementStartIndex < valueLength && value[elementStartIndex] == JsWriter.MapStartChar) - { - var tmpMap = ParseDictionary(elementValue, createMapType, parseKeyFn, parseValueFn); - if (tmpMap != null && tmpMap.Count > 0) { - to[mapKey] = (TValue) tmpMap; + to[mapKey] = DeserializeType.ParsePrimitive(elementValue.Value(), value[elementStartIndex]); + } + else + { + to[mapKey] = valueParseMethod(elementValue); + } + + Serializer.EatItemSeperatorOrMapEndChar(value, ref index); + } + + return to; + } + + public static IDictionary ParseDictionary( + string value, Type createMapType, + ParseStringDelegate parseKeyFn, ParseStringDelegate parseValueFn) + { + return ParseDictionary(value.AsSpan(), + createMapType, + v => parseKeyFn(v.ToString()), + v => parseValueFn(v.ToString()) + ); + } + + + public static IDictionary ParseDictionary( + ReadOnlySpan value, Type createMapType, + ParseStringSpanDelegate parseKeyFn, ParseStringSpanDelegate parseValueFn) + { + if (value.IsEmpty) return null; + + var to = (createMapType == null) + ? new Dictionary() + : (IDictionary)createMapType.CreateInstance(); + + var objDeserializer = Json.JsonTypeSerializer.Instance.ObjectDeserializer; + if (to is Dictionary && objDeserializer != null && typeof(TSerializer) == typeof(Json.JsonTypeSerializer)) + return (IDictionary) objDeserializer(value); + + var config = JsConfig.GetConfig(); + + var tryToParseItemsAsDictionaries = + config.ConvertObjectTypesIntoStringDictionary && typeof(TValue) == typeof(object); + var tryToParseItemsAsPrimitiveTypes = + config.TryToParsePrimitiveTypeValues && typeof(TValue) == typeof(object); + + var index = VerifyAndGetStartIndex(value, createMapType); + + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return to; + + var valueLength = value.Length; + while (index < valueLength) + { + var keyValue = Serializer.EatMapKey(value, ref index); + Serializer.EatMapKeySeperator(value, ref index); + var elementStartIndex = index; + var elementValue = Serializer.EatTypeValue(value, ref index); + if (keyValue.IsNullOrEmpty()) continue; + + TKey mapKey = (TKey)parseKeyFn(keyValue); + + if (tryToParseItemsAsDictionaries) + { + Serializer.EatWhitespace(value, ref elementStartIndex); + if (elementStartIndex < valueLength && value[elementStartIndex] == JsWriter.MapStartChar) + { + var tmpMap = ParseDictionary(elementValue, createMapType, parseKeyFn, parseValueFn); + if (tmpMap != null && tmpMap.Count > 0) + { + to[mapKey] = (TValue)tmpMap; } - } - else if (elementStartIndex < valueLength && value[elementStartIndex] == JsWriter.ListStartChar) + } + else if (elementStartIndex < valueLength && value[elementStartIndex] == JsWriter.ListStartChar) { - to[mapKey] = (TValue) DeserializeList, TSerializer>.Parse(elementValue); - } - else + to[mapKey] = (TValue)DeserializeList, TSerializer>.ParseStringSpan(elementValue); + } + else { - to[mapKey] = (TValue) (tryToParseItemsAsPrimitiveTypes && elementStartIndex < valueLength - ? DeserializeType.ParsePrimitive(elementValue, value[elementStartIndex]) - : parseValueFn(elementValue)); + to[mapKey] = (TValue)(tryToParseItemsAsPrimitiveTypes && elementStartIndex < valueLength + ? DeserializeType.ParsePrimitive(elementValue.Value(), value[elementStartIndex]) + : parseValueFn(elementValue).Value()); } - } + } else { - if (tryToParseItemsAsPrimitiveTypes && elementStartIndex < valueLength) { + if (tryToParseItemsAsPrimitiveTypes && elementStartIndex < valueLength) + { Serializer.EatWhitespace(value, ref elementStartIndex); - to[mapKey] = (TValue) DeserializeType.ParsePrimitive(elementValue, value[elementStartIndex]); - } else { - to[mapKey] = (TValue) parseValueFn(elementValue); + to[mapKey] = (TValue)DeserializeType.ParsePrimitive(elementValue.Value(), value[elementStartIndex]); } - } - - Serializer.EatItemSeperatorOrMapEndChar(value, ref index); - } - - return to; - } - - private static int VerifyAndGetStartIndex(string value, Type createMapType) - { - var index = 0; - if (!Serializer.EatMapStartChar(value, ref index)) - { - //Don't throw ex because some KeyValueDataContractDeserializer don't have '{}' - Tracer.Instance.WriteDebug("WARN: Map definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", - JsWriter.MapStartChar, createMapType != null ? createMapType.Name : "Dictionary<,>", value.Substring(0, value.Length < 50 ? value.Length : 50)); - } - return index; - } - - private static Dictionary ParseDelegateCache - = new Dictionary(); - - private delegate object ParseDictionaryDelegate(string value, Type createMapType, - ParseStringDelegate keyParseFn, ParseStringDelegate valueParseFn); - - public static object ParseDictionaryType(string value, Type createMapType, Type[] argTypes, - ParseStringDelegate keyParseFn, ParseStringDelegate valueParseFn) - { - - ParseDictionaryDelegate parseDelegate; - var key = GetTypesKey(argTypes); - if (ParseDelegateCache.TryGetValue(key, out parseDelegate)) + else + { + to[mapKey] = (TValue)parseValueFn(elementValue).Value(); + } + } + + Serializer.EatItemSeperatorOrMapEndChar(value, ref index); + } + + return to; + } + + private static int VerifyAndGetStartIndex(ReadOnlySpan value, Type createMapType) + { + var index = 0; + if (value.Length > 0 && !Serializer.EatMapStartChar(value, ref index)) + { + //Don't throw ex because some KeyValueDataContractDeserializer don't have '{}' + Tracer.Instance.WriteDebug("WARN: Map definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", + JsWriter.MapStartChar, createMapType != null ? createMapType.Name : "Dictionary<,>", value.Substring(0, value.Length < 50 ? value.Length : 50)); + } + return index; + } + + private static Dictionary ParseDelegateCache + = new Dictionary(); + + private delegate object ParseDictionaryDelegate(ReadOnlySpan value, Type createMapType, + ParseStringSpanDelegate keyParseFn, ParseStringSpanDelegate valueParseFn); + + public static object ParseDictionaryType(string value, Type createMapType, Type[] argTypes, + ParseStringDelegate keyParseFn, ParseStringDelegate valueParseFn) => + ParseDictionaryType(value.AsSpan(), createMapType, argTypes, + v => keyParseFn(v.ToString()), v => valueParseFn(v.ToString())); + + static readonly Type[] signature = {typeof(ReadOnlySpan), typeof(Type), typeof(ParseStringSpanDelegate), typeof(ParseStringSpanDelegate)}; + + public static object ParseDictionaryType(ReadOnlySpan value, Type createMapType, Type[] argTypes, + ParseStringSpanDelegate keyParseFn, ParseStringSpanDelegate valueParseFn) + { + var key = new TypesKey(argTypes[0], argTypes[1]); + if (ParseDelegateCache.TryGetValue(key, out var parseDelegate)) return parseDelegate(value, createMapType, keyParseFn, valueParseFn); - var mi = typeof(DeserializeDictionary).GetMethod("ParseDictionary", BindingFlags.Static | BindingFlags.Public); + var mi = typeof(DeserializeDictionary).GetStaticMethod("ParseDictionary", signature); var genericMi = mi.MakeGenericMethod(argTypes); - parseDelegate = (ParseDictionaryDelegate)Delegate.CreateDelegate(typeof(ParseDictionaryDelegate), genericMi); + parseDelegate = (ParseDictionaryDelegate)genericMi.MakeDelegate(typeof(ParseDictionaryDelegate)); - Dictionary snapshot, newCache; + Dictionary snapshot, newCache; do { snapshot = ParseDelegateCache; - newCache = new Dictionary(ParseDelegateCache); - newCache[key] = parseDelegate; + newCache = new Dictionary(ParseDelegateCache) { + [key] = parseDelegate + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseDelegateCache, newCache, snapshot), snapshot)); - return parseDelegate(value, createMapType, keyParseFn, valueParseFn); - } - - private static string GetTypesKey(params Type[] types) - { - var sb = new StringBuilder(256); - foreach (var type in types) - { - if (sb.Length > 0) - sb.Append(">"); - - sb.Append(type.FullName); - } - return sb.ToString(); - } - } + return parseDelegate(value, createMapType, keyParseFn, valueParseFn); + } + + struct TypesKey + { + Type Type1 { get; } + Type Type2 { get; } + + readonly int hashcode; + + public TypesKey(Type type1, Type type2) + { + Type1 = type1; + Type2 = type2; + unchecked + { + hashcode = Type1.GetHashCode() ^ (37 * Type2.GetHashCode()); + } + } + + public override bool Equals(object obj) + { + var types = (TypesKey)obj; + + return Type1 == types.Type1 && Type2 == types.Type2; + } + + public override int GetHashCode() => hashcode; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeDynamic.cs b/src/ServiceStack.Text/Common/DeserializeDynamic.cs deleted file mode 100644 index 844b015b6..000000000 --- a/src/ServiceStack.Text/Common/DeserializeDynamic.cs +++ /dev/null @@ -1,86 +0,0 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// -#if NET40 -using System; -using System.Collections.Generic; -using System.Dynamic; -using ServiceStack.Text.Json; - -namespace ServiceStack.Text.Common -{ - internal static class DeserializeDynamic - where TSerializer : ITypeSerializer - { - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - - private static readonly ParseStringDelegate CachedParseFn; - static DeserializeDynamic() - { - CachedParseFn = ParseDynamic; - } - - public static ParseStringDelegate Parse - { - get { return CachedParseFn; } - } - - public static IDynamicMetaObjectProvider ParseDynamic(string value) - { - var index = VerifyAndGetStartIndex(value, typeof(ExpandoObject)); - - var result = new ExpandoObject(); - - if (JsonTypeSerializer.IsEmptyMap(value)) return result; - - var container = (IDictionary) result; - - var tryToParsePrimitiveTypes = JsConfig.TryToParsePrimitiveTypeValues; - - var valueLength = value.Length; - while (index < valueLength) - { - var keyValue = Serializer.EatMapKey(value, ref index); - Serializer.EatMapKeySeperator(value, ref index); - var elementValue = Serializer.EatValue(value, ref index); - - var mapKey = Serializer.UnescapeString(keyValue); - - if (JsonUtils.IsJsObject(elementValue)) { - container[mapKey] = ParseDynamic(elementValue); - } else if (JsonUtils.IsJsArray(elementValue)) { - container[mapKey] = DeserializeList, TSerializer>.Parse(elementValue); - } else if (tryToParsePrimitiveTypes) { - container[mapKey] = DeserializeType.ParsePrimitive(elementValue) ?? Serializer.UnescapeString(elementValue); - } else { - container[mapKey] = Serializer.UnescapeString(elementValue); - } - - Serializer.EatItemSeperatorOrMapEndChar(value, ref index); - } - - return result; - } - - private static int VerifyAndGetStartIndex(string value, Type createMapType) - { - var index = 0; - if (!Serializer.EatMapStartChar(value, ref index)) - { - //Don't throw ex because some KeyValueDataContractDeserializer don't have '{}' - Tracer.Instance.WriteDebug("WARN: Map definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", - JsWriter.MapStartChar, createMapType != null ? createMapType.Name : "Dictionary<,>", value.Substring(0, value.Length < 50 ? value.Length : 50)); - } - return index; - } - } -} -#endif \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeKeyValuePair.cs b/src/ServiceStack.Text/Common/DeserializeKeyValuePair.cs index 1c20665ed..8ded3e2ca 100644 --- a/src/ServiceStack.Text/Common/DeserializeKeyValuePair.cs +++ b/src/ServiceStack.Text/Common/DeserializeKeyValuePair.cs @@ -5,17 +5,14 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; -using System.Collections; using System.Collections.Generic; -using System.Reflection; using System.Runtime.Serialization; -using System.Text; using System.Threading; using ServiceStack.Text.Json; @@ -29,16 +26,17 @@ internal static class DeserializeKeyValuePair const int KeyIndex = 0; const int ValueIndex = 1; - public static ParseStringDelegate GetParseMethod(Type type) + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); + + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) { var mapInterface = type.GetTypeWithGenericInterfaceOf(typeof(KeyValuePair<,>)); var keyValuePairArgs = mapInterface.GetGenericArguments(); - - var keyTypeParseMethod = Serializer.GetParseFn(keyValuePairArgs[KeyIndex]); + var keyTypeParseMethod = Serializer.GetParseStringSpanFn(keyValuePairArgs[KeyIndex]); if (keyTypeParseMethod == null) return null; - var valueTypeParseMethod = Serializer.GetParseFn(keyValuePairArgs[ValueIndex]); + var valueTypeParseMethod = Serializer.GetParseStringSpanFn(keyValuePairArgs[ValueIndex]); if (valueTypeParseMethod == null) return null; var createMapType = type.HasAnyTypeDefinitionsOf(typeof(KeyValuePair<,>)) @@ -46,16 +44,22 @@ public static ParseStringDelegate GetParseMethod(Type type) return value => ParseKeyValuePairType(value, createMapType, keyValuePairArgs, keyTypeParseMethod, valueTypeParseMethod); } - + public static object ParseKeyValuePair( string value, Type createMapType, - ParseStringDelegate parseKeyFn, ParseStringDelegate parseValueFn) + ParseStringDelegate parseKeyFn, ParseStringDelegate parseValueFn) => + ParseKeyValuePair(value.AsSpan(), createMapType, + v => parseKeyFn(v.ToString()), v => parseValueFn(v.ToString())); + + public static object ParseKeyValuePair( + ReadOnlySpan value, Type createMapType, + ParseStringSpanDelegate parseKeyFn, ParseStringSpanDelegate parseValueFn) { - if (value == null) return default(KeyValuePair); + if (value.IsEmpty) return default(KeyValuePair); - var index = 1; + var index = VerifyAndGetStartIndex(value, createMapType); - if (JsonTypeSerializer.IsEmptyMap(value)) return new KeyValuePair(); + if (JsonTypeSerializer.IsEmptyMap(value, index)) return new KeyValuePair(); var keyValue = default(TKey); var valueValue = default(TValue); @@ -66,36 +70,54 @@ public static object ParseKeyValuePair( Serializer.EatMapKeySeperator(value, ref index); var keyElementValue = Serializer.EatTypeValue(value, ref index); - if (string.Compare(key, "key", StringComparison.InvariantCultureIgnoreCase) == 0) + if (key.CompareIgnoreCase("key".AsSpan())) keyValue = (TKey)parseKeyFn(keyElementValue); - else if (string.Compare(key, "value", StringComparison.InvariantCultureIgnoreCase) == 0) - valueValue = (TValue) parseValueFn(keyElementValue); - else - throw new SerializationException("Incorrect KeyValuePair property: " + key); + else if (key.CompareIgnoreCase("value".AsSpan())) + valueValue = (TValue)parseValueFn(keyElementValue); + else if (!key.SequenceEqual(JsConfig.TypeAttr.AsSpan())) + throw new SerializationException("Incorrect KeyValuePair property: " + key.ToString()); + Serializer.EatItemSeperatorOrMapEndChar(value, ref index); } return new KeyValuePair(keyValue, valueValue); } + private static int VerifyAndGetStartIndex(ReadOnlySpan value, Type createMapType) + { + var index = 0; + if (!Serializer.EatMapStartChar(value, ref index)) + { + //Don't throw ex because some KeyValueDataContractDeserializer don't have '{}' + Tracer.Instance.WriteDebug("WARN: Map definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", + JsWriter.MapStartChar, createMapType != null ? createMapType.Name : "Dictionary<,>", value.Substring(0, value.Length < 50 ? value.Length : 50)); + } + return index; + } + private static Dictionary ParseDelegateCache = new Dictionary(); - private delegate object ParseKeyValuePairDelegate(string value, Type createMapType, - ParseStringDelegate keyParseFn, ParseStringDelegate valueParseFn); + private delegate object ParseKeyValuePairDelegate(ReadOnlySpan value, Type createMapType, + ParseStringSpanDelegate keyParseFn, ParseStringSpanDelegate valueParseFn); public static object ParseKeyValuePairType(string value, Type createMapType, Type[] argTypes, - ParseStringDelegate keyParseFn, ParseStringDelegate valueParseFn) - { + ParseStringDelegate keyParseFn, ParseStringDelegate valueParseFn) => + ParseKeyValuePairType(value.AsSpan(), createMapType, argTypes, + v => keyParseFn(v.ToString()), v => valueParseFn(v.ToString())); + + static readonly Type[] signature = { typeof(ReadOnlySpan), typeof(Type), typeof(ParseStringSpanDelegate), typeof(ParseStringSpanDelegate) }; - ParseKeyValuePairDelegate parseDelegate; + public static object ParseKeyValuePairType(ReadOnlySpan value, Type createMapType, Type[] argTypes, + ParseStringSpanDelegate keyParseFn, ParseStringSpanDelegate valueParseFn) + { var key = GetTypesKey(argTypes); - if (ParseDelegateCache.TryGetValue(key, out parseDelegate)) + if (ParseDelegateCache.TryGetValue(key, out var parseDelegate)) return parseDelegate(value, createMapType, keyParseFn, valueParseFn); - var mi = typeof(DeserializeKeyValuePair).GetMethod("ParseKeyValuePair", BindingFlags.Static | BindingFlags.Public); + var mi = typeof(DeserializeKeyValuePair).GetStaticMethod("ParseKeyValuePair", signature); var genericMi = mi.MakeGenericMethod(argTypes); - parseDelegate = (ParseKeyValuePairDelegate)Delegate.CreateDelegate(typeof(ParseKeyValuePairDelegate), genericMi); + parseDelegate = (ParseKeyValuePairDelegate)genericMi.MakeDelegate(typeof(ParseKeyValuePairDelegate)); Dictionary snapshot, newCache; do @@ -112,7 +134,7 @@ public static object ParseKeyValuePairType(string value, Type createMapType, Typ private static string GetTypesKey(params Type[] types) { - var sb = new StringBuilder(256); + var sb = StringBuilderThreadStatic.Allocate(); foreach (var type in types) { if (sb.Length > 0) @@ -120,7 +142,7 @@ private static string GetTypesKey(params Type[] types) sb.Append(type.FullName); } - return sb.ToString(); + return StringBuilderThreadStatic.ReturnAndFree(sb); } } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeListWithElements.cs b/src/ServiceStack.Text/Common/DeserializeListWithElements.cs index 1f60e8fbd..bb1f0a575 100644 --- a/src/ServiceStack.Text/Common/DeserializeListWithElements.cs +++ b/src/ServiceStack.Text/Common/DeserializeListWithElements.cs @@ -5,263 +5,334 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Reflection; using System.Threading; -using ServiceStack.Text.Json; namespace ServiceStack.Text.Common { - internal static class DeserializeListWithElements - where TSerializer : ITypeSerializer - { - internal static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + public static class DeserializeListWithElements + where TSerializer : ITypeSerializer + { + internal static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - private static Dictionary ParseDelegateCache - = new Dictionary(); + private static Dictionary ParseDelegateCache + = new Dictionary(); - private delegate object ParseListDelegate(string value, Type createListType, ParseStringDelegate parseFn); + public delegate object ParseListDelegate(ReadOnlySpan value, Type createListType, ParseStringSpanDelegate parseFn); - public static Func GetListTypeParseFn( - Type createListType, Type elementType, ParseStringDelegate parseFn) - { - ParseListDelegate parseDelegate; - if (ParseDelegateCache.TryGetValue(elementType, out parseDelegate)) + public static Func GetListTypeParseFn( + Type createListType, Type elementType, ParseStringDelegate parseFn) + { + var func = GetListTypeParseStringSpanFn(createListType, elementType, v => parseFn(v.ToString())); + return (s, t, d) => func(s.AsSpan(), t, v => d(v.ToString())); + } + + private static readonly Type[] signature = {typeof(ReadOnlySpan), typeof(Type), typeof(ParseStringSpanDelegate)}; + + public static ParseListDelegate GetListTypeParseStringSpanFn( + Type createListType, Type elementType, ParseStringSpanDelegate parseFn) + { + if (ParseDelegateCache.TryGetValue(elementType, out var parseDelegate)) return parseDelegate.Invoke; var genericType = typeof(DeserializeListWithElements<,>).MakeGenericType(elementType, typeof(TSerializer)); - var mi = genericType.GetMethod("ParseGenericList", BindingFlags.Static | BindingFlags.Public); - parseDelegate = (ParseListDelegate)Delegate.CreateDelegate(typeof(ParseListDelegate), mi); + var mi = genericType.GetStaticMethod("ParseGenericList", signature); + parseDelegate = (ParseListDelegate)mi.MakeDelegate(typeof(ParseListDelegate)); Dictionary snapshot, newCache; do { snapshot = ParseDelegateCache; - newCache = new Dictionary(ParseDelegateCache); - newCache[elementType] = parseDelegate; + newCache = new Dictionary(ParseDelegateCache) { [elementType] = parseDelegate }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseDelegateCache, newCache, snapshot), snapshot)); - return parseDelegate.Invoke; - } - - internal static string StripList(string value) - { - if (string.IsNullOrEmpty(value)) - return null; - - const int startQuotePos = 1; - const int endQuotePos = 2; - var ret = value[0] == JsWriter.ListStartChar - ? value.Substring(startQuotePos, value.Length - endQuotePos) - : value; - - var pos = 0; - Serializer.EatWhitespace(ret, ref pos); - return ret.Substring(pos, ret.Length - pos); - } - - public static List ParseStringList(string value) - { - if ((value = StripList(value)) == null) return null; - if (value == string.Empty) return new List(); - - var to = new List(); - var valueLength = value.Length; - - var i = 0; - while (i < valueLength) - { - var elementValue = Serializer.EatValue(value, ref i); + return parseDelegate.Invoke; + } + + public static ReadOnlySpan StripList(ReadOnlySpan value) + { + if (value.IsNullOrEmpty()) + return default; + + value = value.Trim(); + + const int startQuotePos = 1; + const int endQuotePos = 2; + var ret = value[0] == JsWriter.ListStartChar + ? value.Slice(startQuotePos, value.Length - endQuotePos) + : value; + var val = ret.AdvancePastWhitespace(); + if (val.Length == 0) + return TypeConstants.EmptyStringSpan; + return val; + } + + public static List ParseStringList(string value) + { + return ParseStringList(value.AsSpan()); + } + + public static List ParseStringList(ReadOnlySpan value) + { + if ((value = StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new List(); + + var to = new List(); + var valueLength = value.Length; + + var i = 0; + while (i < valueLength) + { + var elementValue = Serializer.EatValue(value, ref i); var listValue = Serializer.UnescapeString(elementValue); - to.Add(listValue); - Serializer.EatItemSeperatorOrMapEndChar(value, ref i); - } - - return to; - } - - public static List ParseIntList(string value) - { - if ((value = StripList(value)) == null) return null; - if (value == string.Empty) return new List(); - - var intParts = value.Split(JsWriter.ItemSeperator); - var intValues = new List(intParts.Length); - foreach (var intPart in intParts) - { - intValues.Add(int.Parse(intPart)); - } - return intValues; - } - } - - internal static class DeserializeListWithElements - where TSerializer : ITypeSerializer - { - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - - public static ICollection ParseGenericList(string value, Type createListType, ParseStringDelegate parseFn) - { - if ((value = DeserializeListWithElements.StripList(value)) == null) return null; - - var isReadOnly = createListType != null - && (createListType.IsGenericType && createListType.GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>)); - - var to = (createListType == null || isReadOnly) - ? new List() - : (ICollection)createListType.CreateInstance(); - - if (value == string.Empty) return to; - - var tryToParseItemsAsPrimitiveTypes = - JsConfig.TryToParsePrimitiveTypeValues && typeof(T) == typeof(object); - - if (!string.IsNullOrEmpty(value)) - { - var valueLength = value.Length; - var i = 0; + to.Add(listValue.Value()); + if (Serializer.EatItemSeperatorOrMapEndChar(value, ref i) && i == valueLength) + { + // If we ate a separator and we are at the end of the value, + // it means the last element is empty => add default + to.Add(null); + } + } + + return to; + } + + public static List ParseIntList(string value) => ParseIntList(value.AsSpan()); + + public static List ParseIntList(ReadOnlySpan value) + { + if ((value = StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new List(); + + var to = new List(); + var valueLength = value.Length; + + var i = 0; + while (i < valueLength) + { + var elementValue = Serializer.EatValue(value, ref i); + to.Add(MemoryProvider.Instance.ParseInt32(elementValue)); + Serializer.EatItemSeperatorOrMapEndChar(value, ref i); + } + + return to; + } + + public static List ParseByteList(string value) => ParseByteList(value.AsSpan()); + + public static List ParseByteList(ReadOnlySpan value) + { + if ((value = StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new List(); + + var to = new List(); + var valueLength = value.Length; + + var i = 0; + while (i < valueLength) + { + var elementValue = Serializer.EatValue(value, ref i); + to.Add(MemoryProvider.Instance.ParseByte(elementValue)); + Serializer.EatItemSeperatorOrMapEndChar(value, ref i); + } + + return to; + } + } + + public static class DeserializeListWithElements + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + public static ICollection ParseGenericList(string value, Type createListType, ParseStringDelegate parseFn) + { + return ParseGenericList(value.AsSpan(), createListType, v => parseFn(v.ToString())); + } + + public static ICollection ParseGenericList(ReadOnlySpan value, Type createListType, ParseStringSpanDelegate parseFn) + { + var isReadOnly = createListType != null && (createListType.IsGenericType && createListType.GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>)); + var to = (createListType == null || isReadOnly) + ? new List() + : (ICollection)createListType.CreateInstance(); + + var objSerializer = Json.JsonTypeSerializer.Instance.ObjectDeserializer; + if (to is List && objSerializer != null && typeof(TSerializer) == typeof(Json.JsonTypeSerializer)) + return (ICollection)objSerializer(value); + + if ((value = DeserializeListWithElements.StripList(value)).IsEmpty) + return null; + + if (value.IsNullOrEmpty()) + return isReadOnly ? (ICollection)Activator.CreateInstance(createListType, to) : to; + + var tryToParseItemsAsPrimitiveTypes = + typeof(T) == typeof(object) && JsConfig.TryToParsePrimitiveTypeValues; + + if (!value.IsNullOrEmpty()) + { + var valueLength = value.Length; + var i = 0; Serializer.EatWhitespace(value, ref i); - if (i < valueLength && value[i] == JsWriter.MapStartChar) - { - do - { - var itemValue = Serializer.EatTypeValue(value, ref i); - to.Add((T)parseFn(itemValue)); + if (i < valueLength && value[i] == JsWriter.MapStartChar) + { + do + { + var itemValue = Serializer.EatTypeValue(value, ref i); + if (!itemValue.IsEmpty) + { + to.Add((T)parseFn(itemValue)); + } + else + { + to.Add(default); + } Serializer.EatWhitespace(value, ref i); - } while (++i < value.Length); - } - else - { - - while (i < valueLength) - { + } while (++i < value.Length); + } + else + { + + while (i < valueLength) + { var startIndex = i; - var elementValue = Serializer.EatValue(value, ref i); - var listValue = elementValue; - if (listValue != null) { - if (tryToParseItemsAsPrimitiveTypes) { + var elementValue = Serializer.EatValue(value, ref i); + var listValue = elementValue; + var isEmpty = listValue.IsNullOrEmpty(); + if (!isEmpty) + { + if (tryToParseItemsAsPrimitiveTypes) + { Serializer.EatWhitespace(value, ref startIndex); - to.Add((T) DeserializeType.ParsePrimitive(elementValue, value[startIndex])); - } else { - to.Add((T) parseFn(elementValue)); + to.Add((T)DeserializeType.ParsePrimitive(elementValue.Value(), value[startIndex])); + } + else + { + to.Add((T)parseFn(elementValue)); } } - if (Serializer.EatItemSeperatorOrMapEndChar(value, ref i) - && i == valueLength) - { - // If we ate a separator and we are at the end of the value, - // it means the last element is empty => add default - to.Add(default(T)); - } - } - - } - } - - //TODO: 8-10-2011 -- this CreateInstance call should probably be moved over to ReflectionExtensions, - //but not sure how you'd like to go about caching constructors with parameters -- I would probably build a NewExpression, .Compile to a LambdaExpression and cache - return isReadOnly ? (ICollection)Activator.CreateInstance(createListType, to) : to; - } - } - - internal static class DeserializeList - where TSerializer : ITypeSerializer - { - private readonly static ParseStringDelegate CacheFn; - - static DeserializeList() - { - CacheFn = GetParseFn(); - } - - public static ParseStringDelegate Parse - { - get { return CacheFn; } - } - - public static ParseStringDelegate GetParseFn() - { - var listInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IList<>)); - if (listInterface == null) - throw new ArgumentException(string.Format("Type {0} is not of type IList<>", typeof(T).FullName)); - - //optimized access for regularly used types - if (typeof(T) == typeof(List)) - return DeserializeListWithElements.ParseStringList; - - if (typeof(T) == typeof(List)) - return DeserializeListWithElements.ParseIntList; - - var elementType = listInterface.GetGenericArguments()[0]; - - var supportedTypeParseMethod = DeserializeListWithElements.Serializer.GetParseFn(elementType); - if (supportedTypeParseMethod != null) - { - var createListType = typeof(T).HasAnyTypeDefinitionsOf(typeof(List<>), typeof(IList<>)) - ? null : typeof(T); - - var parseFn = DeserializeListWithElements.GetListTypeParseFn(createListType, elementType, supportedTypeParseMethod); - return value => parseFn(value, createListType, supportedTypeParseMethod); - } - - return null; - } - - } - - internal static class DeserializeEnumerable - where TSerializer : ITypeSerializer - { - private readonly static ParseStringDelegate CacheFn; - - static DeserializeEnumerable() - { - CacheFn = GetParseFn(); - } - - public static ParseStringDelegate Parse - { - get { return CacheFn; } - } - - public static ParseStringDelegate GetParseFn() - { - var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); - if (enumerableInterface == null) - throw new ArgumentException(string.Format("Type {0} is not of type IEnumerable<>", typeof(T).FullName)); - - //optimized access for regularly used types - if (typeof(T) == typeof(IEnumerable)) - return DeserializeListWithElements.ParseStringList; - - if (typeof(T) == typeof(IEnumerable)) - return DeserializeListWithElements.ParseIntList; - - var elementType = enumerableInterface.GetGenericArguments()[0]; - - var supportedTypeParseMethod = DeserializeListWithElements.Serializer.GetParseFn(elementType); - if (supportedTypeParseMethod != null) - { - const Type createListTypeWithNull = null; //Use conversions outside this class. see: Queue - - var parseFn = DeserializeListWithElements.GetListTypeParseFn( - createListTypeWithNull, elementType, supportedTypeParseMethod); - - return value => parseFn(value, createListTypeWithNull, supportedTypeParseMethod); - } - - return null; - } - - } + if (Serializer.EatItemSeperatorOrMapEndChar(value, ref i) && i == valueLength) + { + // If we ate a separator and we are at the end of the value, + // it means the last element is empty => add default + to.Add(default); + continue; + } + + if (isEmpty) + to.Add(default); + } + + } + } + + //TODO: 8-10-2011 -- this CreateInstance call should probably be moved over to ReflectionExtensions, + //but not sure how you'd like to go about caching constructors with parameters -- I would probably build a NewExpression, .Compile to a LambdaExpression and cache + return isReadOnly ? (ICollection)Activator.CreateInstance(createListType, to) : to; + } + } + + public static class DeserializeList + where TSerializer : ITypeSerializer + { + private static readonly ParseStringSpanDelegate CacheFn; + + static DeserializeList() + { + CacheFn = GetParseStringSpanFn(); + } + + public static ParseStringDelegate Parse => v => CacheFn(v.AsSpan()); + + public static ParseStringSpanDelegate ParseStringSpan => CacheFn; + + public static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); + + public static ParseStringSpanDelegate GetParseStringSpanFn() + { + var listInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IList<>)); + if (listInterface == null) + throw new ArgumentException($"Type {typeof(T).FullName} is not of type IList<>"); + + //optimized access for regularly used types + if (typeof(T) == typeof(List)) + return DeserializeListWithElements.ParseStringList; + + if (typeof(T) == typeof(List)) + return DeserializeListWithElements.ParseIntList; + + var elementType = listInterface.GetGenericArguments()[0]; + + var supportedTypeParseMethod = DeserializeListWithElements.Serializer.GetParseStringSpanFn(elementType); + if (supportedTypeParseMethod != null) + { + var createListType = typeof(T).HasAnyTypeDefinitionsOf(typeof(List<>), typeof(IList<>)) + ? null : typeof(T); + + var parseFn = DeserializeListWithElements.GetListTypeParseStringSpanFn(createListType, elementType, supportedTypeParseMethod); + return value => parseFn(value, createListType, supportedTypeParseMethod); + } + + return null; + } + + } + + internal static class DeserializeEnumerable + where TSerializer : ITypeSerializer + { + private static readonly ParseStringSpanDelegate CacheFn; + + static DeserializeEnumerable() + { + CacheFn = GetParseStringSpanFn(); + } + + public static ParseStringDelegate Parse => v => CacheFn(v.AsSpan()); + + public static ParseStringSpanDelegate ParseStringSpan => CacheFn; + + public static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); + + public static ParseStringSpanDelegate GetParseStringSpanFn() + { + var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); + if (enumerableInterface == null) + throw new ArgumentException($"Type {typeof(T).FullName} is not of type IEnumerable<>"); + + //optimized access for regularly used types + if (typeof(T) == typeof(IEnumerable)) + return DeserializeListWithElements.ParseStringList; + + if (typeof(T) == typeof(IEnumerable)) + return DeserializeListWithElements.ParseIntList; + + var elementType = enumerableInterface.GetGenericArguments()[0]; + + var supportedTypeParseMethod = DeserializeListWithElements.Serializer.GetParseStringSpanFn(elementType); + if (supportedTypeParseMethod != null) + { + const Type createListTypeWithNull = null; //Use conversions outside this class. see: Queue + + var parseFn = DeserializeListWithElements.GetListTypeParseStringSpanFn( + createListTypeWithNull, elementType, supportedTypeParseMethod); + + return value => parseFn(value, createListTypeWithNull, supportedTypeParseMethod); + } + + return null; + } + + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeSpecializedCollections.cs b/src/ServiceStack.Text/Common/DeserializeSpecializedCollections.cs index 9f9b03372..868c1c912 100644 --- a/src/ServiceStack.Text/Common/DeserializeSpecializedCollections.cs +++ b/src/ServiceStack.Text/Common/DeserializeSpecializedCollections.cs @@ -1,212 +1,203 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Reflection; namespace ServiceStack.Text.Common { - internal static class DeserializeSpecializedCollections - where TSerializer : ITypeSerializer - { - private readonly static ParseStringDelegate CacheFn; - - static DeserializeSpecializedCollections() - { - CacheFn = GetParseFn(); - } - - public static ParseStringDelegate Parse - { - get { return CacheFn; } - } - - public static ParseStringDelegate GetParseFn() - { - if (typeof(T).HasAnyTypeDefinitionsOf(typeof(Queue<>))) - { - if (typeof(T) == typeof(Queue)) - return ParseStringQueue; - - if (typeof(T) == typeof(Queue)) - return ParseIntQueue; - - return GetGenericQueueParseFn(); - } - - if (typeof(T).HasAnyTypeDefinitionsOf(typeof(Stack<>))) - { - if (typeof(T) == typeof(Stack)) - return ParseStringStack; - - if (typeof(T) == typeof(Stack)) - return ParseIntStack; - - return GetGenericStackParseFn(); - } + internal static class DeserializeSpecializedCollections + where TSerializer : ITypeSerializer + { + private static readonly ParseStringSpanDelegate CacheFn; -#if !SILVERLIGHT - if (typeof(T) == typeof(StringCollection)) - { - return ParseStringCollection; - } -#endif - - return GetGenericEnumerableParseFn(); - } - - public static Queue ParseStringQueue(string value) - { - var parse = (IEnumerable)DeserializeList, TSerializer>.Parse(value); - return new Queue(parse); - } - - public static Queue ParseIntQueue(string value) - { - var parse = (IEnumerable)DeserializeList, TSerializer>.Parse(value); - return new Queue(parse); - } - -#if !SILVERLIGHT - public static StringCollection ParseStringCollection(string value) where TS : ITypeSerializer - { - if ((value = DeserializeListWithElements.StripList(value)) == null) return null; - return value == String.Empty - ? new StringCollection() - : ToStringCollection(DeserializeListWithElements.ParseStringList(value)); - } - - public static StringCollection ToStringCollection(List items) - { - var to = new StringCollection(); - foreach (var item in items) - { - to.Add(item); - } - return to; - } -#endif - - internal static ParseStringDelegate GetGenericQueueParseFn() - { - var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); - var elementType = enumerableInterface.GetGenericArguments()[0]; - - var genericType = typeof(SpecializedQueueElements<>).MakeGenericType(elementType); - - var mi = genericType.GetMethod("ConvertToQueue", BindingFlags.Static | BindingFlags.Public); - - var convertToQueue = (ConvertObjectDelegate)Delegate.CreateDelegate(typeof(ConvertObjectDelegate), mi); - - var parseFn = DeserializeEnumerable.GetParseFn(); - - return x => convertToQueue(parseFn(x)); - } - - public static Stack ParseStringStack(string value) - { - var parse = (IEnumerable)DeserializeList, TSerializer>.Parse(value); - return new Stack(parse); - } - - public static Stack ParseIntStack(string value) - { - var parse = (IEnumerable)DeserializeList, TSerializer>.Parse(value); - return new Stack(parse); - } - - internal static ParseStringDelegate GetGenericStackParseFn() - { - var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); - var elementType = enumerableInterface.GetGenericArguments()[0]; - - var genericType = typeof(SpecializedQueueElements<>).MakeGenericType(elementType); - - var mi = genericType.GetMethod("ConvertToStack", BindingFlags.Static | BindingFlags.Public); - - var convertToQueue = (ConvertObjectDelegate)Delegate.CreateDelegate(typeof(ConvertObjectDelegate), mi); - - var parseFn = DeserializeEnumerable.GetParseFn(); - - return x => convertToQueue(parseFn(x)); - } - - public static ParseStringDelegate GetGenericEnumerableParseFn() - { - var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); - var elementType = enumerableInterface.GetGenericArguments()[0]; - - var genericType = typeof(SpecializedEnumerableElements<,>).MakeGenericType(typeof(T), elementType); - - var fi = genericType.GetField("ConvertFn", BindingFlags.Static | BindingFlags.Public); - - var convertFn = fi.GetValue(null) as ConvertObjectDelegate; - if (convertFn == null) return null; - - var parseFn = DeserializeEnumerable.GetParseFn(); - - return x => convertFn(parseFn(x)); - } - } - - internal class SpecializedQueueElements - { - public static Queue ConvertToQueue(object enumerable) - { - if (enumerable == null) return null; - return new Queue((IEnumerable)enumerable); - } - - public static Stack ConvertToStack(object enumerable) - { - if (enumerable == null) return null; - return new Stack((IEnumerable)enumerable); - } - } - - internal class SpecializedEnumerableElements - { - public static ConvertObjectDelegate ConvertFn; - - static SpecializedEnumerableElements() - { - foreach (var ctorInfo in typeof(TCollection).GetConstructors()) - { - var ctorParams = ctorInfo.GetParameters(); - if (ctorParams.Length != 1) continue; - var ctorParam = ctorParams[0]; - if (typeof(IEnumerable).IsAssignableFrom(ctorParam.ParameterType) - || ctorParam.ParameterType.IsOrHasGenericInterfaceTypeOf(typeof(IEnumerable<>))) - { - ConvertFn = fromObject => { - var to = Activator.CreateInstance(typeof(TCollection), fromObject); - return to; - }; - return; - } - } - - if (typeof(TCollection).IsOrHasGenericInterfaceTypeOf(typeof(ICollection<>))) - { - ConvertFn = ConvertFromCollection; - } - } - - public static object Convert(object enumerable) - { - return ConvertFn(enumerable); - } - - public static object ConvertFromCollection(object enumerable) - { - var to = (ICollection)typeof(TCollection).CreateInstance(); - var from = (IEnumerable)enumerable; - foreach (var item in from) - { - to.Add(item); - } - return to; - } - } -} \ No newline at end of file + static DeserializeSpecializedCollections() + { + CacheFn = GetParseStringSpanFn(); + } + + public static ParseStringDelegate Parse => v => CacheFn(v.AsSpan()); + + public static ParseStringSpanDelegate ParseStringSpan => CacheFn; + + public static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); + + public static ParseStringSpanDelegate GetParseStringSpanFn() + { + if (typeof(T).HasAnyTypeDefinitionsOf(typeof(Queue<>))) + { + if (typeof(T) == typeof(Queue)) + return ParseStringQueue; + + if (typeof(T) == typeof(Queue)) + return ParseIntQueue; + + return GetGenericQueueParseFn(); + } + + if (typeof(T).HasAnyTypeDefinitionsOf(typeof(Stack<>))) + { + if (typeof(T) == typeof(Stack)) + return ParseStringStack; + + if (typeof(T) == typeof(Stack)) + return ParseIntStack; + + return GetGenericStackParseFn(); + } + + var fn = PclExport.Instance.GetSpecializedCollectionParseStringSpanMethod(typeof(T)); + if (fn != null) + return fn; + + if (typeof(T) == typeof(IEnumerable) || typeof(T) == typeof(ICollection)) + { + return GetEnumerableParseStringSpanFn(); + } + + return GetGenericEnumerableParseStringSpanFn(); + } + + public static Queue ParseStringQueue(string value) => ParseStringQueue(value.AsSpan()); + + public static Queue ParseStringQueue(ReadOnlySpan value) + { + var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSpan(value); + return new Queue(parse); + } + + public static Queue ParseIntQueue(string value) => ParseIntQueue(value.AsSpan()); + + + public static Queue ParseIntQueue(ReadOnlySpan value) + { + var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSpan(value); + return new Queue(parse); + } + + internal static ParseStringSpanDelegate GetGenericQueueParseFn() + { + var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); + var elementType = enumerableInterface.GetGenericArguments()[0]; + var genericType = typeof(SpecializedQueueElements<>).MakeGenericType(elementType); + var mi = genericType.GetStaticMethod("ConvertToQueue"); + var convertToQueue = (ConvertObjectDelegate)mi.MakeDelegate(typeof(ConvertObjectDelegate)); + + var parseFn = DeserializeEnumerable.GetParseStringSpanFn(); + + return x => convertToQueue(parseFn(x)); + } + + public static Stack ParseStringStack(string value) => ParseStringStack(value.AsSpan()); + + public static Stack ParseStringStack(ReadOnlySpan value) + { + var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSpan(value); + return new Stack(parse); + } + + public static Stack ParseIntStack(string value) => ParseIntStack(value.AsSpan()); + + public static Stack ParseIntStack(ReadOnlySpan value) + { + var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSpan(value); + return new Stack(parse); + } + + internal static ParseStringSpanDelegate GetGenericStackParseFn() + { + var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); + + var elementType = enumerableInterface.GetGenericArguments()[0]; + var genericType = typeof(SpecializedQueueElements<>).MakeGenericType(elementType); + var mi = genericType.GetStaticMethod("ConvertToStack"); + var convertToQueue = (ConvertObjectDelegate)mi.MakeDelegate(typeof(ConvertObjectDelegate)); + + var parseFn = DeserializeEnumerable.GetParseStringSpanFn(); + + return x => convertToQueue(parseFn(x)); + } + + public static ParseStringDelegate GetEnumerableParseFn() => DeserializeListWithElements.ParseStringList; + + public static ParseStringSpanDelegate GetEnumerableParseStringSpanFn() => DeserializeListWithElements.ParseStringList; + + public static ParseStringDelegate GetGenericEnumerableParseFn() => v => GetGenericEnumerableParseStringSpanFn()(v.AsSpan()); + + public static ParseStringSpanDelegate GetGenericEnumerableParseStringSpanFn() + { + var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); + if (enumerableInterface == null) return null; + var elementType = enumerableInterface.GetGenericArguments()[0]; + var genericType = typeof(SpecializedEnumerableElements<,>).MakeGenericType(typeof(T), elementType); + var fi = genericType.GetPublicStaticField("ConvertFn"); + + var convertFn = fi.GetValue(null) as ConvertObjectDelegate; + if (convertFn == null) return null; + + var parseFn = DeserializeEnumerable.GetParseStringSpanFn(); + + return x => convertFn(parseFn(x)); + } + } + + internal class SpecializedQueueElements + { + public static Queue ConvertToQueue(object enumerable) + { + if (enumerable == null) return null; + return new Queue((IEnumerable)enumerable); + } + + public static Stack ConvertToStack(object enumerable) + { + if (enumerable == null) return null; + return new Stack((IEnumerable)enumerable); + } + } + + internal class SpecializedEnumerableElements + { + public static ConvertObjectDelegate ConvertFn; + + static SpecializedEnumerableElements() + { + foreach (var ctorInfo in typeof(TCollection).GetConstructors()) + { + var ctorParams = ctorInfo.GetParameters(); + if (ctorParams.Length != 1) continue; + var ctorParam = ctorParams[0]; + + if (typeof(IEnumerable).IsAssignableFrom(ctorParam.ParameterType) + || ctorParam.ParameterType.IsOrHasGenericInterfaceTypeOf(typeof(IEnumerable<>))) + { + ConvertFn = fromObject => + { + var to = Activator.CreateInstance(typeof(TCollection), fromObject); + return to; + }; + return; + } + } + + if (typeof(TCollection).IsOrHasGenericInterfaceTypeOf(typeof(ICollection<>))) + { + ConvertFn = ConvertFromCollection; + } + } + + public static object Convert(object enumerable) + { + return ConvertFn(enumerable); + } + + public static object ConvertFromCollection(object enumerable) + { + var to = (ICollection)typeof(TCollection).CreateInstance(); + var from = (IEnumerable)enumerable; + foreach (var item in from) + { + to.Add(item); + } + return to; + } + } +} diff --git a/src/ServiceStack.Text/Common/DeserializeType.cs b/src/ServiceStack.Text/Common/DeserializeType.cs index 6c74428bd..47571abe6 100644 --- a/src/ServiceStack.Text/Common/DeserializeType.cs +++ b/src/ServiceStack.Text/Common/DeserializeType.cs @@ -5,109 +5,155 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // -#if !XBOX && !MONOTOUCH && !SILVERLIGHT -using System.Reflection.Emit; -#endif - using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Reflection; -using ServiceStack.Text.Json; +using System.Runtime.CompilerServices; namespace ServiceStack.Text.Common { - internal static class DeserializeType + public static class DeserializeType where TSerializer : ITypeSerializer { private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - public static ParseStringDelegate GetParseMethod(TypeConfig typeConfig) + internal static ParseStringDelegate GetParseMethod(TypeConfig typeConfig) => v => GetParseStringSpanMethod(typeConfig)(v.AsSpan()); + + internal static ParseStringSpanDelegate GetParseStringSpanMethod(TypeConfig typeConfig) { var type = typeConfig.Type; - if (!type.IsClass || type.IsAbstract || type.IsInterface) return null; + if (!type.IsStandardClass()) return null; + var accessors = DeserializeTypeRef.GetTypeAccessors(typeConfig, Serializer); - var map = DeserializeTypeRef.GetTypeAccessorMap(typeConfig, Serializer); - - var ctorFn = JsConfig.ModelFactory(type); - if (map == null) + var ctorFn = JsConfig.ModelFactory(type); + if (accessors == null) return value => ctorFn(); - return typeof(TSerializer) == typeof(Json.JsonTypeSerializer) - ? (ParseStringDelegate)(value => DeserializeTypeRefJson.StringToType(type, value, ctorFn, map)) - : value => DeserializeTypeRefJsv.StringToType(type, value, ctorFn, map); + if (typeof(TSerializer) == typeof(Json.JsonTypeSerializer)) + return new StringToTypeContext(typeConfig, ctorFn, accessors).DeserializeJson; + + return new StringToTypeContext(typeConfig, ctorFn, accessors).DeserializeJsv; + } + + internal struct StringToTypeContext + { + private readonly TypeConfig typeConfig; + private readonly EmptyCtorDelegate ctorFn; + private readonly KeyValuePair[] accessors; + + public StringToTypeContext(TypeConfig typeConfig, EmptyCtorDelegate ctorFn, KeyValuePair[] accessors) + { + this.typeConfig = typeConfig; + this.ctorFn = ctorFn; + this.accessors = accessors; + } + + internal object DeserializeJson(ReadOnlySpan value) => DeserializeTypeRefJson.StringToType(value, typeConfig, ctorFn, accessors); + + internal object DeserializeJsv(ReadOnlySpan value) => DeserializeTypeRefJsv.StringToType(value, typeConfig, ctorFn, accessors); } - public static object ObjectStringToType(string strType) + public static object ObjectStringToType(ReadOnlySpan strType) { var type = ExtractType(strType); if (type != null) { - var parseFn = Serializer.GetParseFn(type); + var parseFn = Serializer.GetParseStringSpanFn(type); var propertyValue = parseFn(strType); return propertyValue; } - if (JsConfig.ConvertObjectTypesIntoStringDictionary && !string.IsNullOrEmpty(strType)) { - if (strType[0] == JsWriter.MapStartChar) { - var dynamicMatch = DeserializeDictionary.ParseDictionary(strType, null, Serializer.UnescapeString, Serializer.UnescapeString); - if (dynamicMatch != null && dynamicMatch.Count > 0) { + var config = JsConfig.GetConfig(); + + if (config.ConvertObjectTypesIntoStringDictionary && !strType.IsNullOrEmpty()) + { + var firstChar = strType[0]; + var endChar = strType[strType.Length - 1]; + if (firstChar == JsWriter.MapStartChar && endChar == JsWriter.MapEndChar) + { + var dynamicMatch = DeserializeDictionary.ParseDictionary(strType, null, v => Serializer.UnescapeString(v).ToString(), v => Serializer.UnescapeString(v).ToString()); + if (dynamicMatch != null && dynamicMatch.Count > 0) + { return dynamicMatch; } } - if (strType[0] == JsWriter.ListStartChar) { - return DeserializeList, TSerializer>.Parse(strType); + if (firstChar == JsWriter.ListStartChar && endChar == JsWriter.ListEndChar) + { + return DeserializeList, TSerializer>.ParseStringSpan(strType); } } - return Serializer.UnescapeString(strType); + var primitiveType = config.TryToParsePrimitiveTypeValues ? ParsePrimitive(strType) : null; + if (primitiveType != null) + return primitiveType; + + if (Serializer.ObjectDeserializer != null && typeof(TSerializer) == typeof(Json.JsonTypeSerializer)) + return !strType.IsNullOrEmpty() + ? Serializer.ObjectDeserializer(strType) + : strType.Value(); + + return Serializer.UnescapeString(strType).Value(); } - public static Type ExtractType(string strType) + public static Type ExtractType(string strType) => ExtractType(strType.AsSpan()); + + //TODO: optimize ExtractType + public static Type ExtractType(ReadOnlySpan strType) { + if (strType.IsEmpty || strType.Length <= 1) return null; + + var hasWhitespace = Json.JsonUtils.WhiteSpaceChars.Contains(strType[1]); + if (hasWhitespace) + { + var pos = strType.IndexOf('"'); + if (pos >= 0) + strType = ("{" + strType.Substring(pos, strType.Length - pos)).AsSpan(); + } + var typeAttrInObject = Serializer.TypeAttrInObject; - if (strType != null - && strType.Length > typeAttrInObject.Length - && strType.Substring(0, typeAttrInObject.Length) == typeAttrInObject) + if (strType.Length > typeAttrInObject.Length + && strType.Slice(0, typeAttrInObject.Length).EqualsOrdinal(typeAttrInObject)) { var propIndex = typeAttrInObject.Length; - var typeName = Serializer.UnescapeSafeString(Serializer.EatValue(strType, ref propIndex)); + var typeName = Serializer.UnescapeSafeString(Serializer.EatValue(strType, ref propIndex)).ToString(); - var type = JsConfig.TypeFinder.Invoke(typeName); + var type = JsConfig.TypeFinder(typeName); - if (type == null) { - Tracer.Instance.WriteWarning("Could not find type: " + typeName); - return null; - } + JsWriter.AssertAllowedRuntimeType(type); -#if !SILVERLIGHT && !MONOTOUCH - if (type.IsInterface || type.IsAbstract) { - return DynamicProxy.GetInstanceFor(type).GetType(); - } -#endif + if (type == null) + { + Tracer.Instance.WriteWarning("Could not find type: " + typeName); + return null; + } - return type; + return ReflectionOptimizer.Instance.UseType(type); } return null; } - public static object ParseAbstractType(string value) + public static object ParseAbstractType(ReadOnlySpan value) { if (typeof(T).IsAbstract) { - if (string.IsNullOrEmpty(value)) return null; + if (value.IsNullOrEmpty()) return null; var concreteType = ExtractType(value); if (concreteType != null) { - return Serializer.GetParseFn(concreteType)(value); + var fn = Serializer.GetParseStringSpanFn(concreteType); + if (fn == ParseAbstractType) + return null; + + var ret = fn(value); + return ret; } Tracer.Instance.WriteWarning( "Could not deserialize Abstract Type with unknown concrete type: " + typeof(T).FullName); @@ -117,98 +163,146 @@ public static object ParseAbstractType(string value) public static object ParseQuotedPrimitive(string value) { - if (string.IsNullOrEmpty(value)) return null; + var config = JsConfig.GetConfig(); + var fn = config.ParsePrimitiveFn; + var result = fn?.Invoke(value); + if (result != null) + return result; + + if (string.IsNullOrEmpty(value)) + return null; + + if (Guid.TryParse(value, out Guid guidValue)) return guidValue; -#if NET40 - Guid guidValue; - if (Guid.TryParse(value, out guidValue)) return guidValue; -#endif - if (value.StartsWith(DateTimeSerializer.EscapedWcfJsonPrefix) || value.StartsWith(DateTimeSerializer.WcfJsonPrefix)) { + if (value.StartsWith(DateTimeSerializer.EscapedWcfJsonPrefix, StringComparison.Ordinal) || value.StartsWith(DateTimeSerializer.WcfJsonPrefix, StringComparison.Ordinal)) return DateTimeSerializer.ParseWcfJsonDate(value); - } - - if (JsConfig.DateHandler == JsonDateHandler.ISO8601) + + if (JsConfig.DateHandler == DateHandler.ISO8601) { // check that we have UTC ISO8601 date: // YYYY-MM-DDThh:mm:ssZ // YYYY-MM-DDThh:mm:ss+02:00 // YYYY-MM-DDThh:mm:ss-02:00 - if (value.Length > 14 && value[10] == 'T' && - (value.EndsWith("Z", StringComparison.InvariantCulture) - || value[value.Length - 6] == '+' - || value[value.Length - 6] == '-')) + if (value.Length > 14 && value[10] == 'T' && + (value.EndsWithInvariant("Z") + || value[value.Length - 6] == '+' + || value[value.Length - 6] == '-')) { return DateTimeSerializer.ParseShortestXsdDateTime(value); } } - return Serializer.UnescapeString(value); - } - - public static object ParsePrimitive(string value) - { - if (string.IsNullOrEmpty(value)) return null; - - bool boolValue; - if (bool.TryParse(value, out boolValue)) return boolValue; - - decimal decimalValue; - if (decimal.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out decimalValue)) + if (config.DateHandler == DateHandler.RFC1123) { - if (decimalValue == decimal.Truncate(decimalValue)) + // check that we have RFC1123 date: + // ddd, dd MMM yyyy HH:mm:ss GMT + if (value.Length == 29 && (value.EndsWithInvariant("GMT"))) { - if (decimalValue <= ulong.MaxValue && decimalValue >= 0) return (ulong)decimalValue; - if (decimalValue <= long.MaxValue && decimalValue >= long.MinValue) - { - var longValue = (long)decimalValue; - if (longValue <= sbyte.MaxValue && longValue >= sbyte.MinValue) return (sbyte)longValue; - if (longValue <= byte.MaxValue && longValue >= byte.MinValue) return (byte)longValue; - if (longValue <= short.MaxValue && longValue >= short.MinValue) return (short)longValue; - if (longValue <= ushort.MaxValue && longValue >= ushort.MinValue) return (ushort)longValue; - if (longValue <= int.MaxValue && longValue >= int.MinValue) return (int)longValue; - if (longValue <= uint.MaxValue && longValue >= uint.MinValue) return (uint)longValue; - } + return DateTimeSerializer.ParseRFC1123DateTime(value); } - return decimalValue; } - float floatValue; - if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out floatValue)) return floatValue; + return Serializer.UnescapeString(value); + } - double doubleValue; - if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue)) return doubleValue; + public static object ParsePrimitive(string value) => ParsePrimitive(value.AsSpan()); - return null; + public static object ParsePrimitive(ReadOnlySpan value) + { + var fn = JsConfig.ParsePrimitiveFn; + var result = fn?.Invoke(value.ToString()); + if (result != null) + return result; + + if (value.IsNullOrEmpty()) + return null; + + if (value.TryParseBoolean(out bool boolValue)) + return boolValue; + + return value.ParseNumber(); } internal static object ParsePrimitive(string value, char firstChar) { - if (typeof(TSerializer) == typeof(JsonTypeSerializer)) { + if (typeof(TSerializer) == typeof(Json.JsonTypeSerializer)) + { return firstChar == JsWriter.QuoteChar - ? ParseQuotedPrimitive(value) - : ParsePrimitive(value); + ? ParseQuotedPrimitive(value) + : ParsePrimitive(value); } return (ParsePrimitive(value) ?? ParseQuotedPrimitive(value)); } } + + internal static class TypeAccessorUtils + { + internal static TypeAccessor Get(this KeyValuePair[] accessors, ReadOnlySpan propertyName, bool lenient) + { + var testValue = FindPropertyAccessor(accessors, propertyName); + if (testValue != null) + return testValue; + if (lenient) + return FindPropertyAccessor(accessors, + propertyName.ToString().Replace("-", string.Empty).Replace("_", string.Empty).AsSpan()); + + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] //Binary Search + private static TypeAccessor FindPropertyAccessor(KeyValuePair[] accessors, ReadOnlySpan propertyName) + { + var lo = 0; + var hi = accessors.Length - 1; + var mid = (lo + hi + 1) / 2; + + while (lo <= hi) + { + var test = accessors[mid]; + var cmp = propertyName.CompareTo(test.Key.AsSpan(), StringComparison.OrdinalIgnoreCase); + if (cmp == 0) + return test.Value; + + if (cmp < 0) + hi = mid - 1; + else + lo = mid + 1; + + mid = (lo + hi + 1) / 2; + } + return null; + } + } + internal class TypeAccessor { - internal ParseStringDelegate GetProperty; - internal SetPropertyDelegate SetProperty; + internal ParseStringSpanDelegate GetProperty; + internal SetMemberDelegate SetProperty; internal Type PropertyType; public static Type ExtractType(ITypeSerializer Serializer, string strType) + => ExtractType(Serializer, strType.AsSpan()); + + public static Type ExtractType(ITypeSerializer Serializer, ReadOnlySpan strType) { - var typeAttrInObject = Serializer.TypeAttrInObject; + if (strType.IsEmpty || strType.Length <= 1) return null; - if (strType != null - && strType.Length > typeAttrInObject.Length - && strType.Substring(0, typeAttrInObject.Length) == typeAttrInObject) + var hasWhitespace = Json.JsonUtils.WhiteSpaceChars.Contains(strType[1]); + if (hasWhitespace) + { + var pos = strType.IndexOf('"'); + if (pos >= 0) + strType = ("{" + strType.Substring(pos)).AsSpan(); + } + + var typeAttrInObject = Serializer.TypeAttrInObject; + if (strType.Length > typeAttrInObject.Length + && strType.Slice(0, typeAttrInObject.Length).EqualsOrdinal(typeAttrInObject)) { var propIndex = typeAttrInObject.Length; - var typeName = Serializer.EatValue(strType, ref propIndex); - var type = JsConfig.TypeFinder.Invoke(typeName); + var typeName = Serializer.EatValue(strType, ref propIndex).ToString(); + var type = JsConfig.TypeFinder(typeName); if (type == null) Tracer.Instance.WriteWarning("Could not find type: " + typeName); @@ -223,28 +317,56 @@ public static TypeAccessor Create(ITypeSerializer serializer, TypeConfig typeCon return new TypeAccessor { PropertyType = propertyInfo.PropertyType, - GetProperty = serializer.GetParseFn(propertyInfo.PropertyType), + GetProperty = GetPropertyMethod(serializer, propertyInfo), SetProperty = GetSetPropertyMethod(typeConfig, propertyInfo), }; } - private static SetPropertyDelegate GetSetPropertyMethod(TypeConfig typeConfig, PropertyInfo propertyInfo) + internal static ParseStringSpanDelegate GetPropertyMethod(ITypeSerializer serializer, PropertyInfo propertyInfo) { - if (propertyInfo.ReflectedType != propertyInfo.DeclaringType) - propertyInfo = propertyInfo.DeclaringType.GetProperty(propertyInfo.Name); + var getPropertyFn = serializer.GetParseStringSpanFn(propertyInfo.PropertyType); + if (propertyInfo.PropertyType == typeof(object) || + propertyInfo.PropertyType.HasInterface(typeof(IEnumerable))) + { + var declaringTypeNamespace = propertyInfo.DeclaringType?.Namespace; + if (declaringTypeNamespace == null || (!JsConfig.AllowRuntimeTypeInTypesWithNamespaces.Contains(declaringTypeNamespace) + && !JsConfig.AllowRuntimeTypeInTypes.Contains(propertyInfo.DeclaringType.FullName))) + { + return value => + { + var hold = JsState.IsRuntimeType; + try + { + JsState.IsRuntimeType = true; + return getPropertyFn(value); + } + finally + { + JsState.IsRuntimeType = hold; + } + }; + } + } + return getPropertyFn; + } - if (!propertyInfo.CanWrite && !typeConfig.EnableAnonymousFieldSetterses) return null; + private static SetMemberDelegate GetSetPropertyMethod(TypeConfig typeConfig, PropertyInfo propertyInfo) + { + if (typeConfig.Type != propertyInfo.DeclaringType) + propertyInfo = propertyInfo.DeclaringType.GetProperty(propertyInfo.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + if (!propertyInfo.CanWrite && !typeConfig.EnableAnonymousFieldSetters) return null; FieldInfo fieldInfo = null; if (!propertyInfo.CanWrite) { - //TODO: What string comparison is used in SST? - string fieldNameFormat = Env.IsMono ? "<{0}>" : "<{0}>i__Field"; + var fieldNameFormat = Env.IsMono ? "<{0}>" : "<{0}>i__Field"; var fieldName = string.Format(fieldNameFormat, propertyInfo.Name); - var fieldInfos = typeConfig.Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetField); + + var fieldInfos = typeConfig.Type.GetWritableFields(); foreach (var f in fieldInfos) { - if (f.IsInitOnly && f.FieldType == propertyInfo.PropertyType && f.Name == fieldName) + if (f.IsInitOnly && f.FieldType == propertyInfo.PropertyType && f.Name.EqualsIgnoreCase(fieldName)) { fieldInfo = f; break; @@ -254,89 +376,106 @@ private static SetPropertyDelegate GetSetPropertyMethod(TypeConfig typeConfig, P if (fieldInfo == null) return null; } -#if SILVERLIGHT || MONOTOUCH || XBOX - if (propertyInfo.CanWrite) - { - var setMethodInfo = propertyInfo.GetSetMethod(true); - return (instance, value) => setMethodInfo.Invoke(instance, new[] { value }); - } - if (fieldInfo == null) return null; - return (instance, value) => fieldInfo.SetValue(instance, value); -#else - return propertyInfo.CanWrite - ? CreateIlPropertySetter(propertyInfo) - : CreateIlFieldSetter(fieldInfo); -#endif + return propertyInfo.CanWrite + ? ReflectionOptimizer.Instance.CreateSetter(propertyInfo) + : ReflectionOptimizer.Instance.CreateSetter(fieldInfo); } -#if !SILVERLIGHT && !MONOTOUCH && !XBOX - - private static SetPropertyDelegate CreateIlPropertySetter(PropertyInfo propertyInfo) - { - var propSetMethod = propertyInfo.GetSetMethod(true); - if (propSetMethod == null) - return null; + public static TypeAccessor Create(ITypeSerializer serializer, TypeConfig typeConfig, FieldInfo fieldInfo) + { + return new TypeAccessor + { + PropertyType = fieldInfo.FieldType, + GetProperty = serializer.GetParseStringSpanFn(fieldInfo.FieldType), + SetProperty = GetSetFieldMethod(typeConfig, fieldInfo), + }; + } - var setter = CreateDynamicSetMethod(propertyInfo); + private static SetMemberDelegate GetSetFieldMethod(TypeConfig typeConfig, FieldInfo fieldInfo) + { + if (typeConfig.Type != fieldInfo.DeclaringType) + fieldInfo = fieldInfo.DeclaringType.GetField(fieldInfo.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - var generator = setter.GetILGenerator(); - generator.Emit(OpCodes.Ldarg_0); - generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); - generator.Emit(OpCodes.Ldarg_1); + return ReflectionOptimizer.Instance.CreateSetter(fieldInfo); + } + } - generator.Emit(propertyInfo.PropertyType.IsClass - ? OpCodes.Castclass - : OpCodes.Unbox_Any, - propertyInfo.PropertyType); + public static class DeserializeTypeExensions + { + public static bool Has(this ParseAsType flags, ParseAsType flag) + { + return (flag & flags) != 0; + } - generator.EmitCall(OpCodes.Callvirt, propSetMethod, (Type[])null); - generator.Emit(OpCodes.Ret); + public static object ParseNumber(this ReadOnlySpan value) => ParseNumber(value, JsConfig.TryParseIntoBestFit); + public static object ParseNumber(this ReadOnlySpan value, bool bestFit) + { + if (value.Length == 1) + { + int singleDigit = value[0]; + if (singleDigit >= 48 || singleDigit <= 57) // 0 - 9 + { + var result = singleDigit - 48; + if (bestFit) + return (byte) result; + return result; + } + } - return (SetPropertyDelegate)setter.CreateDelegate(typeof(SetPropertyDelegate)); - } + var config = JsConfig.GetConfig(); - private static SetPropertyDelegate CreateIlFieldSetter(FieldInfo fieldInfo) - { - var setter = CreateDynamicSetMethod(fieldInfo); + // Parse as decimal + var acceptDecimal = config.ParsePrimitiveFloatingPointTypes.Has(ParseAsType.Decimal); + var isDecimal = value.TryParseDecimal(out decimal decimalValue); - var generator = setter.GetILGenerator(); - generator.Emit(OpCodes.Ldarg_0); - generator.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); - generator.Emit(OpCodes.Ldarg_1); + // Check if the number is an Primitive Integer type given that we have a decimal + if (isDecimal && decimalValue == decimal.Truncate(decimalValue)) + { + // Value is a whole number + var parseAs = config.ParsePrimitiveIntegerTypes; + if (parseAs.Has(ParseAsType.Byte) && decimalValue <= byte.MaxValue && decimalValue >= byte.MinValue) + return (byte)decimalValue; + if (parseAs.Has(ParseAsType.SByte) && decimalValue <= sbyte.MaxValue && decimalValue >= sbyte.MinValue) + return (sbyte)decimalValue; + if (parseAs.Has(ParseAsType.Int16) && decimalValue <= Int16.MaxValue && decimalValue >= Int16.MinValue) + return (Int16)decimalValue; + if (parseAs.Has(ParseAsType.UInt16) && decimalValue <= UInt16.MaxValue && decimalValue >= UInt16.MinValue) + return (UInt16)decimalValue; + if (parseAs.Has(ParseAsType.Int32) && decimalValue <= Int32.MaxValue && decimalValue >= Int32.MinValue) + return (Int32)decimalValue; + if (parseAs.Has(ParseAsType.UInt32) && decimalValue <= UInt32.MaxValue && decimalValue >= UInt32.MinValue) + return (UInt32)decimalValue; + if (parseAs.Has(ParseAsType.Int64) && decimalValue <= Int64.MaxValue && decimalValue >= Int64.MinValue) + return (Int64)decimalValue; + if (parseAs.Has(ParseAsType.UInt64) && decimalValue <= UInt64.MaxValue && decimalValue >= UInt64.MinValue) + return (UInt64)decimalValue; + return decimalValue; + } - generator.Emit(fieldInfo.FieldType.IsClass - ? OpCodes.Castclass - : OpCodes.Unbox_Any, - fieldInfo.FieldType); + // Value is a floating point number - generator.Emit(OpCodes.Stfld, fieldInfo); - generator.Emit(OpCodes.Ret); + // Return a decimal if the user accepts a decimal + if (isDecimal && acceptDecimal) + return decimalValue; - return (SetPropertyDelegate)setter.CreateDelegate(typeof(SetPropertyDelegate)); - } + var acceptFloat = config.ParsePrimitiveFloatingPointTypes.HasFlag(ParseAsType.Single); + var isFloat = value.TryParseFloat(out float floatValue); + if (acceptFloat && isFloat) + return floatValue; - private static DynamicMethod CreateDynamicSetMethod(MemberInfo memberInfo) - { - var args = new[] { typeof(object), typeof(object) }; - var name = string.Format("_{0}{1}_", "Set", memberInfo.Name); - var returnType = typeof(void); + var acceptDouble = config.ParsePrimitiveFloatingPointTypes.HasFlag(ParseAsType.Double); + var isDouble = value.TryParseDouble(out double doubleValue); + if (acceptDouble && isDouble) + return doubleValue; - return !memberInfo.DeclaringType.IsInterface - ? new DynamicMethod(name, returnType, args, memberInfo.DeclaringType, true) - : new DynamicMethod(name, returnType, args, memberInfo.Module, true); - } -#endif + if (isDecimal) + return decimalValue; + if (isFloat) + return floatValue; + if (isDouble) + return doubleValue; - internal static SetPropertyDelegate GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) - { - if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Any()) return null; - -#if SILVERLIGHT || MONOTOUCH || XBOX - var setMethodInfo = propertyInfo.GetSetMethod(true); - return (instance, value) => setMethodInfo.Invoke(instance, new[] { value }); -#else - return CreateIlPropertySetter(propertyInfo); -#endif + return null; } } } diff --git a/src/ServiceStack.Text/Common/DeserializeTypeRef.cs b/src/ServiceStack.Text/Common/DeserializeTypeRef.cs index ea58a4531..ea59a9c7a 100644 --- a/src/ServiceStack.Text/Common/DeserializeTypeRef.cs +++ b/src/ServiceStack.Text/Common/DeserializeTypeRef.cs @@ -1,156 +1,111 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Runtime.Serialization; +using System.Threading; +using ServiceStack.Text.Support; namespace ServiceStack.Text.Common { - internal static class DeserializeTypeRef - { - internal static SerializationException CreateSerializationError(Type type, string strType) - { - return new SerializationException(String.Format( - "Type definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", - JsWriter.MapStartChar, type.Name, strType.Substring(0, strType.Length < 50 ? strType.Length : 50))); - } + internal static class DeserializeTypeRef + { + internal static SerializationException CreateSerializationError(Type type, string strType) + { + return new SerializationException(String.Format( + "Type definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", + JsWriter.MapStartChar, type.Name, strType.Substring(0, strType.Length < 50 ? strType.Length : 50))); + } + + internal static SerializationException GetSerializationException(string propertyName, string propertyValueString, Type propertyType, Exception e) + { + var serializationException = new SerializationException($"Failed to set property '{propertyName}' with '{propertyValueString}'", e); + if (propertyName != null) + { + serializationException.Data.Add("propertyName", propertyName); + } + if (propertyValueString != null) + { + serializationException.Data.Add("propertyValueString", propertyValueString); + } + if (propertyType != null) + { + serializationException.Data.Add("propertyType", propertyType); + } + return serializationException; + } - internal static SerializationException GetSerializationException(string propertyName, string propertyValueString, Type propertyType, Exception e) - { - var serializationException = new SerializationException(String.Format("Failed to set property '{0}' with '{1}'", propertyName, propertyValueString), e); - if (propertyName != null) { - serializationException.Data.Add("propertyName", propertyName); - } - if (propertyValueString != null) { - serializationException.Data.Add("propertyValueString", propertyValueString); - } - if (propertyType != null) { - serializationException.Data.Add("propertyType", propertyType); - } - return serializationException; - } + private static Dictionary[]> TypeAccessorsCache = new Dictionary[]>(); - internal static Dictionary GetTypeAccessorMap(TypeConfig typeConfig, ITypeSerializer serializer) + internal static KeyValuePair[] GetCachedTypeAccessors(Type type, ITypeSerializer serializer) + { + if (TypeAccessorsCache.TryGetValue(type, out var typeAccessors)) + return typeAccessors; + + var typeConfig = new TypeConfig(type); + typeAccessors = GetTypeAccessors(typeConfig, serializer); + + Dictionary[]> snapshot, newCache; + do + { + snapshot = TypeAccessorsCache; + newCache = new Dictionary[]>(TypeAccessorsCache) { + [type] = typeAccessors + }; + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref TypeAccessorsCache, newCache, snapshot), snapshot)); + + return typeAccessors; + } + + internal static KeyValuePair[] GetTypeAccessors(TypeConfig typeConfig, ITypeSerializer serializer) { var type = typeConfig.Type; var propertyInfos = type.GetSerializableProperties(); - if (propertyInfos.Length == 0) return null; + var fieldInfos = type.GetSerializableFields(); + if (propertyInfos.Length == 0 && fieldInfos.Length == 0) + return default; - var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + var accessors = new KeyValuePair[propertyInfos.Length + fieldInfos.Length]; + var i = 0; - var isDataContract = type.IsDto(); - foreach (var propertyInfo in propertyInfos) + if (propertyInfos.Length != 0) { - var propertyName = propertyInfo.Name; - if (isDataContract) + for (; i < propertyInfos.Length; i++) { + var propertyInfo = propertyInfos[i]; + var propertyName = propertyInfo.Name; var dcsDataMember = propertyInfo.GetDataMember(); - if (dcsDataMember != null && dcsDataMember.Name != null) + if (dcsDataMember?.Name != null) { propertyName = dcsDataMember.Name; } + + accessors[i] = new KeyValuePair(propertyName, TypeAccessor.Create(serializer, typeConfig, propertyInfo)); } - map[propertyName] = TypeAccessor.Create(serializer, typeConfig, propertyInfo); } - return map; - } - - /* The old Reference generic implementation - internal static object StringToType( - ITypeSerializer Serializer, - Type type, - string strType, - EmptyCtorDelegate ctorFn, - Dictionary typeAccessorMap) - { - var index = 0; - - if (strType == null) - return null; - if (!Serializer.EatMapStartChar(strType, ref index)) - throw DeserializeTypeRef.CreateSerializationError(type, strType); - - if (strType == JsWriter.EmptyMap) return ctorFn(); - - object instance = null; - - var strTypeLength = strType.Length; - while (index < strTypeLength) - { - var propertyName = Serializer.EatMapKey(strType, ref index); - - Serializer.EatMapKeySeperator(strType, ref index); - - var propertyValueStr = Serializer.EatValue(strType, ref index); - var possibleTypeInfo = propertyValueStr != null && propertyValueStr.Length > 1 && propertyValueStr[0] == '_'; - - if (possibleTypeInfo && propertyName == JsWriter.TypeAttr) - { - var typeName = Serializer.ParseString(propertyValueStr); - instance = ReflectionExtensions.CreateInstance(typeName); - if (instance == null) - { - Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr); - } - else - { - //If __type info doesn't match, ignore it. - if (!type.IsInstanceOfType(instance)) - instance = null; - } - - Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - continue; - } - - if (instance == null) instance = ctorFn(); - - TypeAccessor typeAccessor; - typeAccessorMap.TryGetValue(propertyName, out typeAccessor); - - var propType = possibleTypeInfo ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; - if (propType != null) - { - try - { - if (typeAccessor != null) - { - var parseFn = Serializer.GetParseFn(propType); - var propertyValue = parseFn(propertyValueStr); - typeAccessor.SetProperty(instance, propertyValue); - } - - Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - - continue; - } - catch - { - Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName, propertyValueStr); - } - } - - if (typeAccessor != null && typeAccessor.GetProperty != null && typeAccessor.SetProperty != null) - { - try - { - var propertyValue = typeAccessor.GetProperty(propertyValueStr); - typeAccessor.SetProperty(instance, propertyValue); - } - catch - { - Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName, propertyValueStr); - } - } - - Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - } + if (fieldInfos.Length != 0) + { + for (var j=0; j < fieldInfos.Length; j++) + { + var fieldInfo = fieldInfos[j]; + var fieldName = fieldInfo.Name; + var dcsDataMember = fieldInfo.GetDataMember(); + if (dcsDataMember?.Name != null) + { + fieldName = dcsDataMember.Name; + } - return instance; - } - */ - } + accessors[i + j] = new KeyValuePair(fieldName, TypeAccessor.Create(serializer, typeConfig, fieldInfo)); + } + } + + Array.Sort(accessors, (x,y) => string.Compare(x.Key, y.Key, StringComparison.OrdinalIgnoreCase)); + return accessors; + } + } - //The same class above but JSON-specific to enable inlining in this hot class. + //The same class above but JSON-specific to enable inlining in this hot class. } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs b/src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs index 8a7d082dd..6960ddc2f 100644 --- a/src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs +++ b/src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs @@ -1,186 +1,176 @@ using System; using System.Collections.Generic; -using System.Globalization; using ServiceStack.Text.Json; namespace ServiceStack.Text.Common { - // Provides a contract for mapping properties to their type accessors - internal interface IPropertyNameResolver + internal static class DeserializeTypeRefJson { - TypeAccessor GetTypeAccessorForProperty(string propertyName, Dictionary typeAccessorMap); - } - // The default behavior is that the target model must match property names exactly - internal class DefaultPropertyNameResolver : IPropertyNameResolver - { - public virtual TypeAccessor GetTypeAccessorForProperty(string propertyName, Dictionary typeAccessorMap) - { - TypeAccessor typeAccessor; - typeAccessorMap.TryGetValue(propertyName, out typeAccessor); - return typeAccessor; - } - } - // The lenient behavior is that properties on the target model can be .NET-cased, while the source JSON can differ - internal class LenientPropertyNameResolver : DefaultPropertyNameResolver - { - - public override TypeAccessor GetTypeAccessorForProperty(string propertyName, Dictionary typeAccessorMap) - { - TypeAccessor typeAccessor; - - // camelCase is already supported by default, so no need to add another transform in the tree - return typeAccessorMap.TryGetValue(TransformFromLowercaseUnderscore(propertyName), out typeAccessor) - ? typeAccessor - : base.GetTypeAccessorForProperty(propertyName, typeAccessorMap); - } + private static readonly JsonTypeSerializer Serializer = (JsonTypeSerializer)JsonTypeSerializer.Instance; - private static string TransformFromLowercaseUnderscore(string propertyName) + internal static object StringToType(ReadOnlySpan strType, + TypeConfig typeConfig, + EmptyCtorDelegate ctorFn, + KeyValuePair[] typeAccessors) { - // "lowercase_underscore" -> LowercaseUnderscore - return propertyName.ToTitleCase(); - } + var index = 0; + var type = typeConfig.Type; - } + if (strType.IsEmpty) + return null; - internal static class DeserializeTypeRefJson - { - public static readonly IPropertyNameResolver DefaultPropertyNameResolver = new DefaultPropertyNameResolver(); - public static readonly IPropertyNameResolver LenientPropertyNameResolver = new LenientPropertyNameResolver(); - public static IPropertyNameResolver PropertyNameResolver = DefaultPropertyNameResolver; + var buffer = strType; + var strTypeLength = strType.Length; - private static readonly JsonTypeSerializer Serializer = (JsonTypeSerializer)JsonTypeSerializer.Instance; + //if (!Serializer.EatMapStartChar(strType, ref index)) + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline + if (buffer[index] != JsWriter.MapStartChar) + throw DeserializeTypeRef.CreateSerializationError(type, strType.ToString()); + + index++; + if (JsonTypeSerializer.IsEmptyMap(strType, index)) + return ctorFn(); + + var config = JsConfig.GetConfig(); + var typeAttr = config.TypeAttrMemory; + + object instance = null; + var textCase = typeConfig.TextCase.GetValueOrDefault(config.TextCase); + var lenient = config.PropertyConvention == PropertyConvention.Lenient || textCase == TextCase.SnakeCase; - internal static object StringToType( - Type type, - string strType, - EmptyCtorDelegate ctorFn, - Dictionary typeAccessorMap) - { - var index = 0; - - if (strType == null) - return null; - - //if (!Serializer.EatMapStartChar(strType, ref index)) - for (; index < strType.Length; index++) { var c = strType[index]; if (c >= JsonTypeSerializer.WhiteSpaceFlags.Length || !JsonTypeSerializer.WhiteSpaceFlags[c]) break; } //Whitespace inline - if (strType[index++] != JsWriter.MapStartChar) - throw DeserializeTypeRef.CreateSerializationError(type, strType); - - if (JsonTypeSerializer.IsEmptyMap(strType)) return ctorFn(); - - object instance = null; - - var strTypeLength = strType.Length; - while (index < strTypeLength) - { - var propertyName = JsonTypeSerializer.ParseJsonString(strType, ref index); - - //Serializer.EatMapKeySeperator(strType, ref index); - for (; index < strType.Length; index++) { var c = strType[index]; if (c >= JsonTypeSerializer.WhiteSpaceFlags.Length || !JsonTypeSerializer.WhiteSpaceFlags[c]) break; } //Whitespace inline - if (strType.Length != index) index++; - - var propertyValueStr = Serializer.EatValue(strType, ref index); - var possibleTypeInfo = propertyValueStr != null && propertyValueStr.Length > 1; - - //if we already have an instance don't check type info, because then we will have a half deserialized object - //we could throw here or just use the existing instance. - if (instance == null && possibleTypeInfo && propertyName == JsWriter.TypeAttr) - { - var explicitTypeName = Serializer.ParseString(propertyValueStr); - var explicitType = AssemblyUtils.FindType(explicitTypeName); - if (explicitType != null && !explicitType.IsInterface && !explicitType.IsAbstract) { + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline + + while (index < strTypeLength) + { + var propertyName = JsonTypeSerializer.UnescapeJsString(strType, JsonUtils.QuoteChar, removeQuotes:true, ref index); + + //Serializer.EatMapKeySeperator(strType, ref index); + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline + if (strTypeLength != index) index++; + + var propertyValueStr = Serializer.EatValue(strType, ref index); + var possibleTypeInfo = propertyValueStr != null && propertyValueStr.Length > 1; + + //if we already have an instance don't check type info, because then we will have a half deserialized object + //we could throw here or just use the existing instance. + if (instance == null && possibleTypeInfo && propertyName.Equals(typeAttr.Span, StringComparison.OrdinalIgnoreCase)) + { + var explicitTypeName = Serializer.ParseString(propertyValueStr); + var explicitType = config.TypeFinder(explicitTypeName); + + if (explicitType == null || explicitType.IsInterface || explicitType.IsAbstract) + { + Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr.ToString()); + } + else if (!type.IsAssignableFrom(explicitType)) + { + Tracer.Instance.WriteWarning("Could not assign type: " + propertyValueStr.ToString()); + } + else + { + JsWriter.AssertAllowedRuntimeType(explicitType); instance = explicitType.CreateInstance(); } - if (instance == null) - { - Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr); - } - else - { - //If __type info doesn't match, ignore it. - if (!type.IsInstanceOfType(instance)) { - instance = null; - } else { - var derivedType = instance.GetType(); - if (derivedType != type) { - var derivedTypeConfig = new TypeConfig(derivedType); - var map = DeserializeTypeRef.GetTypeAccessorMap(derivedTypeConfig, Serializer); - if (map != null) { - typeAccessorMap = map; - } + if (instance != null) + { + //If __type info doesn't match, ignore it. + if (!type.IsInstanceOfType(instance)) + { + instance = null; + } + else + { + var derivedType = instance.GetType(); + if (derivedType != type) + { + var map = DeserializeTypeRef.GetCachedTypeAccessors(derivedType, Serializer); + if (map != null) + typeAccessors = map; } - } - } - - Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - continue; - } - - if (instance == null) instance = ctorFn(); - - var typeAccessor = PropertyNameResolver.GetTypeAccessorForProperty(propertyName, typeAccessorMap); - - var propType = possibleTypeInfo && propertyValueStr[0] == '_' ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; - if (propType != null) - { - try - { - if (typeAccessor != null) - { - //var parseFn = Serializer.GetParseFn(propType); - var parseFn = JsonReader.GetParseFn(propType); - - var propertyValue = parseFn(propertyValueStr); - typeAccessor.SetProperty(instance, propertyValue); - } - - //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - for (; index < strType.Length; index++) { var c = strType[index]; if (c >= JsonTypeSerializer.WhiteSpaceFlags.Length || !JsonTypeSerializer.WhiteSpaceFlags[c]) break; } //Whitespace inline - if (index != strType.Length) - { - var success = strType[index] == JsWriter.ItemSeperator || strType[index] == JsWriter.MapEndChar; - index++; - if (success) - for (; index < strType.Length; index++) { var c = strType[index]; if (c >= JsonTypeSerializer.WhiteSpaceFlags.Length || !JsonTypeSerializer.WhiteSpaceFlags[c]) break; } //Whitespace inline - } - - continue; - } - catch(Exception e) - { - if (JsConfig.ThrowOnDeserializationError) throw DeserializeTypeRef.GetSerializationException(propertyName, propertyValueStr, propType, e); - else Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName, propertyValueStr); - } - } - - if (typeAccessor != null && typeAccessor.GetProperty != null && typeAccessor.SetProperty != null) - { - try - { - var propertyValue = typeAccessor.GetProperty(propertyValueStr); - typeAccessor.SetProperty(instance, propertyValue); - } - catch(Exception e) - { - if (JsConfig.ThrowOnDeserializationError) throw DeserializeTypeRef.GetSerializationException(propertyName, propertyValueStr, typeAccessor.PropertyType, e); - else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName, propertyValueStr); - } - } - - //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - for (; index < strType.Length; index++) { var c = strType[index]; if (c >= JsonTypeSerializer.WhiteSpaceFlags.Length || !JsonTypeSerializer.WhiteSpaceFlags[c]) break; } //Whitespace inline - if (index != strType.Length) - { - var success = strType[index] == JsWriter.ItemSeperator || strType[index] == JsWriter.MapEndChar; - index++; - if (success) - for (; index < strType.Length; index++) { var c = strType[index]; if (c >= JsonTypeSerializer.WhiteSpaceFlags.Length || !JsonTypeSerializer.WhiteSpaceFlags[c]) break; } //Whitespace inline - } - - } - - return instance; - } - } + } + } + + Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); + continue; + } + + if (instance == null) instance = ctorFn(); + + var typeAccessor = typeAccessors.Get(propertyName, lenient); + + var propType = possibleTypeInfo && propertyValueStr[0] == '_' ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; + if (propType != null) + { + try + { + if (typeAccessor != null) + { + //var parseFn = Serializer.GetParseFn(propType); + var parseFn = JsonReader.GetParseStringSpanFn(propType); + + var propertyValue = parseFn(propertyValueStr); + if (typeConfig.OnDeserializing != null) + propertyValue = typeConfig.OnDeserializing(instance, propertyName.ToString(), propertyValue); + typeAccessor.SetProperty(instance, propertyValue); + } + + //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline + if (index != strTypeLength) + { + var success = buffer[index] == JsWriter.ItemSeperator || buffer[index] == JsWriter.MapEndChar; + index++; + if (success) + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline + } + + continue; + } + catch (Exception e) + { + config.OnDeserializationError?.Invoke(instance, propType, propertyName.ToString(), propertyValueStr.ToString(), e); + if (config.ThrowOnError) throw DeserializeTypeRef.GetSerializationException(propertyName.ToString(), propertyValueStr.ToString(), propType, e); + else Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName.ToString(), propertyValueStr.ToString()); + } + } + + if (typeAccessor?.GetProperty != null && typeAccessor.SetProperty != null) + { + try + { + var propertyValue = typeAccessor.GetProperty(propertyValueStr); + if (typeConfig.OnDeserializing != null) + propertyValue = typeConfig.OnDeserializing(instance, propertyName.ToString(), propertyValue); + typeAccessor.SetProperty(instance, propertyValue); + } + catch (NotSupportedException) { throw; } + catch (Exception e) + { + config.OnDeserializationError?.Invoke(instance, propType ?? typeAccessor.PropertyType, propertyName.ToString(), propertyValueStr.ToString(), e); + if (config.ThrowOnError) throw DeserializeTypeRef.GetSerializationException(propertyName.ToString(), propertyValueStr.ToString(), typeAccessor.PropertyType, e); + else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName.ToString(), propertyValueStr.ToString()); + } + } + else + { + // the property is not known by the DTO + typeConfig.OnDeserializing?.Invoke(instance, propertyName.ToString(), propertyValueStr.ToString()); + } + + //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline + if (index != strType.Length) + { + var success = buffer[index] == JsWriter.ItemSeperator || buffer[index] == JsWriter.MapEndChar; + index++; + if (success) + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline + } + + } + + return instance; + } + } } diff --git a/src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs b/src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs index 0881a2a81..2f6c57379 100644 --- a/src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs +++ b/src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs @@ -1,130 +1,155 @@ using System; using System.Collections.Generic; -using System.Runtime.Serialization; using ServiceStack.Text.Json; using ServiceStack.Text.Jsv; namespace ServiceStack.Text.Common { - internal static class DeserializeTypeRefJsv - { - private static readonly JsvTypeSerializer Serializer = (JsvTypeSerializer)JsvTypeSerializer.Instance; + internal static class DeserializeTypeRefJsv + { + private static readonly JsvTypeSerializer Serializer = (JsvTypeSerializer)JsvTypeSerializer.Instance; - internal static object StringToType( - Type type, - string strType, - EmptyCtorDelegate ctorFn, - Dictionary typeAccessorMap) - { - var index = 0; + static readonly ReadOnlyMemory typeAttr = JsWriter.TypeAttr.AsMemory(); - if (strType == null) - return null; + internal static object StringToType(ReadOnlySpan strType, + TypeConfig typeConfig, + EmptyCtorDelegate ctorFn, + KeyValuePair[] typeAccessors) + { + var index = 0; + var type = typeConfig.Type; - //if (!Serializer.EatMapStartChar(strType, ref index)) - if (strType[index++] != JsWriter.MapStartChar) - throw DeserializeTypeRef.CreateSerializationError(type, strType); + if (strType.IsEmpty) + return null; - if (JsonTypeSerializer.IsEmptyMap(strType)) return ctorFn(); + //if (!Serializer.EatMapStartChar(strType, ref index)) + if (strType[index++] != JsWriter.MapStartChar) + throw DeserializeTypeRef.CreateSerializationError(type, strType.ToString()); - object instance = null; + if (JsonTypeSerializer.IsEmptyMap(strType)) + return ctorFn(); - var strTypeLength = strType.Length; - while (index < strTypeLength) - { - var propertyName = Serializer.EatMapKey(strType, ref index); + var config = JsConfig.GetConfig(); - //Serializer.EatMapKeySeperator(strType, ref index); - index++; + object instance = null; + var lenient = config.PropertyConvention == PropertyConvention.Lenient || config.TextCase == TextCase.SnakeCase; - var propertyValueStr = Serializer.EatValue(strType, ref index); - var possibleTypeInfo = propertyValueStr != null && propertyValueStr.Length > 1; + var strTypeLength = strType.Length; + while (index < strTypeLength) + { + var propertyName = Serializer.EatMapKey(strType, ref index).Trim(); - if (possibleTypeInfo && propertyName == JsWriter.TypeAttr) - { - var explicitTypeName = Serializer.ParseString(propertyValueStr); - var explicitType = AssemblyUtils.FindType(explicitTypeName); - if (explicitType != null && !explicitType.IsInterface && !explicitType.IsAbstract) { + //Serializer.EatMapKeySeperator(strType, ref index); + index++; + + var propertyValueStr = Serializer.EatValue(strType, ref index); + var possibleTypeInfo = propertyValueStr != null && propertyValueStr.Length > 1; + + if (possibleTypeInfo && propertyName.Equals(typeAttr.Span, StringComparison.OrdinalIgnoreCase)) + { + var explicitTypeName = Serializer.ParseString(propertyValueStr); + var explicitType = config.TypeFinder(explicitTypeName); + + if (explicitType == null || explicitType.IsInterface || explicitType.IsAbstract) + { + Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr.ToString()); + } + else if (!type.IsAssignableFrom(explicitType)) + { + Tracer.Instance.WriteWarning("Could not assign type: " + propertyValueStr.ToString()); + } + else + { + JsWriter.AssertAllowedRuntimeType(explicitType); instance = explicitType.CreateInstance(); } - if (instance == null) - { - Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr); - } - else - { - //If __type info doesn't match, ignore it. - if (!type.IsInstanceOfType(instance)) { - instance = null; - } else { - var derivedType = instance.GetType(); - if (derivedType != type) { - var derivedTypeConfig = new TypeConfig(derivedType); - var map = DeserializeTypeRef.GetTypeAccessorMap(derivedTypeConfig, Serializer); - if (map != null) { - typeAccessorMap = map; - } + if (instance != null) + { + //If __type info doesn't match, ignore it. + if (!type.IsInstanceOfType(instance)) + { + instance = null; + } + else + { + var derivedType = instance.GetType(); + if (derivedType != type) + { + var map = DeserializeTypeRef.GetCachedTypeAccessors(derivedType, Serializer); + if (map != null) + typeAccessors = map; } - } - } - - //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - if (index != strType.Length) index++; - - continue; - } - - if (instance == null) instance = ctorFn(); - - TypeAccessor typeAccessor; - typeAccessorMap.TryGetValue(propertyName, out typeAccessor); - - var propType = possibleTypeInfo && propertyValueStr[0] == '_' ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; - if (propType != null) - { - try - { - if (typeAccessor != null) - { - var parseFn = Serializer.GetParseFn(propType); - var propertyValue = parseFn(propertyValueStr); - typeAccessor.SetProperty(instance, propertyValue); - } - - //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - if (index != strType.Length) index++; - - continue; - } - catch(Exception e) - { - if (JsConfig.ThrowOnDeserializationError) throw DeserializeTypeRef.GetSerializationException(propertyName, propertyValueStr, propType, e); - else Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName, propertyValueStr); - } - } - - if (typeAccessor != null && typeAccessor.GetProperty != null && typeAccessor.SetProperty != null) - { - try - { - var propertyValue = typeAccessor.GetProperty(propertyValueStr); - typeAccessor.SetProperty(instance, propertyValue); - } - catch(Exception e) - { - if (JsConfig.ThrowOnDeserializationError) throw DeserializeTypeRef.GetSerializationException(propertyName, propertyValueStr, propType, e); - else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName, propertyValueStr); - } - } - - //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - if (index != strType.Length) index++; - } - - return instance; - } - } - - //The same class above but JSON-specific to enable inlining in this hot class. + } + } + + //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); + if (index != strType.Length) index++; + + continue; + } + + if (instance == null) instance = ctorFn(); + + var typeAccessor = typeAccessors.Get(propertyName, lenient); + + var propType = possibleTypeInfo && propertyValueStr[0] == '_' ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; + if (propType != null) + { + try + { + if (typeAccessor != null) + { + var parseFn = Serializer.GetParseStringSpanFn(propType); + var propertyValue = parseFn(propertyValueStr); + if (typeConfig.OnDeserializing != null) + propertyValue = typeConfig.OnDeserializing(instance, propertyName.ToString(), propertyValue); + typeAccessor.SetProperty(instance, propertyValue); + } + + //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); + if (index != strType.Length) index++; + + continue; + } + catch (Exception e) + { + config.OnDeserializationError?.Invoke(instance, propType, propertyName.ToString(), propertyValueStr.ToString(), e); + if (config.ThrowOnError) throw DeserializeTypeRef.GetSerializationException(propertyName.ToString(), propertyValueStr.ToString(), propType, e); + else Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName.ToString(), propertyValueStr.ToString()); + } + } + + if (typeAccessor?.GetProperty != null && typeAccessor.SetProperty != null) + { + try + { + var propertyValue = typeAccessor.GetProperty(propertyValueStr); + if (typeConfig.OnDeserializing != null) + propertyValue = typeConfig.OnDeserializing(instance, propertyName.ToString(), propertyValue); + typeAccessor.SetProperty(instance, propertyValue); + } + catch (NotSupportedException) { throw; } + catch (Exception e) + { + config.OnDeserializationError?.Invoke(instance, propType ?? typeAccessor.PropertyType, propertyName.ToString(), propertyValueStr.ToString(), e); + if (config.ThrowOnError) throw DeserializeTypeRef.GetSerializationException(propertyName.ToString(), propertyValueStr.ToString(), propType, e); + else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName.ToString(), propertyValueStr.ToString()); + } + } + else + { + // the property is not known by the DTO + typeConfig.OnDeserializing?.Invoke(instance, propertyName.ToString(), propertyValueStr.ToString()); + } + + //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); + if (index != strType.Length) index++; + } + + return instance; + } + } + + //The same class above but JSON-specific to enable inlining in this hot class. } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeTypeUtils.cs b/src/ServiceStack.Text/Common/DeserializeTypeUtils.cs index a73668b52..9e2a2e490 100644 --- a/src/ServiceStack.Text/Common/DeserializeTypeUtils.cs +++ b/src/ServiceStack.Text/Common/DeserializeTypeUtils.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; @@ -15,37 +15,39 @@ namespace ServiceStack.Text.Common { - public class DeserializeTypeUtils - { - public static ParseStringDelegate GetParseMethod(Type type) - { - var typeConstructor = GetTypeStringConstructor(type); - if (typeConstructor != null) - { - return value => typeConstructor.Invoke(new object[] { value }); - } + public class DeserializeTypeUtils + { + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); - return null; - } + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) + { + var typeConstructor = GetTypeStringConstructor(type); + if (typeConstructor != null) + { + return value => typeConstructor.Invoke(new object[] { value.ToString() }); + } - /// - /// Get the type(string) constructor if exists - /// - /// The type. - /// - public static ConstructorInfo GetTypeStringConstructor(Type type) - { - foreach (var ci in type.GetConstructors()) - { - var paramInfos = ci.GetParameters(); - var matchFound = (paramInfos.Length == 1 && paramInfos[0].ParameterType == typeof(string)); - if (matchFound) - { - return ci; - } - } - return null; - } + return null; + } - } + /// + /// Get the type(string) constructor if exists + /// + /// The type. + /// + public static ConstructorInfo GetTypeStringConstructor(Type type) + { + foreach (var ci in type.GetConstructors()) + { + var paramInfos = ci.GetParameters(); + var matchFound = paramInfos.Length == 1 && paramInfos[0].ParameterType == typeof(string); + if (matchFound) + { + return ci; + } + } + return null; + } + + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/ITypeSerializer.cs b/src/ServiceStack.Text/Common/ITypeSerializer.cs index e1730003c..2f25ebaa9 100644 --- a/src/ServiceStack.Text/Common/ITypeSerializer.cs +++ b/src/ServiceStack.Text/Common/ITypeSerializer.cs @@ -4,62 +4,85 @@ namespace ServiceStack.Text.Common { - internal interface ITypeSerializer - { + public delegate object ObjectDeserializerDelegate(ReadOnlySpan value); + + public interface ITypeSerializer + { + ObjectDeserializerDelegate ObjectDeserializer { get; set; } + bool IncludeNullValues { get; } + bool IncludeNullValuesInDictionaries { get; } string TypeAttrInObject { get; } - WriteObjectDelegate GetWriteFn(); - WriteObjectDelegate GetWriteFn(Type type); - TypeInfo GetTypeInfo(Type type); + WriteObjectDelegate GetWriteFn(); + WriteObjectDelegate GetWriteFn(Type type); + TypeInfo GetTypeInfo(Type type); - void WriteRawString(TextWriter writer, string value); - void WritePropertyName(TextWriter writer, string value); + void WriteRawString(TextWriter writer, string value); + void WritePropertyName(TextWriter writer, string value); - void WriteBuiltIn(TextWriter writer, object value); - void WriteObjectString(TextWriter writer, object value); - void WriteException(TextWriter writer, object value); - void WriteString(TextWriter writer, string value); - void WriteDateTime(TextWriter writer, object oDateTime); - void WriteNullableDateTime(TextWriter writer, object dateTime); - void WriteDateTimeOffset(TextWriter writer, object oDateTimeOffset); - void WriteNullableDateTimeOffset(TextWriter writer, object dateTimeOffset); - void WriteTimeSpan(TextWriter writer, object dateTimeOffset); - void WriteNullableTimeSpan(TextWriter writer, object dateTimeOffset); - void WriteGuid(TextWriter writer, object oValue); - void WriteNullableGuid(TextWriter writer, object oValue); - void WriteBytes(TextWriter writer, object oByteValue); - void WriteChar(TextWriter writer, object charValue); - void WriteByte(TextWriter writer, object byteValue); - void WriteInt16(TextWriter writer, object intValue); - void WriteUInt16(TextWriter writer, object intValue); - void WriteInt32(TextWriter writer, object intValue); - void WriteUInt32(TextWriter writer, object uintValue); - void WriteInt64(TextWriter writer, object longValue); - void WriteUInt64(TextWriter writer, object ulongValue); - void WriteBool(TextWriter writer, object boolValue); - void WriteFloat(TextWriter writer, object floatValue); - void WriteDouble(TextWriter writer, object doubleValue); + void WriteBuiltIn(TextWriter writer, object value); + void WriteObjectString(TextWriter writer, object value); + void WriteException(TextWriter writer, object value); + void WriteString(TextWriter writer, string value); + void WriteFormattableObjectString(TextWriter writer, object value); + void WriteDateTime(TextWriter writer, object oDateTime); + void WriteNullableDateTime(TextWriter writer, object dateTime); + void WriteDateTimeOffset(TextWriter writer, object oDateTimeOffset); + void WriteNullableDateTimeOffset(TextWriter writer, object dateTimeOffset); + void WriteTimeSpan(TextWriter writer, object timeSpan); + void WriteNullableTimeSpan(TextWriter writer, object timeSpan); + void WriteGuid(TextWriter writer, object oValue); + void WriteNullableGuid(TextWriter writer, object oValue); + void WriteBytes(TextWriter writer, object oByteValue); + void WriteChar(TextWriter writer, object charValue); + void WriteByte(TextWriter writer, object byteValue); + void WriteSByte(TextWriter writer, object sbyteValue); + void WriteInt16(TextWriter writer, object intValue); + void WriteUInt16(TextWriter writer, object intValue); + void WriteInt32(TextWriter writer, object intValue); + void WriteUInt32(TextWriter writer, object uintValue); + void WriteInt64(TextWriter writer, object longValue); + void WriteUInt64(TextWriter writer, object ulongValue); + void WriteBool(TextWriter writer, object boolValue); + void WriteFloat(TextWriter writer, object floatValue); + void WriteDouble(TextWriter writer, object doubleValue); void WriteDecimal(TextWriter writer, object decimalValue); void WriteEnum(TextWriter writer, object enumValue); - void WriteEnumFlags(TextWriter writer, object enumFlagValue); - void WriteLinqBinary(TextWriter writer, object linqBinaryValue); - //object EncodeMapKey(object value); +#if NET6_0_OR_GREATER + void WriteDateOnly(TextWriter writer, object oDateOnly); + void WriteNullableDateOnly(TextWriter writer, object oDateOnly); + void WriteTimeOnly(TextWriter writer, object oTimeOnly); + void WriteNullableTimeOnly(TextWriter writer, object oTimeOnly); +#endif - ParseStringDelegate GetParseFn(); - ParseStringDelegate GetParseFn(Type type); + ParseStringDelegate GetParseFn(); + ParseStringSpanDelegate GetParseStringSpanFn(); + ParseStringDelegate GetParseFn(Type type); + ParseStringSpanDelegate GetParseStringSpanFn(Type type); - string ParseRawString(string value); + string ParseRawString(string value); string ParseString(string value); + string ParseString(ReadOnlySpan value); string UnescapeString(string value); + ReadOnlySpan UnescapeString(ReadOnlySpan value); + object UnescapeStringAsObject(ReadOnlySpan value); string UnescapeSafeString(string value); + ReadOnlySpan UnescapeSafeString(ReadOnlySpan value); string EatTypeValue(string value, ref int i); - bool EatMapStartChar(string value, ref int i); - string EatMapKey(string value, ref int i); - bool EatMapKeySeperator(string value, ref int i); - void EatWhitespace(string value, ref int i); - string EatValue(string value, ref int i); - bool EatItemSeperatorOrMapEndChar(string value, ref int i); - } + ReadOnlySpan EatTypeValue(ReadOnlySpan value, ref int i); + bool EatMapStartChar(string value, ref int i); + bool EatMapStartChar(ReadOnlySpan value, ref int i); + string EatMapKey(string value, ref int i); + ReadOnlySpan EatMapKey(ReadOnlySpan value, ref int i); + bool EatMapKeySeperator(string value, ref int i); + bool EatMapKeySeperator(ReadOnlySpan value, ref int i); + void EatWhitespace(string value, ref int i); + void EatWhitespace(ReadOnlySpan value, ref int i); + string EatValue(string value, ref int i); + ReadOnlySpan EatValue(ReadOnlySpan value, ref int i); + bool EatItemSeperatorOrMapEndChar(string value, ref int i); + bool EatItemSeperatorOrMapEndChar(ReadOnlySpan value, ref int i); + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/JsDelegates.cs b/src/ServiceStack.Text/Common/JsDelegates.cs index bf7482169..b6bb0cdcc 100644 --- a/src/ServiceStack.Text/Common/JsDelegates.cs +++ b/src/ServiceStack.Text/Common/JsDelegates.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; @@ -16,21 +16,25 @@ namespace ServiceStack.Text.Common { - internal delegate void WriteListDelegate(TextWriter writer, object oList, WriteObjectDelegate toStringFn); + internal delegate void WriteListDelegate(TextWriter writer, object oList, WriteObjectDelegate toStringFn); - internal delegate void WriteGenericListDelegate(TextWriter writer, IList list, WriteObjectDelegate toStringFn); + internal delegate void WriteGenericListDelegate(TextWriter writer, IList list, WriteObjectDelegate toStringFn); - internal delegate void WriteDelegate(TextWriter writer, object value); + internal delegate void WriteDelegate(TextWriter writer, object value); - internal delegate ParseStringDelegate ParseFactoryDelegate(); + internal delegate ParseStringSpanDelegate ParseFactoryDelegate(); - internal delegate void WriteObjectDelegate(TextWriter writer, object obj); + public delegate object DeserializeStringSpanDelegate(Type type, ReadOnlySpan source); - public delegate void SetPropertyDelegate(object instance, object propertyValue); + public delegate void WriteObjectDelegate(TextWriter writer, object obj); - public delegate object ParseStringDelegate(string stringValue); + public delegate object ParseStringDelegate(string stringValue); - public delegate object ConvertObjectDelegate(object fromObject); + public delegate object ParseStringSpanDelegate(ReadOnlySpan value); + + public delegate object ConvertObjectDelegate(object fromObject); public delegate object ConvertInstanceDelegate(object obj, Type type); + + public delegate void DeserializationErrorDelegate(object instance, Type propertyType, string propertyName, string propertyValueStr, Exception ex); } diff --git a/src/ServiceStack.Text/Common/JsReader.cs b/src/ServiceStack.Text/Common/JsReader.cs index 18828b7ff..bfdcd897c 100644 --- a/src/ServiceStack.Text/Common/JsReader.cs +++ b/src/ServiceStack.Text/Common/JsReader.cs @@ -1,125 +1,163 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; namespace ServiceStack.Text.Common { - internal class JsReader - where TSerializer : ITypeSerializer - { - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - - public ParseStringDelegate GetParseFn() - { - var onDeserializedFn = JsConfig.OnDeserializedFn; - if (onDeserializedFn != null) { - return value => onDeserializedFn((T)GetCoreParseFn()(value)); + public class JsReader + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + public ParseStringDelegate GetParseFn() + { + var onDeserializedFn = JsConfig.OnDeserializedFn; + if (onDeserializedFn != null) + { + var parseFn = GetCoreParseFn(); + return value => onDeserializedFn((T)parseFn(value)); + } + + return GetCoreParseFn(); + } + + public ParseStringSpanDelegate GetParseStringSpanFn() + { + var onDeserializedFn = JsConfig.OnDeserializedFn; + if (onDeserializedFn != null) + { + var parseFn = GetCoreParseStringSpanFn(); + return value => onDeserializedFn((T)parseFn(value)); } - return GetCoreParseFn(); - } + return GetCoreParseStringSpanFn(); + } - private ParseStringDelegate GetCoreParseFn() - { - var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + private ParseStringDelegate GetCoreParseFn() + { + return v => GetCoreParseStringSpanFn()(v.AsSpan()); + } - if (JsConfig.HasDeserializeFn) - return value => JsConfig.ParseFn(Serializer, value); + private ParseStringSpanDelegate GetCoreParseStringSpanFn() + { + var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); - if (type.IsEnum) - { - return x => Enum.Parse(type, x, true); - } + if (JsConfig.HasDeserializeFn) + return value => JsConfig.ParseFn(Serializer, value.Value()); - if (type == typeof(string)) - return Serializer.UnescapeString; + if (type.IsEnum) + return x => ParseUtils.TryParseEnum(type, Serializer.UnescapeSafeString(x).Value()); - if (type == typeof(object)) - return DeserializeType.ObjectStringToType; + if (type == typeof(string)) + return Serializer.UnescapeStringAsObject; - var specialParseFn = ParseUtils.GetSpecialParseMethod(type); - if (specialParseFn != null) - return specialParseFn; + if (type == typeof(object)) + return DeserializeType.ObjectStringToType; - if (type.IsEnum) - return x => Enum.Parse(type, x, true); + var specialParseFn = ParseUtils.GetSpecialParseMethod(type); + if (specialParseFn != null) + return v => specialParseFn(v.Value()); - if (type.IsArray) - { - return DeserializeArray.Parse; - } + if (type.IsArray) + { + return DeserializeArray.ParseStringSpan; + } - var builtInMethod = DeserializeBuiltin.Parse; - if (builtInMethod != null) - return value => builtInMethod(Serializer.UnescapeSafeString(value)); + var builtInMethod = DeserializeBuiltin.ParseStringSpan; + if (builtInMethod != null) + return value => builtInMethod(Serializer.UnescapeSafeString(value)); - if (type.IsGenericType()) - { - if (type.IsOrHasGenericInterfaceTypeOf(typeof(IList<>))) - return DeserializeList.Parse; + if (type.HasGenericType()) + { + if (type.IsOrHasGenericInterfaceTypeOf(typeof(IList<>))) + return DeserializeList.ParseStringSpan; - if (type.IsOrHasGenericInterfaceTypeOf(typeof(IDictionary<,>))) - return DeserializeDictionary.GetParseMethod(type); + if (type.IsOrHasGenericInterfaceTypeOf(typeof(IDictionary<,>))) + return DeserializeDictionary.GetParseStringSpanMethod(type); - if (type.IsOrHasGenericInterfaceTypeOf(typeof(ICollection<>))) - return DeserializeCollection.GetParseMethod(type); + if (type.IsOrHasGenericInterfaceTypeOf(typeof(ICollection<>))) + return DeserializeCollection.GetParseStringSpanMethod(type); - if (type.HasAnyTypeDefinitionsOf(typeof(Queue<>)) - || type.HasAnyTypeDefinitionsOf(typeof(Stack<>))) - return DeserializeSpecializedCollections.Parse; + if (type.HasAnyTypeDefinitionsOf(typeof(Queue<>)) + || type.HasAnyTypeDefinitionsOf(typeof(Stack<>))) + return DeserializeSpecializedCollections.ParseStringSpan; if (type.IsOrHasGenericInterfaceTypeOf(typeof(KeyValuePair<,>))) - return DeserializeKeyValuePair.GetParseMethod(type); + return DeserializeKeyValuePair.GetParseStringSpanMethod(type); - if (type.IsOrHasGenericInterfaceTypeOf(typeof(IEnumerable<>))) - return DeserializeEnumerable.Parse; - } + if (type.IsOrHasGenericInterfaceTypeOf(typeof(IEnumerable<>))) + return DeserializeEnumerable.ParseStringSpan; -#if NET40 - if (typeof (T).IsAssignableFrom(typeof (System.Dynamic.IDynamicMetaObjectProvider)) || - typeof (T).HasInterface(typeof (System.Dynamic.IDynamicMetaObjectProvider))) - { - return DeserializeDynamic.Parse; + var customFn = DeserializeCustomGenericType.GetParseStringSpanMethod(type); + if (customFn != null) + return customFn; } -#endif - var isDictionary = typeof(T).IsAssignableFrom(typeof(IDictionary)) - || typeof(T).HasInterface(typeof(IDictionary)); + var pclParseFn = PclExport.Instance.GetJsReaderParseStringSpanMethod(typeof(T)); + if (pclParseFn != null) + return pclParseFn; + + var isDictionary = typeof(T) != typeof(IEnumerable) && typeof(T) != typeof(ICollection) + && (typeof(T).IsAssignableFrom(typeof(IDictionary)) || typeof(T).HasInterface(typeof(IDictionary))); if (isDictionary) { - return DeserializeDictionary.GetParseMethod(type); + return DeserializeDictionary.GetParseStringSpanMethod(type); + } + + var isEnumerable = typeof(T).IsAssignableFrom(typeof(IEnumerable)) + || typeof(T).HasInterface(typeof(IEnumerable)); + if (isEnumerable) + { + var parseFn = DeserializeSpecializedCollections.ParseStringSpan; + if (parseFn != null) + return parseFn; + } + + if (type.IsValueType) + { + //at first try to find more faster `ParseStringSpan` method + var staticParseStringSpanMethod = StaticParseMethod.ParseStringSpan; + if (staticParseStringSpanMethod != null) + return value => staticParseStringSpanMethod(Serializer.UnescapeSafeString(value)); + + //then try to find `Parse` method + var staticParseMethod = StaticParseMethod.Parse; + if (staticParseMethod != null) + return value => staticParseMethod(Serializer.UnescapeSafeString(value).ToString()); + } + else + { + var staticParseStringSpanMethod = StaticParseRefTypeMethod.ParseStringSpan; + if (staticParseStringSpanMethod != null) + return value => staticParseStringSpanMethod(Serializer.UnescapeSafeString(value)); + + var staticParseMethod = StaticParseRefTypeMethod.Parse; + if (staticParseMethod != null) + return value => staticParseMethod(Serializer.UnescapeSafeString(value).ToString()); } - var isEnumerable = typeof(T).IsAssignableFrom(typeof(IEnumerable)) - || typeof(T).HasInterface(typeof(IEnumerable)); - if (isEnumerable) - { - var parseFn = DeserializeSpecializedCollections.Parse; - if (parseFn != null) return parseFn; - } - - if (type.IsValueType) - { - var staticParseMethod = StaticParseMethod.Parse; - if (staticParseMethod != null) - return value => staticParseMethod(Serializer.UnescapeSafeString(value)); - } - else - { - var staticParseMethod = StaticParseRefTypeMethod.Parse; - if (staticParseMethod != null) - return value => staticParseMethod(Serializer.UnescapeSafeString(value)); - } - - var typeConstructor = DeserializeType.GetParseMethod(TypeConfig.GetState()); - if (typeConstructor != null) - return typeConstructor; - - var stringConstructor = DeserializeTypeUtils.GetParseMethod(type); - if (stringConstructor != null) return stringConstructor; - - return DeserializeType.ParseAbstractType; - } - - } -} \ No newline at end of file + var typeConstructor = DeserializeType.GetParseStringSpanMethod(TypeConfig.GetState()); + if (typeConstructor != null) + return typeConstructor; + + var stringConstructor = DeserializeTypeUtils.GetParseStringSpanMethod(type); + if (stringConstructor != null) + return stringConstructor; + + return DeserializeType.ParseAbstractType; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + var hold = DeserializeBuiltin.Parse; + hold = DeserializeArray.Parse; + DeserializeType.ExtractType(default(ReadOnlySpan)); + DeserializeArrayWithElements.ParseGenericArray(default(ReadOnlySpan), null); + DeserializeCollection.ParseCollection(default(ReadOnlySpan), null, null); + DeserializeListWithElements.ParseGenericList(default(ReadOnlySpan), null, null); + } + } +} diff --git a/src/ServiceStack.Text/Common/JsState.cs b/src/ServiceStack.Text/Common/JsState.cs index a35f46bd3..778b1060a 100644 --- a/src/ServiceStack.Text/Common/JsState.cs +++ b/src/ServiceStack.Text/Common/JsState.cs @@ -1,16 +1,100 @@ using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace ServiceStack.Text.Common { - internal static class JsState - { - //Exposing field for perf - [ThreadStatic] internal static int WritingKeyCount = 0; + internal static class JsState + { + //Exposing field for perf + [ThreadStatic] + internal static int WritingKeyCount = 0; - [ThreadStatic] internal static bool IsWritingValue = false; + [ThreadStatic] + internal static bool IsWritingValue = false; - [ThreadStatic] internal static bool IsWritingDynamic = false; + [ThreadStatic] + internal static bool IsWritingDynamic = false; - [ThreadStatic] internal static bool QueryStringMode = false; - } + [ThreadStatic] + internal static bool IsRuntimeType = false; + + [ThreadStatic] + internal static bool QueryStringMode = false; + + [ThreadStatic] + internal static int Depth = 0; + + [ThreadStatic] + internal static bool IsCsv = false; + + + [ThreadStatic] + internal static HashSet InSerializerFns = new HashSet(); + + internal static void RegisterSerializer() + { + if (InSerializerFns == null) + InSerializerFns = new HashSet(); + + InSerializerFns.Add(typeof(T)); + } + + internal static void UnRegisterSerializer() + { + if (InSerializerFns == null) + return; + + InSerializerFns.Remove(typeof(T)); + } + + internal static bool InSerializer() + { + return InSerializerFns != null && InSerializerFns.Contains(typeof(T)); + } + + [ThreadStatic] + internal static HashSet InDeserializerFns; + + internal static void RegisterDeserializer() + { + if (InDeserializerFns == null) + InDeserializerFns = new HashSet(); + + InDeserializerFns.Add(typeof(T)); + } + + internal static void UnRegisterDeserializer() + { + if (InDeserializerFns == null) + return; + + InDeserializerFns.Remove(typeof(T)); + } + + internal static bool InDeserializer() + { + return InDeserializerFns != null && InDeserializerFns.Contains(typeof(T)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool Traverse(object value) + { + if (++Depth <= JsConfig.MaxDepth) + return true; + + Tracer.Instance.WriteError( + $"Exceeded MaxDepth limit of {JsConfig.MaxDepth} attempting to serialize {value.GetType().Name}"); + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void UnTraverse() => --Depth; + + internal static void Reset() + { + InSerializerFns = null; + InDeserializerFns = null; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/JsWriter.cs b/src/ServiceStack.Text/Common/JsWriter.cs index ed3c724de..e9bb5dd00 100644 --- a/src/ServiceStack.Text/Common/JsWriter.cs +++ b/src/ServiceStack.Text/Common/JsWriter.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; - +using System.Runtime.CompilerServices; using ServiceStack.Text.Json; using ServiceStack.Text.Jsv; @@ -42,11 +42,12 @@ static JsWriter() { EscapeCharFlags[escapeChar] = true; } - var loadConfig = JsConfig.EmitCamelCaseNames; //force load + var loadConfig = JsConfig.TextCase; //force load } public static void WriteDynamic(Action callback) { + var prevState = JsState.IsWritingDynamic; JsState.IsWritingDynamic = true; try { @@ -54,7 +55,7 @@ public static void WriteDynamic(Action callback) } finally { - JsState.IsWritingDynamic = false; + JsState.IsWritingDynamic = prevState; } } @@ -81,31 +82,32 @@ internal static void WriteItemSeperatorIfRanOnce(TextWriter writer, ref bool ran writer.Write(ItemSeperator); else ranOnce = true; - - foreach (var escapeChar in EscapeChars) - { - EscapeCharFlags[escapeChar] = true; - } } internal static bool ShouldUseDefaultToStringMethod(Type type) { - return type == typeof(byte) || type == typeof(byte?) - || type == typeof(short) || type == typeof(short?) - || type == typeof(ushort) || type == typeof(ushort?) - || type == typeof(int) || type == typeof(int?) - || type == typeof(uint) || type == typeof(uint?) - || type == typeof(long) || type == typeof(long?) - || type == typeof(ulong) || type == typeof(ulong?) - || type == typeof(bool) || type == typeof(bool?) - || type == typeof(DateTime) || type == typeof(DateTime?) - || type == typeof(Guid) || type == typeof(Guid?) - || type == typeof(float) || type == typeof(float?) - || type == typeof(double) || type == typeof(double?) - || type == typeof(decimal) || type == typeof(decimal?); + var underlyingType = Nullable.GetUnderlyingType(type) ?? type; + switch (underlyingType.GetTypeCode()) + { + case TypeCode.SByte: + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + case TypeCode.DateTime: + return true; + } + + return underlyingType == typeof(Guid); } - internal static ITypeSerializer GetTypeSerializer() + public static ITypeSerializer GetTypeSerializer() { if (typeof(TSerializer) == typeof(JsvTypeSerializer)) return JsvTypeSerializer.Instance; @@ -120,17 +122,19 @@ public static void WriteEnumFlags(TextWriter writer, object enumFlagValue) { if (enumFlagValue == null) return; - var typeCode = Type.GetTypeCode(Enum.GetUnderlyingType(enumFlagValue.GetType())); - + var typeCode = Enum.GetUnderlyingType(enumFlagValue.GetType()).GetTypeCode(); switch (typeCode) { + case TypeCode.SByte: + writer.Write((sbyte)enumFlagValue); + break; case TypeCode.Byte: writer.Write((byte)enumFlagValue); break; case TypeCode.Int16: writer.Write((short)enumFlagValue); break; - case TypeCode.UInt16: + case TypeCode.UInt16: writer.Write((ushort)enumFlagValue); break; case TypeCode.Int32: @@ -150,9 +154,49 @@ public static void WriteEnumFlags(TextWriter writer, object enumFlagValue) break; } } - } - internal class JsWriter + public static bool ShouldAllowRuntimeType(Type type) + { + if (!JsState.IsRuntimeType) + return true; + + if (JsConfig.AllowRuntimeType?.Invoke(type) == true) + return true; + + var allowAttributesNamed = JsConfig.AllowRuntimeTypeWithAttributesNamed; + if (allowAttributesNamed?.Count > 0) + { + var oAttrs = type.AllAttributes(); + foreach (var oAttr in oAttrs) + { + if (!(oAttr is Attribute attr)) continue; + if (allowAttributesNamed.Contains(attr.GetType().Name)) + return true; + } + } + + var allowInterfacesNamed = JsConfig.AllowRuntimeTypeWithInterfacesNamed; + if (allowInterfacesNamed?.Count > 0) + { + var interfaces = type.GetInterfaces(); + foreach (var interfaceType in interfaces) + { + if (allowInterfacesNamed.Contains(interfaceType.Name)) + return true; + } + } + + return false; + } + + public static void AssertAllowedRuntimeType(Type type) + { + if (!ShouldAllowRuntimeType(type)) + throw new NotSupportedException($"{type.Name} is not an allowed Runtime Type. Whitelist Type with [RuntimeSerializable] or IRuntimeSerializable."); + } + } + + public class JsWriter where TSerializer : ITypeSerializer { private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); @@ -160,105 +204,171 @@ internal class JsWriter public JsWriter() { this.SpecialTypes = new Dictionary - { - { typeof(Uri), Serializer.WriteObjectString }, - { typeof(Type), WriteType }, - { typeof(Exception), Serializer.WriteException }, -#if !MONOTOUCH && !SILVERLIGHT && !XBOX && !ANDROID - { typeof(System.Data.Linq.Binary), Serializer.WriteLinqBinary }, -#endif - }; + { + { typeof(Uri), Serializer.WriteObjectString }, + { typeof(Type), WriteType }, + { typeof(Exception), Serializer.WriteException }, + }; } public WriteObjectDelegate GetValueTypeToStringMethod(Type type) { - if (type == typeof(char) || type == typeof(char?)) - return Serializer.WriteChar; - if (type == typeof(int) || type == typeof(int?)) - return Serializer.WriteInt32; - if (type == typeof(long) || type == typeof(long?)) - return Serializer.WriteInt64; - if (type == typeof(ulong) || type == typeof(ulong?)) - return Serializer.WriteUInt64; - if (type == typeof(uint) || type == typeof(uint?)) - return Serializer.WriteUInt32; - - if (type == typeof(byte) || type == typeof(byte?)) - return Serializer.WriteByte; - - if (type == typeof(short) || type == typeof(short?)) - return Serializer.WriteInt16; - if (type == typeof(ushort) || type == typeof(ushort?)) - return Serializer.WriteUInt16; - - if (type == typeof(bool) || type == typeof(bool?)) - return Serializer.WriteBool; + var underlyingType = Nullable.GetUnderlyingType(type); + var isNullable = underlyingType != null; + if (underlyingType == null) + underlyingType = type; - if (type == typeof(DateTime)) - return Serializer.WriteDateTime; - - if (type == typeof(DateTime?)) - return Serializer.WriteNullableDateTime; - - if (type == typeof(DateTimeOffset)) - return Serializer.WriteDateTimeOffset; - - if (type == typeof(DateTimeOffset?)) - return Serializer.WriteNullableDateTimeOffset; + if (!underlyingType.IsEnum) + { + var typeCode = underlyingType.GetTypeCode(); + + if (typeCode == TypeCode.Char) + return Serializer.WriteChar; + if (typeCode == TypeCode.Int32) + return Serializer.WriteInt32; + if (typeCode == TypeCode.Int64) + return Serializer.WriteInt64; + if (typeCode == TypeCode.UInt64) + return Serializer.WriteUInt64; + if (typeCode == TypeCode.UInt32) + return Serializer.WriteUInt32; + + if (typeCode == TypeCode.Byte) + return Serializer.WriteByte; + if (typeCode == TypeCode.SByte) + return Serializer.WriteSByte; + + if (typeCode == TypeCode.Int16) + return Serializer.WriteInt16; + if (typeCode == TypeCode.UInt16) + return Serializer.WriteUInt16; + + if (typeCode == TypeCode.Boolean) + return Serializer.WriteBool; + + if (typeCode == TypeCode.Single) + return Serializer.WriteFloat; + + if (typeCode == TypeCode.Double) + return Serializer.WriteDouble; + + if (typeCode == TypeCode.Decimal) + return Serializer.WriteDecimal; + + if (typeCode == TypeCode.DateTime) + if (isNullable) + return Serializer.WriteNullableDateTime; + else + return Serializer.WriteDateTime; + + if (type == typeof(DateTimeOffset)) + return Serializer.WriteDateTimeOffset; + + if (type == typeof(DateTimeOffset?)) + return Serializer.WriteNullableDateTimeOffset; + + if (type == typeof(TimeSpan)) + return Serializer.WriteTimeSpan; + + if (type == typeof(TimeSpan?)) + return Serializer.WriteNullableTimeSpan; + + if (type == typeof(Guid)) + return Serializer.WriteGuid; + + if (type == typeof(Guid?)) + return Serializer.WriteNullableGuid; + +#if NET6_0 + if (type == typeof(DateOnly)) + if (isNullable) + return Serializer.WriteNullableDateOnly; + else + return Serializer.WriteDateOnly; + if (type == typeof(DateOnly?)) + return Serializer.WriteDateOnly; + + if (type == typeof(TimeOnly)) + if (isNullable) + return Serializer.WriteNullableTimeOnly; + else + return Serializer.WriteTimeOnly; + if (type == typeof(TimeOnly?)) + return Serializer.WriteTimeOnly; +#endif + } + else + { + if (underlyingType.IsEnum) + { + return Serializer.WriteEnum; + } + } - if (type == typeof(TimeSpan)) - return Serializer.WriteTimeSpan; + if (type.HasInterface(typeof(IFormattable))) + return Serializer.WriteFormattableObjectString; - if (type == typeof(TimeSpan?)) - return Serializer.WriteNullableTimeSpan; + if (type.HasInterface(typeof(IValueWriter))) + return WriteValue; - if (type == typeof(Guid)) - return Serializer.WriteGuid; + return Serializer.WriteObjectString; + } - if (type == typeof(Guid?)) - return Serializer.WriteNullableGuid; + public WriteObjectDelegate GetWriteFn() + { + if (typeof(T) == typeof(string)) + { + return Serializer.WriteObjectString; + } - if (type == typeof(float) || type == typeof(float?)) - return Serializer.WriteFloat; + WriteObjectDelegate ret = null; - if (type == typeof(double) || type == typeof(double?)) - return Serializer.WriteDouble; + var onSerializingFn = JsConfig.OnSerializingFn; + if (onSerializingFn != null) + { + var writeFn = GetCoreWriteFn(); + ret = (w, x) => writeFn(w, onSerializingFn((T)x)); + } - if (type == typeof(decimal) || type == typeof(decimal?)) - return Serializer.WriteDecimal; + if (JsConfig.HasSerializeFn) + { + ret = JsConfig.WriteFn; + } - if (type.IsEnum || type.UnderlyingSystemType.IsEnum) - return type.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0 - ? (WriteObjectDelegate)Serializer.WriteEnumFlags - : Serializer.WriteEnum; + if (ret == null) + { + ret = GetCoreWriteFn(); + } - Type nullableType; - if ((nullableType = Nullable.GetUnderlyingType(type)) != null && nullableType.IsEnum) - return nullableType.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0 - ? (WriteObjectDelegate)Serializer.WriteEnumFlags - : Serializer.WriteEnum; + var onSerializedFn = JsConfig.OnSerializedFn; + if (onSerializedFn != null) + { + var writerFunc = ret; + ret = (w, x) => + { + writerFunc(w, x); + onSerializedFn((T)x); + }; + } - return Serializer.WriteObjectString; + return ret; } - internal WriteObjectDelegate GetWriteFn() + public void WriteValue(TextWriter writer, object value) { - if (typeof (T) == typeof (string)) { - return Serializer.WriteObjectString; - } - - var onSerializingFn = JsConfig.OnSerializingFn; - if (onSerializingFn != null) { - return (w, x) => GetCoreWriteFn()(w, onSerializingFn((T)x)); - } - - return GetCoreWriteFn(); + var valueWriter = (IValueWriter)value; + valueWriter.WriteTo(Serializer, writer); } + + void ThrowTaskNotSupported(TextWriter writer, object value) => + throw new NotSupportedException("Serializing Task's is not supported. Did you forget to await it?"); private WriteObjectDelegate GetCoreWriteFn() { - if ((typeof(T).IsValueType && !JsConfig.TreatAsRefType(typeof(T))) || - JsConfig.HasSerializeFn) + if (typeof(T).IsInstanceOf(typeof(System.Threading.Tasks.Task))) + return ThrowTaskNotSupported; + + if (typeof(T).IsValueType && !JsConfig.TreatAsRefType(typeof(T)) || JsConfig.HasSerializeFn) { return JsConfig.HasSerializeFn ? JsConfig.WriteFn @@ -289,7 +399,7 @@ private WriteObjectDelegate GetCoreWriteFn() return writeFn; } - if (typeof(T).IsGenericType() || + if (typeof(T).HasGenericType() || typeof(T).HasInterface(typeof(IDictionary))) // is ExpandoObject? { if (typeof(T).IsOrHasGenericInterfaceTypeOf(typeof(IList<>))) @@ -303,29 +413,29 @@ private WriteObjectDelegate GetCoreWriteFn() mapTypeArgs[0], mapTypeArgs[1]); var keyWriteFn = Serializer.GetWriteFn(mapTypeArgs[0]); - var valueWriteFn = typeof(T) == typeof(JsonObject) + var valueWriteFn = typeof(JsonObject).IsAssignableFrom(typeof(T)) ? JsonObject.WriteValue : Serializer.GetWriteFn(mapTypeArgs[1]); return (w, x) => writeFn(w, x, keyWriteFn, valueWriteFn); } + } - var enumerableInterface = typeof(T).GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); - if (enumerableInterface != null) - { - var elementType = enumerableInterface.GetGenericArguments()[0]; - var writeFn = WriteListsOfElements.GetGenericWriteEnumerable(elementType); - return writeFn; - } + var enumerableInterface = typeof(T).GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + if (enumerableInterface != null) + { + var elementType = enumerableInterface.GetGenericArguments()[0]; + var writeFn = WriteListsOfElements.GetGenericWriteEnumerable(elementType); + return writeFn; } - var isDictionary = typeof(T).IsAssignableFrom(typeof(IDictionary)) - || typeof(T).HasInterface(typeof(IDictionary)); + var isDictionary = typeof(T) != typeof(IEnumerable) && typeof(T) != typeof(ICollection) + && (typeof(T).IsAssignableFrom(typeof(IDictionary)) || typeof(T).HasInterface(typeof(IDictionary))); if (isDictionary) { return WriteDictionary.WriteIDictionary; } - + var isEnumerable = typeof(T).IsAssignableFrom(typeof(IEnumerable)) || typeof(T).HasInterface(typeof(IEnumerable)); if (isEnumerable) @@ -333,6 +443,9 @@ private WriteObjectDelegate GetCoreWriteFn() return WriteListsOfElements.WriteIEnumerable; } + if (typeof(T).HasInterface(typeof(IValueWriter))) + return WriteValue; + if (typeof(T).IsClass || typeof(T).IsInterface || JsConfig.TreatAsRefType(typeof(T))) { var typeToStringMethod = WriteType.Write; @@ -345,13 +458,11 @@ private WriteObjectDelegate GetCoreWriteFn() return Serializer.WriteBuiltIn; } - - public Dictionary SpecialTypes; + public readonly Dictionary SpecialTypes; public WriteObjectDelegate GetSpecialWriteFn(Type type) { - WriteObjectDelegate writeFn = null; - if (SpecialTypes.TryGetValue(type, out writeFn)) + if (SpecialTypes.TryGetValue(type, out var writeFn)) return writeFn; if (type.IsInstanceOfType(typeof(Type))) @@ -368,5 +479,21 @@ public void WriteType(TextWriter writer, object value) Serializer.WriteRawString(writer, JsConfig.TypeWriter((Type)value)); } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + WriteListsOfElements.WriteList(null, null); + WriteListsOfElements.WriteIList(null, null); + WriteListsOfElements.WriteEnumerable(null, null); + WriteListsOfElements.WriteListValueType(null, null); + WriteListsOfElements.WriteIListValueType(null, null); + WriteListsOfElements.WriteGenericArrayValueType(null, null); + WriteListsOfElements.WriteArray(null, null); + + TranslateListWithElements.LateBoundTranslateToGenericICollection(null, null); + TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, null); + + QueryStringWriter.WriteObject(null, null); + } } -} \ No newline at end of file +} diff --git a/src/ServiceStack.Text/Common/ParseUtils.cs b/src/ServiceStack.Text/Common/ParseUtils.cs index 16269ce6f..bdc4e7363 100644 --- a/src/ServiceStack.Text/Common/ParseUtils.cs +++ b/src/ServiceStack.Text/Common/ParseUtils.cs @@ -5,56 +5,72 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; -using System.Collections.Generic; -using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; namespace ServiceStack.Text.Common { - internal static class ParseUtils - { - public static object NullValueType(Type type) - { - return ReflectionExtensions.GetDefaultValue(type); - } - - public static object ParseObject(string value) - { - return value; - } - - public static object ParseEnum(Type type, string value) - { - return Enum.Parse(type, value, false); - } - - public static ParseStringDelegate GetSpecialParseMethod(Type type) - { - if (type == typeof(Uri)) - return x => new Uri(x.FromCsvField()); - - //Warning: typeof(object).IsInstanceOfType(typeof(Type)) == True?? - if (type.IsInstanceOfType(typeof(Type))) - return ParseType; - - if (type == typeof(Exception)) - return x => new Exception(x); - - if (type.IsInstanceOf(typeof(Exception))) - return DeserializeTypeUtils.GetParseMethod(type); - - return null; - } - - public static Type ParseType(string assemblyQualifiedName) - { - return AssemblyUtils.FindType(assemblyQualifiedName.FromCsvField()); - } - } + internal static class ParseUtils + { + public static object NullValueType(Type type) + { + return type.GetDefaultValue(); + } + + public static object ParseObject(string value) + { + return value; + } + + public static object ParseEnum(Type type, string value) + { + return Enum.Parse(type, value, false); + } + + public static ParseStringDelegate GetSpecialParseMethod(Type type) + { + if (type == typeof(Uri)) + return x => new Uri(x.FromCsvField()); + + //Warning: typeof(object).IsInstanceOfType(typeof(Type)) == True?? + if (type.IsInstanceOfType(typeof(Type))) + return ParseType; + + if (type == typeof(Exception)) + return x => new Exception(x); + + if (type.IsInstanceOf(typeof(Exception))) + return DeserializeTypeUtils.GetParseMethod(type); + + return null; + } + + public static Type ParseType(string assemblyQualifiedName) + { + return AssemblyUtils.FindType(assemblyQualifiedName.FromCsvField()); + } + + public static object TryParseEnum(Type enumType, string str) + { + if (str == null) + return null; + + if (JsConfig.TextCase == TextCase.SnakeCase) + { + string[] names = Enum.GetNames(enumType); + if (Array.IndexOf(names, str) == -1) // case sensitive ... could use Linq Contains() extension with StringComparer.InvariantCultureIgnoreCase instead for a slight penalty + str = str.Replace("_", ""); + } + + var enumInfo = CachedTypeInfo.Get(enumType).EnumInfo; + return enumInfo.Parse(str); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/StaticParseMethod.cs b/src/ServiceStack.Text/Common/StaticParseMethod.cs index da2f3c976..5b38bd009 100644 --- a/src/ServiceStack.Text/Common/StaticParseMethod.cs +++ b/src/ServiceStack.Text/Common/StaticParseMethod.cs @@ -5,106 +5,128 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; -using System.Reflection; using ServiceStack.Text.Jsv; namespace ServiceStack.Text.Common { - internal delegate object ParseDelegate(string value); - - public static class StaticParseMethod - { - const string ParseMethod = "Parse"; - - private static readonly ParseStringDelegate CacheFn; - - public static ParseStringDelegate Parse - { - get { return CacheFn; } - } - - static StaticParseMethod() - { - CacheFn = GetParseFn(); - } - - public static ParseStringDelegate GetParseFn() - { - // Get the static Parse(string) method on the type supplied - var parseMethodInfo = typeof(T).GetMethod( - ParseMethod, BindingFlags.Public | BindingFlags.Static, null, - new[] { typeof(string) }, null); - - if (parseMethodInfo == null) return null; - - ParseDelegate parseDelegate; - try - { - parseDelegate = (ParseDelegate)Delegate.CreateDelegate(typeof(ParseDelegate), parseMethodInfo); - } - catch (ArgumentException) - { - //Try wrapping strongly-typed return with wrapper fn. - var typedParseDelegate = (Func)Delegate.CreateDelegate(typeof(Func), parseMethodInfo); - parseDelegate = x => typedParseDelegate(x); - } - if (parseDelegate != null) - return value => parseDelegate(value.FromCsvField()); - - return null; - } - } - - internal static class StaticParseRefTypeMethod - where TSerializer : ITypeSerializer - { - static string ParseMethod = typeof(TSerializer) == typeof(JsvTypeSerializer) - ? "ParseJsv" - : "ParseJson"; - - private static readonly ParseStringDelegate CacheFn; - - public static ParseStringDelegate Parse - { - get { return CacheFn; } - } - - static StaticParseRefTypeMethod() - { - CacheFn = GetParseFn(); - } - - public static ParseStringDelegate GetParseFn() - { - // Get the static Parse(string) method on the type supplied - var parseMethodInfo = typeof(T).GetMethod( - ParseMethod, BindingFlags.Public | BindingFlags.Static, null, - new[] { typeof(string) }, null); - - if (parseMethodInfo == null) return null; - - ParseDelegate parseDelegate; - try - { - parseDelegate = (ParseDelegate)Delegate.CreateDelegate(typeof(ParseDelegate), parseMethodInfo); - } - catch (ArgumentException) - { - //Try wrapping strongly-typed return with wrapper fn. - var typedParseDelegate = (Func)Delegate.CreateDelegate(typeof(Func), parseMethodInfo); - parseDelegate = x => typedParseDelegate(x); - } - if (parseDelegate != null) - return value => parseDelegate(value); - - return null; - } - } + internal delegate object ParseDelegate(string value); + + internal static class ParseMethodUtilities + { + public static ParseStringDelegate GetParseFn(string parseMethod) + { + // Get the static Parse(string) method on the type supplied + var parseMethodInfo = typeof(T).GetStaticMethod(parseMethod, new[] { typeof(string) }); + if (parseMethodInfo == null) + return null; + + ParseDelegate parseDelegate = null; + try + { + if (parseMethodInfo.ReturnType != typeof(T)) + { + parseDelegate = (ParseDelegate)parseMethodInfo.MakeDelegate(typeof(ParseDelegate), false); + } + if (parseDelegate == null) + { + //Try wrapping strongly-typed return with wrapper fn. + var typedParseDelegate = (Func)parseMethodInfo.MakeDelegate(typeof(Func)); + parseDelegate = x => typedParseDelegate(x); + } + } + catch (ArgumentException) + { + Tracer.Instance.WriteDebug("Nonstandard Parse method on type {0}", typeof(T)); + } + + if (parseDelegate != null) + return value => parseDelegate(value.FromCsvField()); + + return null; + } + + delegate T ParseStringSpanGenericDelegate(ReadOnlySpan value); + + public static ParseStringSpanDelegate GetParseStringSpanFn(string parseMethod) + { + // Get the static Parse(string) method on the type supplied + var parseMethodInfo = typeof(T).GetStaticMethod(parseMethod, new[] { typeof(string) }); + if (parseMethodInfo == null) + return null; + + ParseStringSpanDelegate parseDelegate = null; + try + { + if (parseMethodInfo.ReturnType != typeof(T)) + { + parseDelegate = (ParseStringSpanDelegate)parseMethodInfo.MakeDelegate(typeof(ParseStringSpanDelegate), false); + } + if (parseDelegate == null) + { + //Try wrapping strongly-typed return with wrapper fn. + var typedParseDelegate = (ParseStringSpanGenericDelegate)parseMethodInfo.MakeDelegate(typeof(ParseStringSpanGenericDelegate)); + parseDelegate = x => typedParseDelegate(x); + } + } + catch (ArgumentException) + { + Tracer.Instance.WriteDebug("Nonstandard Parse method on type {0}", typeof(T)); + } + + if (parseDelegate != null) + return value => parseDelegate(value.ToString().FromCsvField().AsSpan()); + + return null; + } + } + + public static class StaticParseMethod + { + const string ParseMethod = "Parse"; + const string ParseStringSpanMethod = "ParseStringSpanMethod"; + + private static readonly ParseStringDelegate CacheFn; + private static readonly ParseStringSpanDelegate CacheStringSpanFn; + + public static ParseStringDelegate Parse => CacheFn; + public static ParseStringSpanDelegate ParseStringSpan => CacheStringSpanFn; + + static StaticParseMethod() + { + CacheFn = ParseMethodUtilities.GetParseFn(ParseMethod); + CacheStringSpanFn = ParseMethodUtilities.GetParseStringSpanFn(ParseMethod); + } + + } + + internal static class StaticParseRefTypeMethod + where TSerializer : ITypeSerializer + { + static readonly string ParseMethod = typeof(TSerializer) == typeof(JsvTypeSerializer) + ? "ParseJsv" + : "ParseJson"; + + static readonly string ParseStringSpanMethod = typeof(TSerializer) == typeof(JsvTypeSerializer) + ? "ParseStringSpanJsv" + : "ParseStringSpanJson"; + + private static readonly ParseStringDelegate CacheFn; + private static readonly ParseStringSpanDelegate CacheStringSpanFn; + + public static ParseStringDelegate Parse => CacheFn; + public static ParseStringSpanDelegate ParseStringSpan => CacheStringSpanFn; + + static StaticParseRefTypeMethod() + { + CacheFn = ParseMethodUtilities.GetParseFn(ParseMethod); + CacheStringSpanFn = ParseMethodUtilities.GetParseStringSpanFn(ParseStringSpanMethod); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/WriteDictionary.cs b/src/ServiceStack.Text/Common/WriteDictionary.cs index ee9384581..4041e801b 100644 --- a/src/ServiceStack.Text/Common/WriteDictionary.cs +++ b/src/ServiceStack.Text/Common/WriteDictionary.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; @@ -16,68 +16,68 @@ using System.IO; using System.Reflection; using System.Threading; +using System.Linq; using ServiceStack.Text.Json; namespace ServiceStack.Text.Common { - internal delegate void WriteMapDelegate( - TextWriter writer, - object oMap, - WriteObjectDelegate writeKeyFn, - WriteObjectDelegate writeValueFn); - - internal static class WriteDictionary - where TSerializer : ITypeSerializer - { - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - - internal class MapKey - { - internal Type KeyType; - internal Type ValueType; - - public MapKey(Type keyType, Type valueType) - { - KeyType = keyType; - ValueType = valueType; - } - - public bool Equals(MapKey other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other.KeyType, KeyType) && Equals(other.ValueType, ValueType); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(MapKey)) return false; - return Equals((MapKey)obj); - } - - public override int GetHashCode() - { - unchecked - { - return ((KeyType != null ? KeyType.GetHashCode() : 0) * 397) ^ (ValueType != null ? ValueType.GetHashCode() : 0); - } - } - } - - static Dictionary CacheFns = new Dictionary(); - - public static Action - GetWriteGenericDictionary(Type keyType, Type valueType) - { - WriteMapDelegate writeFn; + internal delegate void WriteMapDelegate( + TextWriter writer, + object oMap, + WriteObjectDelegate writeKeyFn, + WriteObjectDelegate writeValueFn); + + internal static class WriteDictionary + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + internal class MapKey + { + internal Type KeyType; + internal Type ValueType; + + public MapKey(Type keyType, Type valueType) + { + KeyType = keyType; + ValueType = valueType; + } + + public bool Equals(MapKey other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(other.KeyType, KeyType) && Equals(other.ValueType, ValueType); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(MapKey)) return false; + return Equals((MapKey)obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((KeyType != null ? KeyType.GetHashCode() : 0) * 397) ^ (ValueType != null ? ValueType.GetHashCode() : 0); + } + } + } + + static Dictionary CacheFns = new Dictionary(); + + public static Action + GetWriteGenericDictionary(Type keyType, Type valueType) + { var mapKey = new MapKey(keyType, valueType); - if (CacheFns.TryGetValue(mapKey, out writeFn)) return writeFn.Invoke; + if (CacheFns.TryGetValue(mapKey, out var writeFn)) return writeFn.Invoke; var genericType = typeof(ToStringDictionaryMethods<,,>).MakeGenericType(keyType, valueType, typeof(TSerializer)); - var mi = genericType.GetMethod("WriteIDictionary", BindingFlags.Static | BindingFlags.Public); - writeFn = (WriteMapDelegate)Delegate.CreateDelegate(typeof(WriteMapDelegate), mi); + var mi = genericType.GetStaticMethod("WriteIDictionary"); + writeFn = (WriteMapDelegate)mi.MakeDelegate(typeof(WriteMapDelegate)); Dictionary snapshot, newCache; do @@ -88,57 +88,67 @@ public static Action - where TSerializer : ITypeSerializer - { - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - - public static void WriteIDictionary( - TextWriter writer, - object oMap, - WriteObjectDelegate writeKeyFn, - WriteObjectDelegate writeValueFn) - { - if (writer == null) return; //AOT - WriteGenericIDictionary(writer, (IDictionary)oMap, writeKeyFn, writeValueFn); - } - - public static void WriteGenericIDictionary( - TextWriter writer, - IDictionary map, - WriteObjectDelegate writeKeyFn, - WriteObjectDelegate writeValueFn) - { - if (map == null) - { - writer.Write(JsonUtils.Null); + } + + writer.Write(JsWriter.MapEndChar); + } + } + + public static class ToStringDictionaryMethods + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + public static void WriteIDictionary( + TextWriter writer, + object oMap, + WriteObjectDelegate writeKeyFn, + WriteObjectDelegate writeValueFn) + { + if (writer == null) return; //AOT + WriteGenericIDictionary(writer, (IDictionary)oMap, writeKeyFn, writeValueFn); + } + + public static void WriteGenericIDictionary( + TextWriter writer, + IDictionary map, + WriteObjectDelegate writeKeyFn, + WriteObjectDelegate writeValueFn) + { + if (map == null) + { + writer.Write(JsonUtils.Null); return; - } - writer.Write(JsWriter.MapStartChar); + } - var encodeMapKey = Serializer.GetTypeInfo(typeof(TKey)).EncodeMapKey; + if (map is JsonObject jsonObject) + map = (IDictionary) jsonObject.ToUnescapedDictionary(); + + writer.Write(JsWriter.MapStartChar); - var ranOnce = false; - foreach (var kvp in map) - { - var isNull = (kvp.Value == null); - if (isNull && !Serializer.IncludeNullValues) continue; + var encodeMapKey = Serializer.GetTypeInfo(typeof(TKey)).EncodeMapKey; - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + var ranOnce = false; + foreach (var kvp in map) + { + var isNull = (kvp.Value == null); + if (isNull && !Serializer.IncludeNullValuesInDictionaries) continue; - JsState.WritingKeyCount++; - JsState.IsWritingValue = false; + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - if (encodeMapKey) - { - JsState.IsWritingValue = true; //prevent ""null"" - writer.Write(JsWriter.QuoteChar); - writeKeyFn(writer, kvp.Key); - writer.Write(JsWriter.QuoteChar); - } - else - { - writeKeyFn(writer, kvp.Key); - } - - JsState.WritingKeyCount--; + JsState.WritingKeyCount++; + try + { + if (encodeMapKey) + { + JsState.IsWritingValue = true; //prevent ""null"" + try + { + writer.Write(JsWriter.QuoteChar); + writeKeyFn(writer, kvp.Key); + writer.Write(JsWriter.QuoteChar); + } + finally + { + JsState.IsWritingValue = false; + } + } + else + { + writeKeyFn(writer, kvp.Key); + } + } + finally + { + JsState.WritingKeyCount--; + } - writer.Write(JsWriter.MapKeySeperator); + writer.Write(JsWriter.MapKeySeperator); if (isNull) { @@ -220,12 +257,18 @@ public static void WriteGenericIDictionary( else { JsState.IsWritingValue = true; - writeValueFn(writer, kvp.Value); - JsState.IsWritingValue = false; + try + { + writeValueFn(writer, kvp.Value); + } + finally + { + JsState.IsWritingValue = false; + } } - } + } - writer.Write(JsWriter.MapEndChar); - } - } + writer.Write(JsWriter.MapEndChar); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/WriteLists.cs b/src/ServiceStack.Text/Common/WriteLists.cs index f08b906d5..cc75def01 100644 --- a/src/ServiceStack.Text/Common/WriteLists.cs +++ b/src/ServiceStack.Text/Common/WriteLists.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; @@ -16,24 +16,25 @@ using System.IO; using System.Reflection; using System.Threading; +using System.Linq; namespace ServiceStack.Text.Common { - internal static class WriteListsOfElements - where TSerializer : ITypeSerializer - { - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + public static class WriteListsOfElements + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - static Dictionary ListCacheFns = new Dictionary(); + static Dictionary ListCacheFns = new Dictionary(); - public static WriteObjectDelegate GetListWriteFn(Type elementType) - { - WriteObjectDelegate writeFn; + public static WriteObjectDelegate GetListWriteFn(Type elementType) + { + WriteObjectDelegate writeFn; if (ListCacheFns.TryGetValue(elementType, out writeFn)) return writeFn; var genericType = typeof(WriteListsOfElements<,>).MakeGenericType(elementType, typeof(TSerializer)); - var mi = genericType.GetMethod("WriteList", BindingFlags.Static | BindingFlags.Public); - writeFn = (WriteObjectDelegate)Delegate.CreateDelegate(typeof(WriteObjectDelegate), mi); + var mi = genericType.GetStaticMethod("WriteList"); + writeFn = (WriteObjectDelegate)mi.MakeDelegate(typeof(WriteObjectDelegate)); Dictionary snapshot, newCache; do @@ -44,21 +45,21 @@ public static WriteObjectDelegate GetListWriteFn(Type elementType) } while (!ReferenceEquals( Interlocked.CompareExchange(ref ListCacheFns, newCache, snapshot), snapshot)); - + return writeFn; - } + } - static Dictionary IListCacheFns = new Dictionary(); + static Dictionary IListCacheFns = new Dictionary(); - public static WriteObjectDelegate GetIListWriteFn(Type elementType) - { - WriteObjectDelegate writeFn; + public static WriteObjectDelegate GetIListWriteFn(Type elementType) + { + WriteObjectDelegate writeFn; if (IListCacheFns.TryGetValue(elementType, out writeFn)) return writeFn; var genericType = typeof(WriteListsOfElements<,>).MakeGenericType(elementType, typeof(TSerializer)); - var mi = genericType.GetMethod("WriteIList", BindingFlags.Static | BindingFlags.Public); - writeFn = (WriteObjectDelegate)Delegate.CreateDelegate(typeof(WriteObjectDelegate), mi); + var mi = genericType.GetStaticMethod("WriteIList"); + writeFn = (WriteObjectDelegate)mi.MakeDelegate(typeof(WriteObjectDelegate)); Dictionary snapshot, newCache; do @@ -69,20 +70,20 @@ public static WriteObjectDelegate GetIListWriteFn(Type elementType) } while (!ReferenceEquals( Interlocked.CompareExchange(ref IListCacheFns, newCache, snapshot), snapshot)); - + return writeFn; - } + } - static Dictionary CacheFns = new Dictionary(); + static Dictionary CacheFns = new Dictionary(); - public static WriteObjectDelegate GetGenericWriteArray(Type elementType) - { - WriteObjectDelegate writeFn; + public static WriteObjectDelegate GetGenericWriteArray(Type elementType) + { + WriteObjectDelegate writeFn; if (CacheFns.TryGetValue(elementType, out writeFn)) return writeFn; var genericType = typeof(WriteListsOfElements<,>).MakeGenericType(elementType, typeof(TSerializer)); - var mi = genericType.GetMethod("WriteArray", BindingFlags.Static | BindingFlags.Public); - writeFn = (WriteObjectDelegate)Delegate.CreateDelegate(typeof(WriteObjectDelegate), mi); + var mi = genericType.GetStaticMethod("WriteArray"); + writeFn = (WriteObjectDelegate)mi.MakeDelegate(typeof(WriteObjectDelegate)); Dictionary snapshot, newCache; do @@ -94,19 +95,19 @@ public static WriteObjectDelegate GetGenericWriteArray(Type elementType) } while (!ReferenceEquals( Interlocked.CompareExchange(ref CacheFns, newCache, snapshot), snapshot)); - return writeFn; - } + return writeFn; + } - static Dictionary EnumerableCacheFns = new Dictionary(); + static Dictionary EnumerableCacheFns = new Dictionary(); - public static WriteObjectDelegate GetGenericWriteEnumerable(Type elementType) - { - WriteObjectDelegate writeFn; + public static WriteObjectDelegate GetGenericWriteEnumerable(Type elementType) + { + WriteObjectDelegate writeFn; if (EnumerableCacheFns.TryGetValue(elementType, out writeFn)) return writeFn; var genericType = typeof(WriteListsOfElements<,>).MakeGenericType(elementType, typeof(TSerializer)); - var mi = genericType.GetMethod("WriteEnumerable", BindingFlags.Static | BindingFlags.Public); - writeFn = (WriteObjectDelegate)Delegate.CreateDelegate(typeof(WriteObjectDelegate), mi); + var mi = genericType.GetStaticMethod("WriteEnumerable"); + writeFn = (WriteObjectDelegate)mi.MakeDelegate(typeof(WriteObjectDelegate)); Dictionary snapshot, newCache; do @@ -118,19 +119,19 @@ public static WriteObjectDelegate GetGenericWriteEnumerable(Type elementType) } while (!ReferenceEquals( Interlocked.CompareExchange(ref EnumerableCacheFns, newCache, snapshot), snapshot)); - return writeFn; - } + return writeFn; + } - static Dictionary ListValueTypeCacheFns = new Dictionary(); + static Dictionary ListValueTypeCacheFns = new Dictionary(); - public static WriteObjectDelegate GetWriteListValueType(Type elementType) - { - WriteObjectDelegate writeFn; + public static WriteObjectDelegate GetWriteListValueType(Type elementType) + { + WriteObjectDelegate writeFn; if (ListValueTypeCacheFns.TryGetValue(elementType, out writeFn)) return writeFn; var genericType = typeof(WriteListsOfElements<,>).MakeGenericType(elementType, typeof(TSerializer)); - var mi = genericType.GetMethod("WriteListValueType", BindingFlags.Static | BindingFlags.Public); - writeFn = (WriteObjectDelegate)Delegate.CreateDelegate(typeof(WriteObjectDelegate), mi); + var mi = genericType.GetStaticMethod("WriteListValueType"); + writeFn = (WriteObjectDelegate)mi.MakeDelegate(typeof(WriteObjectDelegate)); Dictionary snapshot, newCache; do @@ -142,20 +143,20 @@ public static WriteObjectDelegate GetWriteListValueType(Type elementType) } while (!ReferenceEquals( Interlocked.CompareExchange(ref ListValueTypeCacheFns, newCache, snapshot), snapshot)); - return writeFn; - } + return writeFn; + } - static Dictionary IListValueTypeCacheFns = new Dictionary(); + static Dictionary IListValueTypeCacheFns = new Dictionary(); - public static WriteObjectDelegate GetWriteIListValueType(Type elementType) - { - WriteObjectDelegate writeFn; + public static WriteObjectDelegate GetWriteIListValueType(Type elementType) + { + WriteObjectDelegate writeFn; if (IListValueTypeCacheFns.TryGetValue(elementType, out writeFn)) return writeFn; var genericType = typeof(WriteListsOfElements<,>).MakeGenericType(elementType, typeof(TSerializer)); - var mi = genericType.GetMethod("WriteIListValueType", BindingFlags.Static | BindingFlags.Public); - writeFn = (WriteObjectDelegate)Delegate.CreateDelegate(typeof(WriteObjectDelegate), mi); + var mi = genericType.GetStaticMethod("WriteIListValueType"); + writeFn = (WriteObjectDelegate)mi.MakeDelegate(typeof(WriteObjectDelegate)); Dictionary snapshot, newCache; do @@ -167,160 +168,189 @@ public static WriteObjectDelegate GetWriteIListValueType(Type elementType) } while (!ReferenceEquals( Interlocked.CompareExchange(ref IListValueTypeCacheFns, newCache, snapshot), snapshot)); - return writeFn; - } - - public static void WriteIEnumerable(TextWriter writer, object oValueCollection) - { - WriteObjectDelegate toStringFn = null; - - writer.Write(JsWriter.ListStartChar); - - var valueCollection = (IEnumerable)oValueCollection; - var ranOnce = false; - foreach (var valueItem in valueCollection) - { - if (toStringFn == null) - toStringFn = Serializer.GetWriteFn(valueItem.GetType()); - - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - - toStringFn(writer, valueItem); - } - - writer.Write(JsWriter.ListEndChar); - } - } - - internal static class WriteListsOfElements - where TSerializer : ITypeSerializer - { - private static readonly WriteObjectDelegate ElementWriteFn; - - static WriteListsOfElements() - { - ElementWriteFn = JsWriter.GetTypeSerializer().GetWriteFn(); - } - - public static void WriteList(TextWriter writer, object oList) - { - WriteGenericIList(writer, (IList)oList); - } - - public static void WriteGenericList(TextWriter writer, List list) - { - writer.Write(JsWriter.ListStartChar); - - var ranOnce = false; - var listLength = list.Count; - for (var i = 0; i < listLength; i++) - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - ElementWriteFn(writer, list[i]); - } - - writer.Write(JsWriter.ListEndChar); - } - - public static void WriteListValueType(TextWriter writer, object list) - { - WriteGenericListValueType(writer, (List)list); - } - - public static void WriteGenericListValueType(TextWriter writer, List list) - { - if (list == null) return; //AOT - - writer.Write(JsWriter.ListStartChar); - - var ranOnce = false; - var listLength = list.Count; - for (var i = 0; i < listLength; i++) - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - ElementWriteFn(writer, list[i]); - } - - writer.Write(JsWriter.ListEndChar); - } - - public static void WriteIList(TextWriter writer, object oList) - { - WriteGenericIList(writer, (IList)oList); - } - - public static void WriteGenericIList(TextWriter writer, IList list) - { - if (list == null) return; - writer.Write(JsWriter.ListStartChar); - - var ranOnce = false; - var listLength = list.Count; - try - { - for (var i = 0; i < listLength; i++) - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - ElementWriteFn(writer, list[i]); - } - - } - catch (Exception ex) - { - Tracer.Instance.WriteError(ex); - throw; - } - writer.Write(JsWriter.ListEndChar); - } - - public static void WriteIListValueType(TextWriter writer, object list) - { - WriteGenericIListValueType(writer, (IList)list); - } - - public static void WriteGenericIListValueType(TextWriter writer, IList list) - { - if (list == null) return; //AOT - - writer.Write(JsWriter.ListStartChar); - - var ranOnce = false; - var listLength = list.Count; - for (var i = 0; i < listLength; i++) - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - ElementWriteFn(writer, list[i]); - } - - writer.Write(JsWriter.ListEndChar); - } - - public static void WriteArray(TextWriter writer, object oArrayValue) - { - if (oArrayValue == null) return; - WriteGenericArray(writer, (Array)oArrayValue); - } - - public static void WriteGenericArrayValueType (TextWriter writer, object oArray) - { - WriteGenericArrayValueType(writer, (T[])oArray); - } - - public static void WriteGenericArrayValueType(TextWriter writer, T[] array) - { - if (array == null) return; - writer.Write(JsWriter.ListStartChar); - - var ranOnce = false; - var arrayLength = array.Length; - for (var i = 0; i < arrayLength; i++) - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - ElementWriteFn(writer, array[i]); - } - - writer.Write(JsWriter.ListEndChar); - } + return writeFn; + } + + public static void WriteIEnumerable(TextWriter writer, object oValueCollection) + { + WriteObjectDelegate toStringFn = null; + + writer.Write(JsWriter.ListStartChar); + + var valueCollection = (IEnumerable)oValueCollection; + var ranOnce = false; + Type lastType = null; + foreach (var valueItem in valueCollection) + { + if ((toStringFn == null) || (valueItem != null && valueItem.GetType() != lastType)) + { + if (valueItem != null) + { + if (valueItem.GetType() != lastType) + { + lastType = valueItem.GetType(); + toStringFn = Serializer.GetWriteFn(lastType); + } + } + else + { + // this can happen if the first item in the collection was null + lastType = typeof(object); + toStringFn = Serializer.GetWriteFn(lastType); + } + } + + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + + toStringFn(writer, valueItem); + } + + writer.Write(JsWriter.ListEndChar); + } + } + + public static class WriteListsOfElements + where TSerializer : ITypeSerializer + { + private static readonly WriteObjectDelegate ElementWriteFn; + + static WriteListsOfElements() + { + var fn = JsWriter.GetTypeSerializer().GetWriteFn(); + ElementWriteFn = (writer, obj) => { + try + { + if (!JsState.Traverse(obj)) + return; + + fn(writer, obj); + } + finally + { + JsState.UnTraverse(); + } + }; + } + + public static void WriteList(TextWriter writer, object oList) + { + WriteGenericIList(writer, (IList)oList); + } + + public static void WriteGenericList(TextWriter writer, List list) + { + writer.Write(JsWriter.ListStartChar); + + var ranOnce = false; + var listLength = list.Count; + for (var i = 0; i < listLength; i++) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + ElementWriteFn(writer, list[i]); + } + + writer.Write(JsWriter.ListEndChar); + } + + public static void WriteListValueType(TextWriter writer, object list) + { + WriteGenericListValueType(writer, (List)list); + } + + public static void WriteGenericListValueType(TextWriter writer, List list) + { + if (list == null) return; //AOT + + writer.Write(JsWriter.ListStartChar); + + var ranOnce = false; + var listLength = list.Count; + for (var i = 0; i < listLength; i++) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + ElementWriteFn(writer, list[i]); + } + + writer.Write(JsWriter.ListEndChar); + } + + public static void WriteIList(TextWriter writer, object oList) + { + WriteGenericIList(writer, (IList)oList); + } + + public static void WriteGenericIList(TextWriter writer, IList list) + { + if (list == null) return; + writer.Write(JsWriter.ListStartChar); + + var ranOnce = false; + var listLength = list.Count; + try + { + for (var i = 0; i < listLength; i++) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + ElementWriteFn(writer, list[i]); + } + + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + throw; + } + writer.Write(JsWriter.ListEndChar); + } + + public static void WriteIListValueType(TextWriter writer, object list) + { + WriteGenericIListValueType(writer, (IList)list); + } + + public static void WriteGenericIListValueType(TextWriter writer, IList list) + { + if (list == null) return; //AOT + + writer.Write(JsWriter.ListStartChar); + + var ranOnce = false; + var listLength = list.Count; + for (var i = 0; i < listLength; i++) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + ElementWriteFn(writer, list[i]); + } + + writer.Write(JsWriter.ListEndChar); + } + + public static void WriteArray(TextWriter writer, object oArrayValue) + { + if (oArrayValue == null) return; + WriteGenericArray(writer, (Array)oArrayValue); + } + + public static void WriteGenericArrayValueType(TextWriter writer, object oArray) + { + WriteGenericArrayValueType(writer, (T[])oArray); + } + + public static void WriteGenericArrayValueType(TextWriter writer, T[] array) + { + if (array == null) return; + writer.Write(JsWriter.ListStartChar); + + var ranOnce = false; + var arrayLength = array.Length; + for (var i = 0; i < arrayLength; i++) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + ElementWriteFn(writer, array[i]); + } + + writer.Write(JsWriter.ListEndChar); + } private static void WriteGenericArrayMultiDimension(TextWriter writer, Array array, int rank, int[] indices) { @@ -343,163 +373,163 @@ public static void WriteGenericArray(TextWriter writer, Array array) { WriteGenericArrayMultiDimension(writer, array, 0, new int[array.Rank]); } - public static void WriteEnumerable(TextWriter writer, object oEnumerable) - { - WriteGenericEnumerable(writer, (IEnumerable)oEnumerable); - } - - public static void WriteGenericEnumerable(TextWriter writer, IEnumerable enumerable) - { - if (enumerable == null) return; - writer.Write(JsWriter.ListStartChar); - - var ranOnce = false; - foreach (var value in enumerable) - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - ElementWriteFn(writer, value); - } - - writer.Write(JsWriter.ListEndChar); - } - - public static void WriteGenericEnumerableValueType(TextWriter writer, IEnumerable enumerable) - { - writer.Write(JsWriter.ListStartChar); - - var ranOnce = false; - foreach (var value in enumerable) - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - ElementWriteFn(writer, value); - } - - writer.Write(JsWriter.ListEndChar); - } - } - - internal static class WriteLists - { - public static void WriteListString(ITypeSerializer serializer, TextWriter writer, object list) - { - WriteListString(serializer, writer, (List)list); - } - - public static void WriteListString(ITypeSerializer serializer, TextWriter writer, List list) - { - writer.Write(JsWriter.ListStartChar); - - var ranOnce = false; - list.ForEach(x => - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - serializer.WriteString(writer, x); - }); - - writer.Write(JsWriter.ListEndChar); - } - - public static void WriteIListString(ITypeSerializer serializer, TextWriter writer, object list) - { - WriteIListString(serializer, writer, (IList)list); - } - - public static void WriteIListString(ITypeSerializer serializer, TextWriter writer, IList list) - { - writer.Write(JsWriter.ListStartChar); - - var ranOnce = false; - var listLength = list.Count; - for (var i = 0; i < listLength; i++) - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - serializer.WriteString(writer, list[i]); - } - - writer.Write(JsWriter.ListEndChar); - } - - public static void WriteBytes(ITypeSerializer serializer, TextWriter writer, object byteValue) - { - if (byteValue == null) return; - serializer.WriteBytes(writer, byteValue); - } - - public static void WriteStringArray(ITypeSerializer serializer, TextWriter writer, object oList) - { - writer.Write(JsWriter.ListStartChar); - - var list = (string[])oList; - var ranOnce = false; - var listLength = list.Length; - for (var i = 0; i < listLength; i++) - { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - serializer.WriteString(writer, list[i]); - } - - writer.Write(JsWriter.ListEndChar); - } - } - - internal static class WriteLists - where TSerializer : ITypeSerializer - { - private static readonly WriteObjectDelegate CacheFn; - private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - - static WriteLists() - { - CacheFn = GetWriteFn(); - } - - public static WriteObjectDelegate Write - { - get { return CacheFn; } - } - - public static WriteObjectDelegate GetWriteFn() - { - var type = typeof(T); - - var listInterface = type.GetTypeWithGenericTypeDefinitionOf(typeof(IList<>)); - if (listInterface == null) - throw new ArgumentException(string.Format("Type {0} is not of type IList<>", type.FullName)); - - //optimized access for regularly used types - if (type == typeof(List)) - return (w, x) => WriteLists.WriteListString(Serializer, w, x); - if (type == typeof(IList)) - return (w, x) => WriteLists.WriteIListString(Serializer, w, x); - - if (type == typeof(List)) - return WriteListsOfElements.WriteListValueType; - if (type == typeof(IList)) - return WriteListsOfElements.WriteIListValueType; - - if (type == typeof(List)) - return WriteListsOfElements.WriteListValueType; - if (type == typeof(IList)) - return WriteListsOfElements.WriteIListValueType; - - var elementType = listInterface.GetGenericArguments()[0]; - - var isGenericList = typeof(T).IsGenericType - && typeof(T).GetGenericTypeDefinition() == typeof(List<>); - - if (elementType.IsValueType - && JsWriter.ShouldUseDefaultToStringMethod(elementType)) - { - if (isGenericList) - return WriteListsOfElements.GetWriteListValueType(elementType); - - return WriteListsOfElements.GetWriteIListValueType(elementType); - } - - return isGenericList - ? WriteListsOfElements.GetListWriteFn(elementType) - : WriteListsOfElements.GetIListWriteFn(elementType); - } - - } + public static void WriteEnumerable(TextWriter writer, object oEnumerable) + { + WriteGenericEnumerable(writer, (IEnumerable)oEnumerable); + } + + public static void WriteGenericEnumerable(TextWriter writer, IEnumerable enumerable) + { + if (enumerable == null) return; + writer.Write(JsWriter.ListStartChar); + + var ranOnce = false; + foreach (var value in enumerable) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + ElementWriteFn(writer, value); + } + + writer.Write(JsWriter.ListEndChar); + } + + public static void WriteGenericEnumerableValueType(TextWriter writer, IEnumerable enumerable) + { + writer.Write(JsWriter.ListStartChar); + + var ranOnce = false; + foreach (var value in enumerable) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + ElementWriteFn(writer, value); + } + + writer.Write(JsWriter.ListEndChar); + } + } + + internal static class WriteLists + { + public static void WriteListString(ITypeSerializer serializer, TextWriter writer, object list) + { + WriteListString(serializer, writer, (List)list); + } + + public static void WriteListString(ITypeSerializer serializer, TextWriter writer, List list) + { + writer.Write(JsWriter.ListStartChar); + + var ranOnce = false; + foreach (var x in list) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + serializer.WriteString(writer, x); + } + + writer.Write(JsWriter.ListEndChar); + } + + public static void WriteIListString(ITypeSerializer serializer, TextWriter writer, object list) + { + WriteIListString(serializer, writer, (IList)list); + } + + public static void WriteIListString(ITypeSerializer serializer, TextWriter writer, IList list) + { + writer.Write(JsWriter.ListStartChar); + + var ranOnce = false; + var listLength = list.Count; + for (var i = 0; i < listLength; i++) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + serializer.WriteString(writer, list[i]); + } + + writer.Write(JsWriter.ListEndChar); + } + + public static void WriteBytes(ITypeSerializer serializer, TextWriter writer, object byteValue) + { + if (byteValue == null) return; + serializer.WriteBytes(writer, byteValue); + } + + public static void WriteStringArray(ITypeSerializer serializer, TextWriter writer, object oList) + { + writer.Write(JsWriter.ListStartChar); + + var list = (string[])oList; + var ranOnce = false; + var listLength = list.Length; + for (var i = 0; i < listLength; i++) + { + JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + serializer.WriteString(writer, list[i]); + } + + writer.Write(JsWriter.ListEndChar); + } + } + + internal static class WriteLists + where TSerializer : ITypeSerializer + { + private static readonly WriteObjectDelegate CacheFn; + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + static WriteLists() + { + CacheFn = GetWriteFn(); + } + + public static WriteObjectDelegate Write + { + get { return CacheFn; } + } + + public static WriteObjectDelegate GetWriteFn() + { + var type = typeof(T); + + var listInterface = type.GetTypeWithGenericTypeDefinitionOf(typeof(IList<>)); + if (listInterface == null) + throw new ArgumentException(string.Format("Type {0} is not of type IList<>", type.FullName)); + + //optimized access for regularly used types + if (type == typeof(List)) + return (w, x) => WriteLists.WriteListString(Serializer, w, x); + if (type == typeof(IList)) + return (w, x) => WriteLists.WriteIListString(Serializer, w, x); + + if (type == typeof(List)) + return WriteListsOfElements.WriteListValueType; + if (type == typeof(IList)) + return WriteListsOfElements.WriteIListValueType; + + if (type == typeof(List)) + return WriteListsOfElements.WriteListValueType; + if (type == typeof(IList)) + return WriteListsOfElements.WriteIListValueType; + + var elementType = listInterface.GetGenericArguments()[0]; + + var isGenericList = typeof(T).IsGenericType + && typeof(T).GetGenericTypeDefinition() == typeof(List<>); + + if (elementType.IsValueType + && JsWriter.ShouldUseDefaultToStringMethod(elementType)) + { + if (isGenericList) + return WriteListsOfElements.GetWriteListValueType(elementType); + + return WriteListsOfElements.GetWriteIListValueType(elementType); + } + + return isGenericList + ? WriteListsOfElements.GetListWriteFn(elementType) + : WriteListsOfElements.GetIListWriteFn(elementType); + } + + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/WriteType.cs b/src/ServiceStack.Text/Common/WriteType.cs index 50bb3ed2c..192c45343 100644 --- a/src/ServiceStack.Text/Common/WriteType.cs +++ b/src/ServiceStack.Text/Common/WriteType.cs @@ -5,15 +5,19 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; +using System.Collections; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Reflection; using ServiceStack.Text.Json; -using ServiceStack.Text.Reflection; +using ServiceStack.Text.Jsv; namespace ServiceStack.Text.Common { @@ -26,14 +30,11 @@ internal static class WriteType internal static TypePropertyWriter[] PropertyWriters; private static readonly WriteObjectDelegate WriteTypeInfo; - private static bool IsIncluded - { - get { return (JsConfig.IncludeTypeInfo || JsConfig.IncludeTypeInfo); } - } - private static bool IsExcluded - { - get { return (JsConfig.ExcludeTypeInfo || JsConfig.ExcludeTypeInfo); } - } + private static bool IsIncluded => + JsConfig.IncludeTypeInfo.GetValueOrDefault(JsConfig.IncludeTypeInfo); + + private static bool IsExcluded => + JsConfig.ExcludeTypeInfo.GetValueOrDefault(JsConfig.ExcludeTypeInfo); static WriteType() { @@ -50,6 +51,7 @@ static WriteType() { WriteTypeInfo = TypeInfoWriter; } + if (typeof(T).IsAbstract) { WriteTypeInfo = TypeInfoWriter; @@ -71,9 +73,11 @@ private static bool TryWriteSelfType(TextWriter writer) { if (ShouldSkipType()) return false; - Serializer.WriteRawString(writer, JsConfig.TypeAttr); + var config = JsConfig.GetConfig(); + + Serializer.WriteRawString(writer, config.TypeAttr); writer.Write(JsWriter.MapKeySeperator); - Serializer.WriteRawString(writer, JsConfig.TypeWriter(typeof(T))); + Serializer.WriteRawString(writer, config.TypeWriter(typeof(T))); return true; } @@ -81,35 +85,54 @@ private static bool TryWriteTypeInfo(TextWriter writer, object obj) { if (obj == null || ShouldSkipType()) return false; - Serializer.WriteRawString(writer, JsConfig.TypeAttr); + var config = JsConfig.GetConfig(); + + Serializer.WriteRawString(writer, config.TypeAttr); writer.Write(JsWriter.MapKeySeperator); - Serializer.WriteRawString(writer, JsConfig.TypeWriter(obj.GetType())); + Serializer.WriteRawString(writer, config.TypeWriter(obj.GetType())); return true; } - public static WriteObjectDelegate Write - { - get { return CacheFn; } - } + public static WriteObjectDelegate Write => CacheFn; private static WriteObjectDelegate GetWriteFn() { return WriteProperties; } + static Func GetShouldSerializeMethod(MemberInfo member) + { + var method = member.DeclaringType.GetInstanceMethod("ShouldSerialize" + member.Name); + return method == null || method.ReturnType != typeof(bool) + ? null + : (Func)method.CreateDelegate(typeof(Func)); + } + + static Func ShouldSerialize(Type type) + { + var method = type.GetMethodInfo("ShouldSerialize"); + return method == null || method.ReturnType != typeof(bool?) + ? null + : (Func)method.CreateDelegate(typeof(Func)); + } + private static bool Init() { if (!typeof(T).IsClass && !typeof(T).IsInterface && !JsConfig.TreatAsRefType(typeof(T))) return false; var propertyInfos = TypeConfig.Properties; + var fieldInfos = TypeConfig.Fields; var propertyNamesLength = propertyInfos.Length; - PropertyWriters = new TypePropertyWriter[propertyNamesLength]; + var fieldNamesLength = fieldInfos.Length; + PropertyWriters = new TypePropertyWriter[propertyNamesLength + fieldNamesLength]; - if (propertyNamesLength == 0 && !JsState.IsWritingDynamic) + if (propertyNamesLength + fieldNamesLength == 0 && !JsState.IsWritingDynamic) { return typeof(T).IsDto(); } + var shouldSerializeDynamic = ShouldSerialize(typeof(T)); + // NOTE: very limited support for DataContractSerialization (DCS) // NOT supporting Serializable // support for DCS is intended for (re)Name of properties and Ignore by NOT having a DataMember present @@ -118,8 +141,18 @@ private static bool Init() { var propertyInfo = propertyInfos[i]; - string propertyName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore; - + string propertyName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore, propertyDeclaredTypeName; + int propertyOrder = -1; + var propertyType = propertyInfo.PropertyType; + var defaultValue = propertyType.GetDefaultValue(); + bool propertySuppressDefaultConfig = defaultValue != null + && propertyType.IsValueType + && !propertyType.IsEnum + && JsConfig.HasSerializeFn.Contains(propertyType) + && !JsConfig.HasIncludeDefaultValue.Contains(propertyType); + bool propertySuppressDefaultAttribute = false; + + var shouldSerialize = GetShouldSerializeMethod(propertyInfo); if (isDataContract) { var dcsDataMember = propertyInfo.GetDataMember(); @@ -128,62 +161,173 @@ private static bool Init() propertyName = dcsDataMember.Name ?? propertyInfo.Name; propertyNameCLSFriendly = dcsDataMember.Name ?? propertyName.ToCamelCase(); propertyNameLowercaseUnderscore = dcsDataMember.Name ?? propertyName.ToLowercaseUnderscore(); + propertyDeclaredTypeName = propertyType.GetDeclaringTypeName(); + propertyOrder = dcsDataMember.Order; + propertySuppressDefaultAttribute = !dcsDataMember.EmitDefaultValue; } else { - propertyName = propertyInfo.Name; - propertyNameCLSFriendly = propertyName.ToCamelCase(); - propertyNameLowercaseUnderscore = propertyName.ToLowercaseUnderscore(); + var dcsDataMember = propertyInfo.GetDataMember(); + var alias = dcsDataMember?.Name; + + propertyName = alias ?? propertyInfo.Name; + propertyNameCLSFriendly = alias ?? propertyName.ToCamelCase(); + propertyNameLowercaseUnderscore = alias ?? propertyName.ToLowercaseUnderscore(); + propertyDeclaredTypeName = propertyInfo.GetDeclaringTypeName(); } - var propertyType = propertyInfo.PropertyType; - var suppressDefaultValue = propertyType.IsValueType && JsConfig.HasSerializeFn.Contains(propertyType) - ? propertyType.GetDefaultValue() - : null; PropertyWriters[i] = new TypePropertyWriter ( + propertyType, propertyName, + propertyDeclaredTypeName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore, - propertyInfo.GetValueGetter(), + propertyOrder, + propertySuppressDefaultConfig, + propertySuppressDefaultAttribute, + propertyInfo.CreateGetter(), Serializer.GetWriteFn(propertyType), - suppressDefaultValue + propertyType.GetDefaultValue(), + shouldSerialize, + shouldSerializeDynamic, + propertyType.IsEnum ); } + for (var i = 0; i < fieldNamesLength; i++) + { + var fieldInfo = fieldInfos[i]; + + string propertyName, propertyNameCLSFriendly, propertyNameLowercaseUnderscore, propertyDeclaredTypeName; + int propertyOrder = -1; + var propertyType = fieldInfo.FieldType; + var defaultValue = propertyType.GetDefaultValue(); + bool propertySuppressDefaultConfig = defaultValue != null + && propertyType.IsValueType && !propertyType.IsEnum + && JsConfig.HasSerializeFn.Contains(propertyType) + && !JsConfig.HasIncludeDefaultValue.Contains(propertyType); + bool propertySuppressDefaultAttribute = false; + + var shouldSerialize = GetShouldSerializeMethod(fieldInfo); + if (isDataContract) + { + var dcsDataMember = fieldInfo.GetDataMember(); + if (dcsDataMember == null) continue; + + propertyName = dcsDataMember.Name ?? fieldInfo.Name; + propertyNameCLSFriendly = dcsDataMember.Name ?? propertyName.ToCamelCase(); + propertyNameLowercaseUnderscore = dcsDataMember.Name ?? propertyName.ToLowercaseUnderscore(); + propertyDeclaredTypeName = fieldInfo.DeclaringType.Name; + propertyOrder = dcsDataMember.Order; + propertySuppressDefaultAttribute = !dcsDataMember.EmitDefaultValue; + } + else + { + var dcsDataMember = fieldInfo.GetDataMember(); + var alias = dcsDataMember?.Name; + propertyName = alias ?? fieldInfo.Name; + propertyNameCLSFriendly = alias ?? propertyName.ToCamelCase(); + propertyNameLowercaseUnderscore = alias ?? propertyName.ToLowercaseUnderscore(); + propertyDeclaredTypeName = fieldInfo.DeclaringType.Name; + } + + PropertyWriters[i + propertyNamesLength] = new TypePropertyWriter + ( + propertyType, + propertyName, + propertyDeclaredTypeName, + propertyNameCLSFriendly, + propertyNameLowercaseUnderscore, + propertyOrder, + propertySuppressDefaultConfig, + propertySuppressDefaultAttribute, + fieldInfo.CreateGetter(), + Serializer.GetWriteFn(propertyType), + defaultValue, + shouldSerialize, + shouldSerializeDynamic, + propertyType.IsEnum + ); + } + + PropertyWriters = PropertyWriters.OrderBy(x => x.propertyOrder).ToArray(); return true; } internal struct TypePropertyWriter { - internal string PropertyName + internal string GetPropertyName(Config config) { - get + switch (config.TextCase) { - return (JsConfig.EmitCamelCaseNames) - ? propertyNameCLSFriendly - : (JsConfig.EmitLowercaseUnderscoreNames) - ? propertyNameLowercaseUnderscore - : propertyName; + case TextCase.CamelCase: + return propertyNameCLSFriendly; + case TextCase.SnakeCase: + return propertyNameLowercaseUnderscore; + default: + return propertyName; } } + + internal readonly Type PropertyType; internal readonly string propertyName; + internal readonly int propertyOrder; + internal readonly bool propertySuppressDefaultConfig; + internal readonly bool propertySuppressDefaultAttribute; + internal readonly string propertyReferenceName; internal readonly string propertyNameCLSFriendly; internal readonly string propertyNameLowercaseUnderscore; - internal readonly Func GetterFn; + internal readonly GetMemberDelegate GetterFn; internal readonly WriteObjectDelegate WriteFn; internal readonly object DefaultValue; - - public TypePropertyWriter(string propertyName, string propertyNameCLSFriendly, string propertyNameLowercaseUnderscore, - Func getterFn, WriteObjectDelegate writeFn, object defaultValue) + internal readonly Func shouldSerialize; + internal readonly Func shouldSerializeDynamic; + internal readonly bool isEnum; + + public TypePropertyWriter(Type propertyType, string propertyName, string propertyDeclaredTypeName, string propertyNameCLSFriendly, + string propertyNameLowercaseUnderscore, int propertyOrder, bool propertySuppressDefaultConfig, bool propertySuppressDefaultAttribute, + GetMemberDelegate getterFn, WriteObjectDelegate writeFn, object defaultValue, + Func shouldSerialize, + Func shouldSerializeDynamic, + bool isEnum) { + this.PropertyType = propertyType; this.propertyName = propertyName; + this.propertyOrder = propertyOrder; + this.propertySuppressDefaultConfig = propertySuppressDefaultConfig; + this.propertySuppressDefaultAttribute = propertySuppressDefaultAttribute; + this.propertyReferenceName = propertyDeclaredTypeName + "." + propertyName; this.propertyNameCLSFriendly = propertyNameCLSFriendly; this.propertyNameLowercaseUnderscore = propertyNameLowercaseUnderscore; this.GetterFn = getterFn; this.WriteFn = writeFn; this.DefaultValue = defaultValue; + this.shouldSerialize = shouldSerialize; + this.shouldSerializeDynamic = shouldSerializeDynamic; + this.isEnum = isEnum; + } + + public bool ShouldWriteProperty(object propertyValue, Config config) + { + var isDefaultValue = propertyValue == null || Equals(DefaultValue, propertyValue); + if (isDefaultValue) + { + if (!isEnum) + { + if (propertySuppressDefaultAttribute || config.ExcludeDefaultValues) + return false; + if (!Serializer.IncludeNullValues && (propertyValue == null || propertySuppressDefaultConfig)) + return false; + } + else if (!config.IncludeDefaultEnums) + { + return false; + } + } + + return true; } } @@ -221,14 +365,24 @@ public static void WriteAbstractProperties(TextWriter writer, object value) return; } - var writeFn = Serializer.GetWriteFn(valueType); - if (!JsConfig.ExcludeTypeInfo) JsState.IsWritingDynamic = true; - writeFn(writer, value); - if (!JsConfig.ExcludeTypeInfo) JsState.IsWritingDynamic = false; + WriteLateboundProperties(writer, value, valueType); } - public static void WriteProperties(TextWriter writer, object value) + public static void WriteProperties(TextWriter writer, object instance) { + if (instance == null) + { + writer.Write(JsWriter.EmptyMap); + return; + } + + var valueType = instance.GetType(); + if (PropertyWriters != null && valueType != typeof(T) && !typeof(T).IsAbstract) + { + WriteLateboundProperties(writer, instance, valueType); + return; + } + if (typeof(TSerializer) == typeof(JsonTypeSerializer) && JsState.WritingKeyCount > 0) writer.Write(JsWriter.QuoteChar); @@ -238,40 +392,68 @@ public static void WriteProperties(TextWriter writer, object value) if (WriteTypeInfo != null || JsState.IsWritingDynamic) { if (JsConfig.PreferInterfaces && TryWriteSelfType(writer)) i++; - else if (TryWriteTypeInfo(writer, value)) i++; + else if (TryWriteTypeInfo(writer, instance)) i++; JsState.IsWritingDynamic = false; } if (PropertyWriters != null) { + var config = JsConfig.GetConfig(); + + var typedInstance = (T)instance; var len = PropertyWriters.Length; for (int index = 0; index < len; index++) { var propertyWriter = PropertyWriters[index]; - var propertyValue = value != null - ? propertyWriter.GetterFn((T)value) - : null; - if ((propertyValue == null - || (propertyWriter.DefaultValue != null && propertyWriter.DefaultValue.Equals(propertyValue))) - && !Serializer.IncludeNullValues) continue; + if (propertyWriter.shouldSerialize?.Invoke(typedInstance) == false) + continue; + + var dontSkipDefault = false; + if (propertyWriter.shouldSerializeDynamic != null) + { + var shouldSerialize = propertyWriter.shouldSerializeDynamic(typedInstance, propertyWriter.GetPropertyName(config)); + if (shouldSerialize.HasValue) + { + if (shouldSerialize.Value) + dontSkipDefault = true; + else + continue; + } + } + + var propertyValue = propertyWriter.GetterFn(typedInstance); + + if (!dontSkipDefault) + { + if (!propertyWriter.ShouldWriteProperty(propertyValue, config)) + continue; + + if (config.ExcludePropertyReferences?.Contains(propertyWriter.propertyReferenceName) == true) continue; + } if (i++ > 0) writer.Write(JsWriter.ItemSeperator); - Serializer.WritePropertyName(writer, propertyWriter.PropertyName); + Serializer.WritePropertyName(writer, propertyWriter.GetPropertyName(config)); writer.Write(JsWriter.MapKeySeperator); if (typeof(TSerializer) == typeof(JsonTypeSerializer)) JsState.IsWritingValue = true; - if (propertyValue == null) + try { - writer.Write(JsonUtils.Null); + if (propertyValue == null) + { + writer.Write(JsonUtils.Null); + } + else + { + propertyWriter.WriteFn(writer, propertyValue); + } } - else + finally { - propertyWriter.WriteFn(writer, propertyValue); + if (typeof(TSerializer) == typeof(JsonTypeSerializer)) JsState.IsWritingValue = false; } - if (typeof(TSerializer) == typeof(JsonTypeSerializer)) JsState.IsWritingValue = false; } } @@ -281,23 +463,170 @@ public static void WriteProperties(TextWriter writer, object value) writer.Write(JsWriter.QuoteChar); } - public static void WriteQueryString(TextWriter writer, object value) + private static void WriteLateboundProperties(TextWriter writer, object value, Type valueType) + { + var writeFn = Serializer.GetWriteFn(valueType); + var prevState = JsState.IsWritingDynamic; + if (!JsConfig.ExcludeTypeInfo.GetValueOrDefault()) JsState.IsWritingDynamic = true; + writeFn(writer, value); + if (!JsConfig.ExcludeTypeInfo.GetValueOrDefault()) JsState.IsWritingDynamic = prevState; + } + + internal static string GetPropertyName(string propertyName, Config config) + { + switch (config.TextCase) + { + case TextCase.CamelCase: + return propertyName.ToCamelCase(); + case TextCase.SnakeCase: + return propertyName.ToLowercaseUnderscore(); + default: + return propertyName; + } + } + + private static readonly char[] ArrayBrackets = { '[', ']' }; + + public static void WriteComplexQueryStringProperties(string typeName, TextWriter writer, object instance) + { + var i = 0; + if (PropertyWriters != null) + { + var config = JsConfig.GetConfig(); + + var typedInstance = (T)instance; + var len = PropertyWriters.Length; + for (var index = 0; index < len; index++) + { + var propertyWriter = PropertyWriters[index]; + if (propertyWriter.shouldSerialize != null && !propertyWriter.shouldSerialize(typedInstance)) + continue; + + var propertyValue = instance != null ? propertyWriter.GetterFn(typedInstance) : null; + if (propertyWriter.propertySuppressDefaultAttribute && Equals(propertyWriter.DefaultValue, propertyValue)) + continue; + + if ((propertyValue == null + || propertyWriter.propertySuppressDefaultConfig && Equals(propertyWriter.DefaultValue, propertyValue)) + && !Serializer.IncludeNullValues) + continue; + + if (config.ExcludePropertyReferences != null && config.ExcludePropertyReferences.Contains(propertyWriter.propertyReferenceName)) + continue; + + if (i++ > 0) + writer.Write('&'); + + var propertyValueType = propertyValue?.GetType(); + if (propertyValueType != null && + propertyValueType.IsUserType() && + !propertyValueType.HasInterface(typeof(IEnumerable))) + { + //Nested Complex Type: legal_entity[dob][day]=1 + var prefix = $"{typeName}[{propertyWriter.GetPropertyName(config)}]"; + var props = propertyValueType.GetSerializableProperties(); + for (int j = 0; j < props.Length; j++) + { + var pi = props[j]; + var pValue = pi.GetValue(propertyValue, TypeConstants.EmptyObjectArray); + if (pValue == null && !Serializer.IncludeNullValues) + continue; + + if (j > 0) + writer.Write('&'); + + writer.Write(prefix); + writer.Write('['); + writer.Write(GetPropertyName(pi.Name, config)); + writer.Write("]="); + + if (pValue == null) + { + writer.Write(JsonUtils.Null); + } + else + { + JsvWriter.GetWriteFn(pValue.GetType())(writer, pValue); + } + } + } + else + { + writer.Write(typeName); + writer.Write('['); + writer.Write(propertyWriter.GetPropertyName(config)); + writer.Write("]="); + + if (propertyValue == null) + { + writer.Write(JsonUtils.Null); + } + else + { + propertyWriter.WriteFn(writer, propertyValue); + } + } + } + } + } + + public static void WriteQueryString(TextWriter writer, object instance) { try { JsState.QueryStringMode = true; + var config = JsConfig.GetConfig(); + var i = 0; + var typedInstance = (T)instance; foreach (var propertyWriter in PropertyWriters) { - var propertyValue = propertyWriter.GetterFn((T) value); + var propertyValue = propertyWriter.GetterFn(typedInstance); if (propertyValue == null) continue; if (i++ > 0) writer.Write('&'); - Serializer.WritePropertyName(writer, propertyWriter.PropertyName); + var propertyType = propertyValue.GetType(); + var strValue = propertyValue as string; + var isEnumerable = strValue == null + && !propertyType.IsValueType + && propertyType.HasInterface(typeof(IEnumerable)); + + if (QueryStringSerializer.ComplexTypeStrategy != null) + { + var nonEnumerableUserType = !isEnumerable && (propertyType.IsUserType() || propertyType.IsInterface); + if (nonEnumerableUserType || propertyType.IsOrHasGenericInterfaceTypeOf(typeof(IDictionary<,>))) + { + if (QueryStringSerializer.ComplexTypeStrategy(writer, propertyWriter.GetPropertyName(config), propertyValue)) + continue; + } + } + + Serializer.WritePropertyName(writer, propertyWriter.GetPropertyName(config)); writer.Write('='); - propertyWriter.WriteFn(writer, propertyValue); + + if (strValue != null) + { + writer.Write(strValue.UrlEncode()); + } + else if (!isEnumerable) + { + propertyWriter.WriteFn(writer, propertyValue); + } + else + { + //Trim brackets in top-level lists in QueryStrings, e.g: ?a=[1,2,3] => ?a=1,2,3 + using (var ms = MemoryStreamFactory.GetStream()) + { + var enumerableWriter = new StreamWriter(ms); //ms disposed in using + propertyWriter.WriteFn(enumerableWriter, propertyValue); + enumerableWriter.Flush(); + var output = ms.ReadToEnd(); + output = output.Trim(ArrayBrackets); + writer.Write(output); + } + } } } finally diff --git a/src/ServiceStack.Text/Controller/CommandProcessor.cs b/src/ServiceStack.Text/Controller/CommandProcessor.cs index 1125d38c6..bc9e64c11 100644 --- a/src/ServiceStack.Text/Controller/CommandProcessor.cs +++ b/src/ServiceStack.Text/Controller/CommandProcessor.cs @@ -5,14 +5,15 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace ServiceStack.Text.Controller { @@ -27,8 +28,11 @@ public CommandProcessor(object[] controllers) this.Controllers = controllers; this.contextMap = new Dictionary(); - controllers.ToList().ForEach(x => contextMap[x.GetType().Name] = x); - } + foreach (var x in controllers.ToList()) + { + contextMap[x.GetType().Name] = x; + } + } public void Invoke(string commandUri) { @@ -38,16 +42,13 @@ public void Invoke(string commandUri) var pathInfo = PathInfo.Parse(actionParts[1]); - object context; - if (!this.contextMap.TryGetValue(controllerName, out context)) - { - throw new Exception("UnknownContext: " + controllerName); - } + if (!this.contextMap.TryGetValue(controllerName, out var context)) + throw new Exception("UnknownContext: " + controllerName); - var methodName = pathInfo.ActionName; + var methodName = pathInfo.ActionName; - var method = context.GetType().GetMethods().First( - c => c.Name == methodName && c.GetParameters().Count() == pathInfo.Arguments.Count); + var method = context.GetType().GetMethods().First( + c => c.Name == methodName && c.GetParameters().Length == pathInfo.Arguments.Count); var methodParamTypes = method.GetParameters().Select(x => x.ParameterType); diff --git a/src/ServiceStack.Text/Controller/PathInfo.cs b/src/ServiceStack.Text/Controller/PathInfo.cs index 3dd7060ff..bdc2abba5 100644 --- a/src/ServiceStack.Text/Controller/PathInfo.cs +++ b/src/ServiceStack.Text/Controller/PathInfo.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; diff --git a/src/ServiceStack.Text/CsvAttribute.cs b/src/ServiceStack.Text/CsvAttribute.cs index d5a9a9e4a..398e305d1 100644 --- a/src/ServiceStack.Text/CsvAttribute.cs +++ b/src/ServiceStack.Text/CsvAttribute.cs @@ -7,7 +7,7 @@ public enum CsvBehavior FirstEnumerable } - [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] public class CsvAttribute : Attribute { public CsvBehavior CsvBehavior { get; set; } diff --git a/src/ServiceStack.Text/CsvConfig.cs b/src/ServiceStack.Text/CsvConfig.cs index a7b45774d..b6f2c8658 100644 --- a/src/ServiceStack.Text/CsvConfig.cs +++ b/src/ServiceStack.Text/CsvConfig.cs @@ -1,90 +1,86 @@ using System; using System.Collections.Generic; +using System.Globalization; using ServiceStack.Text.Common; namespace ServiceStack.Text { public static class CsvConfig { - static CsvConfig() - { + static CsvConfig() + { Reset(); - } - - [ThreadStatic] - private static string tsItemSeperatorString; - private static string sItemSeperatorString; - public static string ItemSeperatorString - { - get - { - return tsItemSeperatorString ?? sItemSeperatorString ?? JsWriter.ItemSeperatorString; - } - set - { - tsItemSeperatorString = value; - if (sItemSeperatorString == null) sItemSeperatorString = value; + } + + private static CultureInfo sRealNumberCultureInfo; + public static CultureInfo RealNumberCultureInfo + { + get => sRealNumberCultureInfo ?? CultureInfo.InvariantCulture; + set => sRealNumberCultureInfo = value; + } + + [ThreadStatic] + private static string tsItemSeperatorString; + private static string sItemSeperatorString; + public static string ItemSeperatorString + { + get => tsItemSeperatorString ?? sItemSeperatorString ?? JsWriter.ItemSeperatorString; + set + { + tsItemSeperatorString = value; + if (sItemSeperatorString == null) sItemSeperatorString = value; + ResetEscapeStrings(); + } + } + + [ThreadStatic] + private static string tsItemDelimiterString; + private static string sItemDelimiterString; + public static string ItemDelimiterString + { + get => tsItemDelimiterString ?? sItemDelimiterString ?? JsWriter.QuoteString; + set + { + tsItemDelimiterString = value; + if (sItemDelimiterString == null) sItemDelimiterString = value; + EscapedItemDelimiterString = value + value; ResetEscapeStrings(); - } - } - - [ThreadStatic] - private static string tsItemDelimiterString; - private static string sItemDelimiterString; - public static string ItemDelimiterString - { - get - { - return tsItemDelimiterString ?? sItemDelimiterString ?? JsWriter.QuoteString; - } - set - { - tsItemDelimiterString = value; - if (sItemDelimiterString == null) sItemDelimiterString = value; - EscapedItemDelimiterString = value + value; - ResetEscapeStrings(); - } - } + } + } private const string DefaultEscapedItemDelimiterString = JsWriter.QuoteString + JsWriter.QuoteString; [ThreadStatic] - private static string tsEscapedItemDelimiterString; - private static string sEscapedItemDelimiterString; - internal static string EscapedItemDelimiterString - { - get - { - return tsEscapedItemDelimiterString ?? sEscapedItemDelimiterString ?? DefaultEscapedItemDelimiterString; - } - set - { - tsEscapedItemDelimiterString = value; - if (sEscapedItemDelimiterString == null) sEscapedItemDelimiterString = value; - } - } + private static string tsEscapedItemDelimiterString; + private static string sEscapedItemDelimiterString; + internal static string EscapedItemDelimiterString + { + get => tsEscapedItemDelimiterString ?? sEscapedItemDelimiterString ?? DefaultEscapedItemDelimiterString; + set + { + tsEscapedItemDelimiterString = value; + if (sEscapedItemDelimiterString == null) sEscapedItemDelimiterString = value; + } + } private static readonly string[] defaultEscapeStrings = GetEscapeStrings(); - [ThreadStatic] - private static string[] tsEscapeStrings; - private static string[] sEscapeStrings; - public static string[] EscapeStrings - { - get - { - return tsEscapeStrings ?? sEscapeStrings ?? defaultEscapeStrings; - } + [ThreadStatic] + private static string[] tsEscapeStrings; + private static string[] sEscapeStrings; + public static string[] EscapeStrings + { + get => tsEscapeStrings ?? sEscapeStrings ?? defaultEscapeStrings; private set { - tsEscapeStrings = value; - if (sEscapeStrings == null) sEscapeStrings = value; + tsEscapeStrings = value; + if (sEscapeStrings == null) sEscapeStrings = value; } - } + } private static string[] GetEscapeStrings() { - return new[] {ItemDelimiterString, ItemSeperatorString, RowSeparatorString, "\r", "\n"}; + return new[] { ItemDelimiterString, ItemSeperatorString, RowSeparatorString, "\r", "\n" }; } private static void ResetEscapeStrings() @@ -92,82 +88,77 @@ private static void ResetEscapeStrings() EscapeStrings = GetEscapeStrings(); } - [ThreadStatic] - private static string tsRowSeparatorString; - private static string sRowSeparatorString; - public static string RowSeparatorString - { - get - { - return tsRowSeparatorString ?? sRowSeparatorString ?? Environment.NewLine; - } - set - { - tsRowSeparatorString = value; - if (sRowSeparatorString == null) sRowSeparatorString = value; + [ThreadStatic] + private static string tsRowSeparatorString; + private static string sRowSeparatorString; + public static string RowSeparatorString + { + get => tsRowSeparatorString ?? sRowSeparatorString ?? "\r\n"; + set + { + tsRowSeparatorString = value; + if (sRowSeparatorString == null) sRowSeparatorString = value; ResetEscapeStrings(); - } - } - - public static void Reset() - { - tsItemSeperatorString = sItemSeperatorString = null; - tsItemDelimiterString = sItemDelimiterString = null; - tsEscapedItemDelimiterString = sEscapedItemDelimiterString = null; - tsRowSeparatorString = sRowSeparatorString = null; - tsEscapeStrings = sEscapeStrings = null; - } + } + } + + public static void Reset() + { + tsItemSeperatorString = sItemSeperatorString = null; + tsItemDelimiterString = sItemDelimiterString = null; + tsEscapedItemDelimiterString = sEscapedItemDelimiterString = null; + tsRowSeparatorString = sRowSeparatorString = null; + tsEscapeStrings = sEscapeStrings = null; + } } - public static class CsvConfig - { - public static bool OmitHeaders { get; set; } - - private static Dictionary customHeadersMap; - public static Dictionary CustomHeadersMap - { - get - { - return customHeadersMap; - } - set - { - customHeadersMap = value; - if (value == null) return; - CsvWriter.ConfigureCustomHeaders(customHeadersMap); - } - } - - public static object CustomHeaders - { - set - { - if (value == null) return; - if (value.GetType().IsValueType) - throw new ArgumentException("CustomHeaders is a ValueType"); - - var propertyInfos = value.GetType().GetProperties(); - if (propertyInfos.Length == 0) return; - - customHeadersMap = new Dictionary(); - foreach (var pi in propertyInfos) - { - var getMethod = pi.GetGetMethod(); - if (getMethod == null) continue; - - var oValue = getMethod.Invoke(value, new object[0]); - if (oValue == null) continue; - customHeadersMap[pi.Name] = oValue.ToString(); - } - CsvWriter.ConfigureCustomHeaders(customHeadersMap); - } - } - - public static void Reset() - { - OmitHeaders = false; - CsvWriter.Reset(); - } - } + public static class CsvConfig + { + public static bool OmitHeaders { get; set; } + + private static Dictionary customHeadersMap; + public static Dictionary CustomHeadersMap + { + get => customHeadersMap; + set + { + customHeadersMap = value; + if (value == null) return; + CsvWriter.ConfigureCustomHeaders(customHeadersMap); + CsvReader.ConfigureCustomHeaders(customHeadersMap); + } + } + + public static object CustomHeaders + { + set + { + if (value == null) return; + if (value.GetType().IsValueType) + throw new ArgumentException("CustomHeaders is a ValueType"); + + var propertyInfos = value.GetType().GetProperties(); + if (propertyInfos.Length == 0) return; + + customHeadersMap = new Dictionary(); + foreach (var pi in propertyInfos) + { + var getMethod = pi.GetGetMethod(nonPublic:true); + if (getMethod == null) continue; + + var oValue = getMethod.Invoke(value, TypeConstants.EmptyObjectArray); + if (oValue == null) continue; + customHeadersMap[pi.Name] = oValue.ToString(); + } + CsvWriter.ConfigureCustomHeaders(customHeadersMap); + } + } + + public static void Reset() + { + OmitHeaders = false; + CsvWriter.Reset(); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/CsvReader.cs b/src/ServiceStack.Text/CsvReader.cs new file mode 100644 index 000000000..f468ae62a --- /dev/null +++ b/src/ServiceStack.Text/CsvReader.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ServiceStack.Text.Common; +using ServiceStack.Text.Jsv; + +namespace ServiceStack.Text +{ + public class CsvReader + { + public static List ParseLines(string csv) + { + var rows = new List(); + if (string.IsNullOrEmpty(csv)) + return rows; + + var withinQuotes = false; + var lastPos = 0; + + var i = -1; + var len = csv.Length; + while (++i < len) + { + var c = csv[i]; + if (c == JsWriter.QuoteChar) + { + var isLiteralQuote = i + 1 < len && csv[i + 1] == JsWriter.QuoteChar; + if (isLiteralQuote) + { + i++; + continue; + } + + withinQuotes = !withinQuotes; + } + + if (withinQuotes) + continue; + + if (c == JsWriter.LineFeedChar) + { + var str = i > 0 && csv[i - 1] == JsWriter.ReturnChar + ? csv.Substring(lastPos, i - lastPos - 1) + : csv.Substring(lastPos, i - lastPos); + + if (str.Length > 0) + rows.Add(str); + lastPos = i + 1; + } + } + + if (i > lastPos) + { + var str = csv.Substring(lastPos, i - lastPos); + if (str.Length > 0) + rows.Add(str); + } + + return rows; + } + + public static List ParseFields(string line) => ParseFields(line, null); + public static List ParseFields(string line, Func parseFn) + { + var to = new List(); + if (string.IsNullOrEmpty(line)) + return to; + + var i = -1; + var len = line.Length; + while (++i <= len) + { + var value = EatValue(line, ref i); + to.Add(parseFn != null ? parseFn(value.FromCsvField()) : value.FromCsvField()); + } + + return to; + } + + //Originally from JsvTypeSerializer.EatValue() + public static string EatValue(string value, ref int i) + { + var tokenStartPos = i; + var valueLength = value.Length; + if (i == valueLength) return null; + + var valueChar = value[i]; + var withinQuotes = false; + var endsToEat = 1; + var itemSeperator = CsvConfig.ItemSeperatorString.Length == 1 + ? CsvConfig.ItemSeperatorString[0] + : JsWriter.ItemSeperator; + + if (valueChar == itemSeperator || valueChar == JsWriter.MapEndChar) + return null; + + if (valueChar == JsWriter.QuoteChar) //Is Within Quotes, i.e. "..." + { + while (++i < valueLength) + { + valueChar = value[i]; + + if (valueChar != JsWriter.QuoteChar) continue; + + var isLiteralQuote = i + 1 < valueLength && value[i + 1] == JsWriter.QuoteChar; + + i++; //skip quote + if (!isLiteralQuote) + break; + } + return value.Substring(tokenStartPos, i - tokenStartPos); + } + if (valueChar == JsWriter.MapStartChar) //Is Type/Map, i.e. {...} + { + while (++i < valueLength && endsToEat > 0) + { + valueChar = value[i]; + + if (valueChar == JsWriter.QuoteChar) + withinQuotes = !withinQuotes; + + if (withinQuotes) + continue; + + if (valueChar == JsWriter.MapStartChar) + endsToEat++; + + if (valueChar == JsWriter.MapEndChar) + endsToEat--; + } + if (endsToEat > 0) + { + //Unmatched start and end char, give up + i = tokenStartPos; + valueChar = value[i]; + } + else + return value.Substring(tokenStartPos, i - tokenStartPos); + } + if (valueChar == JsWriter.ListStartChar) //Is List, i.e. [...] + { + while (++i < valueLength && endsToEat > 0) + { + valueChar = value[i]; + + if (valueChar == JsWriter.QuoteChar) + withinQuotes = !withinQuotes; + + if (withinQuotes) + continue; + + if (valueChar == JsWriter.ListStartChar) + endsToEat++; + + if (valueChar == JsWriter.ListEndChar) + endsToEat--; + } + if (endsToEat > 0) + { + //Unmatched start and end char, give up + i = tokenStartPos; + valueChar = value[i]; + } + else + return value.Substring(tokenStartPos, i - tokenStartPos); + } + + //if value starts with MapStartChar, check MapEndChar to terminate + char specEndChar = itemSeperator; + if (value[tokenStartPos] == JsWriter.MapStartChar) + specEndChar = JsWriter.MapEndChar; + + while (++i < valueLength) //Is Value + { + valueChar = value[i]; + + if (valueChar == itemSeperator || valueChar == specEndChar) + { + break; + } + } + + return value.Substring(tokenStartPos, i - tokenStartPos); + } + } + + public class CsvReader + { + public static List Headers { get; set; } + + internal static List> PropertySetters; + internal static Dictionary> PropertySettersMap; + + internal static List PropertyConverters; + internal static Dictionary PropertyConvertersMap; + + static CsvReader() + { + Reset(); + } + + internal static void Reset() + { + Headers = new List(); + + PropertySetters = new List>(); + PropertySettersMap = new Dictionary>(PclExport.Instance.InvariantComparerIgnoreCase); + + PropertyConverters = new List(); + PropertyConvertersMap = new Dictionary(PclExport.Instance.InvariantComparerIgnoreCase); + + foreach (var propertyInfo in TypeConfig.Properties) + { + if (!propertyInfo.CanWrite || propertyInfo.GetSetMethod(nonPublic:true) == null) continue; + if (!TypeSerializer.CanCreateFromString(propertyInfo.PropertyType)) continue; + + var propertyName = propertyInfo.Name; + var setter = propertyInfo.CreateSetter(); + PropertySetters.Add(setter); + + var converter = JsvReader.GetParseFn(propertyInfo.PropertyType); + PropertyConverters.Add(converter); + + var dcsDataMemberName = propertyInfo.GetDataMemberName(); + if (dcsDataMemberName != null) + propertyName = dcsDataMemberName; + + Headers.Add(propertyName); + PropertySettersMap[propertyName] = setter; + PropertyConvertersMap[propertyName] = converter; + } + } + + internal static void ConfigureCustomHeaders(Dictionary customHeadersMap) + { + Reset(); + + for (var i = Headers.Count - 1; i >= 0; i--) + { + var oldHeader = Headers[i]; + if (!customHeadersMap.TryGetValue(oldHeader, out var newHeader)) + { + Headers.RemoveAt(i); + PropertySetters.RemoveAt(i); + } + else + { + Headers[i] = newHeader; + + PropertySettersMap.TryGetValue(oldHeader, out var setter); + PropertySettersMap.Remove(oldHeader); + PropertySettersMap[newHeader] = setter; + + PropertyConvertersMap.TryGetValue(oldHeader, out var converter); + PropertyConvertersMap.Remove(oldHeader); + PropertyConvertersMap[newHeader] = converter; + } + } + } + + private static List GetSingleRow(IEnumerable rows, Type recordType) + { + var row = new List(); + foreach (var value in rows) + { + var to = recordType == typeof(string) + ? (T)(object)value + : TypeSerializer.DeserializeFromString(value); + + row.Add(to); + } + return row; + } + + public static List GetRows(IEnumerable records) + { + var rows = new List(); + + if (records == null) return rows; + + if (typeof(T).IsValueType || typeof(T) == typeof(string)) + { + return GetSingleRow(records, typeof(T)); + } + + foreach (var record in records) + { + var to = typeof(T).CreateInstance(); + foreach (var propertySetter in PropertySetters) + { + propertySetter(to, record); + } + rows.Add(to); + } + + return rows; + } + + public static object ReadObject(string csv) + { + if (csv == null) return null; //AOT + + return Read(CsvReader.ParseLines(csv)); + } + + public static object ReadObjectRow(string csv) + { + if (csv == null) return null; //AOT + + return ReadRow(csv); + } + + public static List> ReadStringDictionary(IEnumerable rows) + { + if (rows == null) return null; //AOT + + var to = new List>(); + + List headers = null; + foreach (var row in rows) + { + if (headers == null) + { + headers = CsvReader.ParseFields(row); + continue; + } + + var values = CsvReader.ParseFields(row); + var map = new Dictionary(); + for (int i = 0; i < headers.Count; i++) + { + var header = headers[i]; + map[header] = values[i]; + } + + to.Add(map); + } + + return to; + } + + public static List Read(List rows) + { + var to = new List(); + if (rows == null || rows.Count == 0) return to; //AOT + + if (typeof(T).IsAssignableFrom(typeof(Dictionary))) + { + return ReadStringDictionary(rows).ConvertAll(x => (T)(object)x); + } + + if (typeof(T).IsAssignableFrom(typeof(List))) + { + return new List(rows.Select(x => (T)(object)CsvReader.ParseFields(x))); + } + + List headers = null; + if (!CsvConfig.OmitHeaders || Headers.Count == 0) + { + headers = CsvReader.ParseFields(rows[0], s => s.Trim()); + } + + if (typeof(T).IsValueType || typeof(T) == typeof(string)) + { + return rows.Count == 1 + ? GetSingleRow(CsvReader.ParseFields(rows[0]), typeof(T)) + : GetSingleRow(rows, typeof(T)); + } + + for (var rowIndex = headers == null ? 0 : 1; rowIndex < rows.Count; rowIndex++) + { + var row = rows[rowIndex]; + var o = typeof(T).CreateInstance(); + + var fields = CsvReader.ParseFields(row); + for (int i = 0; i < fields.Count; i++) + { + var setter = i < PropertySetters.Count ? PropertySetters[i] : null; + if (headers != null) + PropertySettersMap.TryGetValue(headers[i], out setter); + + if (setter == null) + continue; + + var converter = i < PropertyConverters.Count ? PropertyConverters[i] : null; + if (headers != null) + PropertyConvertersMap.TryGetValue(headers[i], out converter); + + if (converter == null) + continue; + + var field = fields[i]; + var convertedValue = converter(field); + setter(o, convertedValue); + } + + to.Add(o); + } + + return to; + } + + public static T ReadRow(string value) + { + if (value == null) return default(T); //AOT + + return Read(CsvReader.ParseLines(value)).FirstOrDefault(); + } + + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/CsvSerializer.cs b/src/ServiceStack.Text/CsvSerializer.cs index 2afcef52f..6454916f8 100644 --- a/src/ServiceStack.Text/CsvSerializer.cs +++ b/src/ServiceStack.Text/CsvSerializer.cs @@ -1,272 +1,571 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using ServiceStack.Text.Common; using ServiceStack.Text.Jsv; -using ServiceStack.Text.Reflection; namespace ServiceStack.Text { - public class CsvSerializer - { - private static readonly UTF8Encoding UTF8EncodingWithoutBom = new UTF8Encoding(false); + public class CsvSerializer + { + //Don't emit UTF8 BOM by default + public static Encoding UseEncoding { get; set; } = PclExport.Instance.GetUTF8Encoding(false); - private static Dictionary WriteFnCache = new Dictionary(); + public static Action OnSerialize { get; set; } - internal static WriteObjectDelegate GetWriteFn(Type type) - { - try - { - WriteObjectDelegate writeFn; - if (WriteFnCache.TryGetValue(type, out writeFn)) return writeFn; + private static Dictionary WriteFnCache = new Dictionary(); + internal static WriteObjectDelegate GetWriteFn(Type type) + { + try + { + if (WriteFnCache.TryGetValue(type, out var writeFn)) return writeFn; var genericType = typeof(CsvSerializer<>).MakeGenericType(type); - var mi = genericType.GetMethod("WriteFn", BindingFlags.Public | BindingFlags.Static); - var writeFactoryFn = (Func)Delegate.CreateDelegate( - typeof(Func), mi); + var mi = genericType.GetStaticMethod("WriteFn"); + var writeFactoryFn = (Func)mi.MakeDelegate( + typeof(Func)); + writeFn = writeFactoryFn(); Dictionary snapshot, newCache; do { snapshot = WriteFnCache; - newCache = new Dictionary(WriteFnCache); - newCache[type] = writeFn; + newCache = new Dictionary(WriteFnCache) {[type] = writeFn}; } while (!ReferenceEquals( Interlocked.CompareExchange(ref WriteFnCache, newCache, snapshot), snapshot)); - + + return writeFn; + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + throw; + } + } + + private static Dictionary ReadFnCache = new Dictionary(); + internal static ParseStringDelegate GetReadFn(Type type) + { + try + { + if (ReadFnCache.TryGetValue(type, out var writeFn)) return writeFn; + + var genericType = typeof(CsvSerializer<>).MakeGenericType(type); + var mi = genericType.GetStaticMethod("ReadFn"); + var writeFactoryFn = (Func)mi.MakeDelegate( + typeof(Func)); + + writeFn = writeFactoryFn(); + + Dictionary snapshot, newCache; + do + { + snapshot = ReadFnCache; + newCache = new Dictionary(ReadFnCache) {[type] = writeFn}; + + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref ReadFnCache, newCache, snapshot), snapshot)); + return writeFn; - } - catch (Exception ex) - { - Tracer.Instance.WriteError(ex); - throw; - } - } - - public static string SerializeToCsv(IEnumerable records) - { - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture)) - { - writer.WriteCsv(records); - return sb.ToString(); - } - } - - public static string SerializeToString(T value) - { - if (value == null) return null; - if (typeof(T) == typeof(string)) return value as string; - - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture)) - { - CsvSerializer.WriteObject(writer, value); - } - return sb.ToString(); - } - - public static void SerializeToWriter(T value, TextWriter writer) - { - if (value == null) return; - if (typeof(T) == typeof(string)) - { - writer.Write(value); - return; - } - CsvSerializer.WriteObject(writer, value); - } - - public static void SerializeToStream(T value, Stream stream) - { - if (value == null) return; - var writer = new StreamWriter(stream, UTF8EncodingWithoutBom); - CsvSerializer.WriteObject(writer, value); + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + throw; + } + } + + public static string SerializeToCsv(IEnumerable records) + { + var writer = StringWriterThreadStatic.Allocate(); + writer.WriteCsv(records); + return StringWriterThreadStatic.ReturnAndFree(writer); + } + + public static string SerializeToString(T value) + { + if (value == null) return null; + if (typeof(T) == typeof(string)) return value as string; + + var writer = StringWriterThreadStatic.Allocate(); + CsvSerializer.WriteObject(writer, value); + return StringWriterThreadStatic.ReturnAndFree(writer); + } + + public static void SerializeToWriter(T value, TextWriter writer) + { + if (value == null) return; + if (typeof(T) == typeof(string)) + { + writer.Write(value); + return; + } + CsvSerializer.WriteObject(writer, value); + } + + public static void SerializeToStream(T value, Stream stream) + { + if (value == null) return; + var writer = new StreamWriter(stream, UseEncoding); + CsvSerializer.WriteObject(writer, value); writer.Flush(); - } + } - public static void SerializeToStream(object obj, Stream stream) - { - if (obj == null) return; - var writer = new StreamWriter(stream, UTF8EncodingWithoutBom); + public static void SerializeToStream(object obj, Stream stream) + { + if (obj == null) return; + var writer = new StreamWriter(stream, UseEncoding); var writeFn = GetWriteFn(obj.GetType()); writeFn(writer, obj); writer.Flush(); } - public static T DeserializeFromStream(Stream stream) - { - throw new NotImplementedException(); - } - - public static object DeserializeFromStream(Type type, Stream stream) - { - throw new NotImplementedException(); - } - - public static void WriteLateBoundObject(TextWriter writer, object value) - { - if (value == null) return; - var writeFn = GetWriteFn(value.GetType()); - writeFn(writer, value); - } - } - - internal static class CsvSerializer - { - private static readonly WriteObjectDelegate CacheFn; - - public static WriteObjectDelegate WriteFn() - { - return CacheFn; - } - - private const string IgnoreResponseStatus = "ResponseStatus"; - - private static Func valueGetter = null; - private static WriteObjectDelegate writeElementFn = null; - - private static WriteObjectDelegate GetWriteFn() - { - PropertyInfo firstCandidate = null; - Type bestCandidateEnumerableType = null; - PropertyInfo bestCandidate = null; - - if (typeof(T).IsValueType) - { - return JsvWriter.WriteObject; - } - - //If type is an enumerable property itself write that - bestCandidateEnumerableType = typeof(T).GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); - if (bestCandidateEnumerableType != null) - { - var elementType = bestCandidateEnumerableType.GetGenericArguments()[0]; - writeElementFn = CreateWriteFn(elementType); - - return WriteEnumerableType; - } - - //Look for best candidate property if DTO - if (typeof(T).IsDto() || typeof(T).HasAttr()) - { - var properties = TypeConfig.Properties; - foreach (var propertyInfo in properties) - { - if (propertyInfo.Name == IgnoreResponseStatus) continue; - - if (propertyInfo.PropertyType == typeof(string) - || propertyInfo.PropertyType.IsValueType - || propertyInfo.PropertyType == typeof(byte[])) continue; - - if (firstCandidate == null) - { - firstCandidate = propertyInfo; - } - - var enumProperty = propertyInfo.PropertyType - .GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); - - if (enumProperty != null) - { - bestCandidateEnumerableType = enumProperty; - bestCandidate = propertyInfo; - break; - } - } - } - - //If is not DTO or no candidates exist, write self - var noCandidatesExist = bestCandidate == null && firstCandidate == null; - if (noCandidatesExist) - { - return WriteSelf; - } - - //If is DTO and has an enumerable property serialize that - if (bestCandidateEnumerableType != null) - { - valueGetter = bestCandidate.GetValueGetter(typeof(T)); - - var elementType = bestCandidateEnumerableType.GetGenericArguments()[0]; - writeElementFn = CreateWriteFn(elementType); - - return WriteEnumerableProperty; - } - - //If is DTO and has non-enumerable, reference type property serialize that - valueGetter = firstCandidate.GetValueGetter(typeof(T)); - writeElementFn = CreateWriteRowFn(firstCandidate.PropertyType); - - return WriteNonEnumerableType; - } - - private static WriteObjectDelegate CreateWriteFn(Type elementType) - { - return CreateCsvWriterFn(elementType, "WriteObject"); - } - - private static WriteObjectDelegate CreateWriteRowFn(Type elementType) - { - return CreateCsvWriterFn(elementType, "WriteObjectRow"); - } - - private static WriteObjectDelegate CreateCsvWriterFn(Type elementType, string methodName) - { - var genericType = typeof(CsvWriter<>).MakeGenericType(elementType); - var mi = genericType.GetMethod(methodName, - BindingFlags.Static | BindingFlags.Public); - - var writeFn = (WriteObjectDelegate)Delegate.CreateDelegate(typeof(WriteObjectDelegate), mi); - - return writeFn; - } - - public static void WriteEnumerableType(TextWriter writer, object obj) - { - writeElementFn(writer, obj); - } - - public static void WriteSelf(TextWriter writer, object obj) - { - CsvWriter.WriteRow(writer, (T)obj); - } - - public static void WriteEnumerableProperty(TextWriter writer, object obj) - { - if (obj == null) return; //AOT - - var enumerableProperty = valueGetter(obj); - writeElementFn(writer, enumerableProperty); - } - - public static void WriteNonEnumerableType(TextWriter writer, object obj) - { - var nonEnumerableType = valueGetter(obj); - writeElementFn(writer, nonEnumerableType); - } - - static CsvSerializer() - { - if (typeof(T) == typeof(object)) - { - CacheFn = CsvSerializer.WriteLateBoundObject; - } - else - { - CacheFn = GetWriteFn(); - } - } - - public static void WriteObject(TextWriter writer, object value) - { - CacheFn(writer, value); - } - } + public static T DeserializeFromStream(Stream stream) + { + if (stream == null) return default(T); + using (var reader = new StreamReader(stream, UseEncoding)) + { + return DeserializeFromString(reader.ReadToEnd()); + } + } + + public static object DeserializeFromStream(Type type, Stream stream) + { + if (stream == null) return null; + using (var reader = new StreamReader(stream, UseEncoding)) + { + return DeserializeFromString(type, reader.ReadToEnd()); + } + } + + public static T DeserializeFromReader(TextReader reader) + { + return DeserializeFromString(reader.ReadToEnd()); + } + + public static T DeserializeFromString(string text) + { + if (string.IsNullOrEmpty(text)) return default; + var results = CsvSerializer.ReadObject(text); + return ConvertFrom(results); + } + + public static object DeserializeFromString(Type type, string text) + { + if (string.IsNullOrEmpty(text)) return null; + var hold = JsState.IsCsv; + JsState.IsCsv = true; + try + { + var fn = GetReadFn(type); + var result = fn(text); + var converted = ConvertFrom(type, result); + return converted; + } + finally + { + JsState.IsCsv = hold; + } + } + + public static void WriteLateBoundObject(TextWriter writer, object value) + { + if (value == null) return; + var writeFn = GetWriteFn(value.GetType()); + writeFn(writer, value); + } + + public static object ReadLateBoundObject(Type type, string value) + { + if (value == null) return null; + var readFn = GetReadFn(type); + return readFn(value); + } + + internal static T ConvertFrom(object results) + { + if (results is T variable) + return variable; + + foreach (var ci in typeof(T).GetConstructors()) + { + var ciParams = ci.GetParameters(); + if (ciParams.Length == 1) + { + var pi = ciParams.First(); + if (pi.ParameterType.IsAssignableFrom(typeof(T))) + { + var to = ci.Invoke(new[] { results }); + return (T)to; + } + } + } + + return results.ConvertTo(); + } + + internal static object ConvertFrom(Type type, object results) + { + if (type.IsInstanceOfType(results)) + return results; + + foreach (var ci in type.GetConstructors()) + { + var ciParams = ci.GetParameters(); + if (ciParams.Length == 1) + { + var pi = ciParams.First(); + if (pi.ParameterType.IsAssignableFrom(type)) + { + var to = ci.Invoke(new[] { results }); + return to; + } + } + } + + return results.ConvertTo(type); + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + CsvSerializer.WriteFn(); + CsvSerializer.WriteObject(null, null); + CsvWriter.Write(null, default(IEnumerable)); + CsvWriter.WriteRow(null, default(T)); + CsvWriter.WriteObject(null, default(IEnumerable)); + CsvWriter.WriteObjectRow(null, default(IEnumerable)); + + CsvReader.ReadRow(null); + CsvReader.ReadObject(null); + CsvReader.ReadObjectRow(null); + CsvReader.ReadStringDictionary(null); + } + } + + public static class CsvSerializer + { + private static readonly WriteObjectDelegate WriteCacheFn; + private static readonly ParseStringDelegate ReadCacheFn; + + public static WriteObjectDelegate WriteFn() + { + return WriteCacheFn; + } + + private const string IgnoreResponseStatus = "ResponseStatus"; + + private static GetMemberDelegate valueGetter = null; + private static WriteObjectDelegate writeElementFn = null; + + private static WriteObjectDelegate GetWriteFn() + { + PropertyInfo firstCandidate = null; + Type bestCandidateEnumerableType = null; + PropertyInfo bestCandidate = null; + + if (typeof(T).IsValueType) + { + return JsvWriter.WriteObject; + } + + //If type is an enumerable property itself write that + bestCandidateEnumerableType = typeof(T).GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + if (bestCandidateEnumerableType != null) + { + var dictionaryOrKvps = typeof(T).HasInterface(typeof(IEnumerable>)) + || typeof(T).HasInterface(typeof(IEnumerable>)); + if (dictionaryOrKvps) + { + return WriteSelf; + } + + var elementType = bestCandidateEnumerableType.GetGenericArguments()[0]; + writeElementFn = CreateWriteFn(elementType); + + return WriteEnumerableType; + } + + //Look for best candidate property if DTO + if (typeof(T).IsDto() || typeof(T).HasAttribute()) + { + var properties = TypeConfig.Properties; + foreach (var propertyInfo in properties) + { + if (propertyInfo.Name == IgnoreResponseStatus) continue; + + if (propertyInfo.PropertyType == typeof(string) + || propertyInfo.PropertyType.IsValueType + || propertyInfo.PropertyType == typeof(byte[])) + continue; + + if (firstCandidate == null) + { + firstCandidate = propertyInfo; + } + + var enumProperty = propertyInfo.PropertyType + .GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + + if (enumProperty != null) + { + bestCandidateEnumerableType = enumProperty; + bestCandidate = propertyInfo; + break; + } + } + } + + //If is not DTO or no candidates exist, write self + var noCandidatesExist = bestCandidate == null && firstCandidate == null; + if (noCandidatesExist) + { + return WriteSelf; + } + + //If is DTO and has an enumerable property serialize that + if (bestCandidateEnumerableType != null) + { + valueGetter = bestCandidate.CreateGetter(); + var elementType = bestCandidateEnumerableType.GetGenericArguments()[0]; + writeElementFn = CreateWriteFn(elementType); + + return WriteEnumerableProperty; + } + + //If is DTO and has non-enumerable, reference type property serialize that + valueGetter = firstCandidate.CreateGetter(); + writeElementFn = CreateWriteRowFn(firstCandidate.PropertyType); + + return WriteNonEnumerableType; + } + + private static WriteObjectDelegate CreateWriteFn(Type elementType) + { + return CreateCsvWriterFn(elementType, "WriteObject"); + } + + private static WriteObjectDelegate CreateWriteRowFn(Type elementType) + { + return CreateCsvWriterFn(elementType, "WriteObjectRow"); + } + + private static WriteObjectDelegate CreateCsvWriterFn(Type elementType, string methodName) + { + var genericType = typeof(CsvWriter<>).MakeGenericType(elementType); + var mi = genericType.GetStaticMethod(methodName); + var writeFn = (WriteObjectDelegate)mi.MakeDelegate(typeof(WriteObjectDelegate)); + return writeFn; + } + + public static void WriteEnumerableType(TextWriter writer, object obj) + { + writeElementFn(writer, obj); + } + + public static void WriteSelf(TextWriter writer, object obj) + { + CsvWriter.WriteRow(writer, (T)obj); + } + + public static void WriteEnumerableProperty(TextWriter writer, object obj) + { + if (obj == null) return; //AOT + + var enumerableProperty = valueGetter(obj); + writeElementFn(writer, enumerableProperty); + } + + public static void WriteNonEnumerableType(TextWriter writer, object obj) + { + var nonEnumerableType = valueGetter(obj); + writeElementFn(writer, nonEnumerableType); + } + + public static void WriteObject(TextWriter writer, object value) + { + var hold = JsState.IsCsv; + JsState.IsCsv = true; + try + { + CsvSerializer.OnSerialize?.Invoke(value); + WriteCacheFn(writer, value); + } + finally + { + JsState.IsCsv = hold; + } + } + + + static CsvSerializer() + { + if (typeof(T) == typeof(object)) + { + WriteCacheFn = CsvSerializer.WriteLateBoundObject; + ReadCacheFn = str => CsvSerializer.ReadLateBoundObject(typeof(T), str); + } + else + { + WriteCacheFn = GetWriteFn(); + ReadCacheFn = GetReadFn(); + } + } + + + public static ParseStringDelegate ReadFn() + { + return ReadCacheFn; + } + + private static SetMemberDelegate valueSetter = null; + private static ParseStringDelegate readElementFn = null; + + private static ParseStringDelegate GetReadFn() + { + PropertyInfo firstCandidate = null; + Type bestCandidateEnumerableType = null; + PropertyInfo bestCandidate = null; + + if (typeof(T).IsValueType) + { + return JsvReader.Parse; + } + + //If type is an enumerable property itself write that + bestCandidateEnumerableType = typeof(T).GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + if (bestCandidateEnumerableType != null) + { + var elementType = bestCandidateEnumerableType.GetGenericArguments()[0]; + readElementFn = CreateReadFn(elementType); + + return ReadEnumerableType; + } + + //Look for best candidate property if DTO + if (typeof(T).IsDto() || typeof(T).HasAttribute()) + { + var properties = TypeConfig.Properties; + foreach (var propertyInfo in properties) + { + if (propertyInfo.Name == IgnoreResponseStatus) continue; + + if (propertyInfo.PropertyType == typeof(string) + || propertyInfo.PropertyType.IsValueType + || propertyInfo.PropertyType == typeof(byte[])) + continue; + + if (firstCandidate == null) + { + firstCandidate = propertyInfo; + } + + var enumProperty = propertyInfo.PropertyType + .GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + + if (enumProperty != null) + { + bestCandidateEnumerableType = enumProperty; + bestCandidate = propertyInfo; + break; + } + } + } + + //If is not DTO or no candidates exist, write self + var noCandidatesExist = bestCandidate == null && firstCandidate == null; + if (noCandidatesExist) + { + return ReadSelf; + } + + //If is DTO and has an enumerable property serialize that + if (bestCandidateEnumerableType != null) + { + valueSetter = bestCandidate.CreateSetter(); + var elementType = bestCandidateEnumerableType.GetGenericArguments()[0]; + readElementFn = CreateReadFn(elementType); + + return ReadEnumerableProperty; + } + + //If is DTO and has non-enumerable, reference type property serialize that + valueSetter = firstCandidate.CreateSetter(); + readElementFn = CreateReadRowFn(firstCandidate.PropertyType); + + return ReadNonEnumerableType; + } + + private static ParseStringDelegate CreateReadFn(Type elementType) + { + return CreateCsvReadFn(elementType, "ReadObject"); + } + + private static ParseStringDelegate CreateReadRowFn(Type elementType) + { + return CreateCsvReadFn(elementType, "ReadObjectRow"); + } + + private static ParseStringDelegate CreateCsvReadFn(Type elementType, string methodName) + { + var genericType = typeof(CsvReader<>).MakeGenericType(elementType); + var mi = genericType.GetStaticMethod(methodName); + var readFn = (ParseStringDelegate)mi.MakeDelegate(typeof(ParseStringDelegate)); + return readFn; + } + + public static object ReadEnumerableType(string value) + { + return readElementFn(value); + } + + public static object ReadSelf(string value) + { + return CsvReader.ReadRow(value); + } + + public static object ReadEnumerableProperty(string row) + { + if (row == null) return null; //AOT + + var value = readElementFn(row); + var to = typeof(T).CreateInstance(); + valueSetter(to, value); + return to; + } + + public static object ReadNonEnumerableType(string row) + { + if (row == null) return null; //AOT + + var value = readElementFn(row); + var to = typeof(T).CreateInstance(); + valueSetter(to, value); + return to; + } + + public static object ReadObject(string value) + { + if (value == null) return null; //AOT + + var hold = JsState.IsCsv; + JsState.IsCsv = true; + try + { + return ReadCacheFn(value); + } + finally + { + JsState.IsCsv = hold; + } + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/CsvStreamExtensions.cs b/src/ServiceStack.Text/CsvStreamExtensions.cs index d10dce87f..bb3d49581 100644 --- a/src/ServiceStack.Text/CsvStreamExtensions.cs +++ b/src/ServiceStack.Text/CsvStreamExtensions.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System.Collections.Generic; @@ -15,20 +15,20 @@ namespace ServiceStack.Text { - public static class CsvStreamExtensions - { - public static void WriteCsv(this Stream outputStream, IEnumerable records) - { - using (var textWriter = new StreamWriter(outputStream)) - { - textWriter.WriteCsv(records); - } - } + public static class CsvStreamExtensions + { + public static void WriteCsv(this Stream outputStream, IEnumerable records) + { + using (var textWriter = new StreamWriter(outputStream)) + { + textWriter.WriteCsv(records); + } + } - public static void WriteCsv(this TextWriter writer, IEnumerable records) - { - CsvWriter.Write(writer, records); - } + public static void WriteCsv(this TextWriter writer, IEnumerable records) + { + CsvWriter.Write(writer, records); + } - } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/CsvWriter.cs b/src/ServiceStack.Text/CsvWriter.cs index 78658236e..1ad3cbfd9 100644 --- a/src/ServiceStack.Text/CsvWriter.cs +++ b/src/ServiceStack.Text/CsvWriter.cs @@ -2,42 +2,72 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.Serialization; +using System.Reflection; using ServiceStack.Text.Common; -using ServiceStack.Text.Reflection; -#if WINDOWS_PHONE -using ServiceStack.Text.WP; -#endif namespace ServiceStack.Text { internal class CsvDictionaryWriter { - public static void WriteRow(TextWriter writer, IEnumerable row) - { - var ranOnce = false; - foreach (var field in row) - { - CsvWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - - writer.Write(field.ToCsvField()); - } - writer.Write(CsvConfig.RowSeparatorString); - } + public static void WriteRow(TextWriter writer, IEnumerable row) + { + if (writer == null) return; //AOT + + var ranOnce = false; + foreach (var field in row) + { + CsvWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + + writer.Write(field.ToCsvField()); + } + writer.Write(CsvConfig.RowSeparatorString); + } public static void WriteObjectRow(TextWriter writer, IEnumerable row) { + if (writer == null) return; //AOT + var ranOnce = false; foreach (var field in row) { - JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + CsvWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); writer.Write(field.ToCsvField()); } - writer.WriteLine(); + writer.Write(CsvConfig.RowSeparatorString); + } + + public static void Write(TextWriter writer, IEnumerable> records) + { + if (records == null) return; //AOT + + var requireHeaders = !CsvConfig>>.OmitHeaders; + if (requireHeaders) + { + var keys = records.Select(x => x.Key); + WriteRow(writer, keys); + } + + var values = records.Select(x => x.Value); + WriteObjectRow(writer, values); + } + + public static void Write(TextWriter writer, IEnumerable> records) + { + if (records == null) return; //AOT + + var requireHeaders = !CsvConfig>>.OmitHeaders; + if (requireHeaders) + { + var keys = records.Select(x => x.Key); + WriteRow(writer, keys); + } + + var values = records.Select(x => x.Value); + WriteObjectRow(writer, values); } - public static void Write(TextWriter writer, IEnumerable> records) + public static void Write(TextWriter writer, IEnumerable> records) { if (records == null) return; //AOT @@ -46,50 +76,57 @@ public static void Write(TextWriter writer, IEnumerable> records) - { - if (records == null) return; //AOT + public static void Write(TextWriter writer, IEnumerable> records) + { + if (records == null) return; //AOT - var allKeys = new HashSet(); - var cachedRecords = new List>(); + var allKeys = new HashSet(); + var cachedRecords = new List>(); - foreach (var record in records) { - foreach (var key in record.Keys) { - if (!allKeys.Contains(key)) { + foreach (var record in records) + { + foreach (var key in record.Keys) + { + if (!allKeys.Contains(key)) + { allKeys.Add(key); } } cachedRecords.Add(record); - } + } - var headers = allKeys.OrderBy(key => key).ToList(); - if (!CsvConfig>.OmitHeaders) { + var headers = allKeys.OrderBy(key => key).ToList(); + if (!CsvConfig>.OmitHeaders) + { WriteRow(writer, headers); } - foreach (var cachedRecord in cachedRecords) { - var fullRecord = new List(); - foreach (var header in headers) { - fullRecord.Add(cachedRecord.ContainsKey(header) - ? cachedRecord[header] - : null); - } + foreach (var cachedRecord in cachedRecords) + { + var fullRecord = headers.ConvertAll(header => + cachedRecord.ContainsKey(header) ? cachedRecord[header] : null); WriteRow(writer, fullRecord); - } - } + } + } } public static class CsvWriter { public static bool HasAnyEscapeChars(string value) { - return CsvConfig.EscapeStrings.Any(value.Contains); + return !string.IsNullOrEmpty(value) + && (CsvConfig.EscapeStrings.Any(value.Contains) + || value[0] == JsWriter.ListStartChar + || value[0] == JsWriter.MapStartChar); } internal static void WriteItemSeperatorIfRanOnce(TextWriter writer, ref bool ranOnce) @@ -101,197 +138,256 @@ internal static void WriteItemSeperatorIfRanOnce(TextWriter writer, ref bool ran } } - internal class CsvWriter - { - public const char DelimiterChar = ','; + public class CsvWriter + { + public const char DelimiterChar = ','; - public static List Headers { get; set; } + public static List Headers { get; set; } - internal static List> PropertyGetters; + internal static List> PropertyGetters; + internal static List PropertyInfos; - private static readonly WriteObjectDelegate OptimizedWriter; + private static readonly WriteObjectDelegate OptimizedWriter; - static CsvWriter() - { - if (typeof(T) == typeof(string)) - { - OptimizedWriter = (w, o) => WriteRow(w, (IEnumerable)o); - return; - } + static CsvWriter() + { + if (typeof(T) == typeof(string)) + { + OptimizedWriter = (w, o) => WriteRow(w, (IEnumerable)o); + return; + } - Reset(); - } + Reset(); + } - internal static void Reset() - { - Headers = new List(); + internal static void Reset() + { + Headers = new List(); - PropertyGetters = new List>(); - var isDataContract = typeof(T).IsDto(); - foreach (var propertyInfo in TypeConfig.Properties) - { - if (!propertyInfo.CanRead || propertyInfo.GetGetMethod() == null) continue; - if (!TypeSerializer.CanCreateFromString(propertyInfo.PropertyType)) continue; + PropertyGetters = new List>(); + PropertyInfos = new List(); + foreach (var propertyInfo in TypeConfig.Properties) + { + if (!propertyInfo.CanRead || propertyInfo.GetGetMethod(nonPublic:true) == null) continue; + if (!TypeSerializer.CanCreateFromString(propertyInfo.PropertyType)) continue; - PropertyGetters.Add(propertyInfo.GetValueGetter()); + PropertyGetters.Add(propertyInfo.CreateGetter()); + PropertyInfos.Add(propertyInfo); + var propertyName = propertyInfo.Name; - if (isDataContract) + var dcsDataMemberName = propertyInfo.GetDataMemberName(); + if (dcsDataMemberName != null) + propertyName = dcsDataMemberName; + Headers.Add(propertyName); + } + } + + internal static void ConfigureCustomHeaders(Dictionary customHeadersMap) + { + Reset(); + + for (var i = Headers.Count - 1; i >= 0; i--) + { + var oldHeader = Headers[i]; + if (!customHeadersMap.TryGetValue(oldHeader, out var newHeaderValue)) { - var dcsDataMember = propertyInfo.GetDataMember(); - if (dcsDataMember != null && dcsDataMember.Name != null) - { - propertyName = dcsDataMember.Name; - } + Headers.RemoveAt(i); + PropertyGetters.RemoveAt(i); } - Headers.Add(propertyName); - } - } - - internal static void ConfigureCustomHeaders(Dictionary customHeadersMap) - { - Reset(); - - for (var i = Headers.Count - 1; i >= 0; i--) - { - var oldHeader = Headers[i]; - string newHeaderValue; - if (!customHeadersMap.TryGetValue(oldHeader, out newHeaderValue)) - { - Headers.RemoveAt(i); - PropertyGetters.RemoveAt(i); - } - else - { - Headers[i] = newHeaderValue.EncodeJsv(); - } - } - } - - private static List GetSingleRow(IEnumerable records, Type recordType) - { - var row = new List(); - foreach (var value in records) - { - var strValue = recordType == typeof(string) - ? value as string - : TypeSerializer.SerializeToString(value); - - row.Add(strValue); - } - return row; - } - - public static List> GetRows(IEnumerable records) - { - var rows = new List>(); - - if (records == null) return rows; - - if (typeof(T).IsValueType || typeof(T) == typeof(string)) - { - rows.Add(GetSingleRow(records, typeof(T))); - return rows; - } - - foreach (var record in records) - { - var row = new List(); - foreach (var propertyGetter in PropertyGetters) - { - var value = propertyGetter(record) ?? ""; - - var strValue = value.GetType() == typeof(string) - ? (string)value - : TypeSerializer.SerializeToString(value); - - row.Add(strValue); - } - rows.Add(row); - } - - return rows; - } - - public static void WriteObject(TextWriter writer, object records) - { - Write(writer, (IEnumerable)records); - } + else + { + Headers[i] = newHeaderValue.EncodeJsv(); + } + } + } + + private static List GetSingleRow(IEnumerable records, Type recordType) + { + var row = new List(); + foreach (var value in records) + { + var strValue = recordType == typeof(string) + ? value as string + : TypeSerializer.SerializeToString(value); + + row.Add(strValue); + } + return row; + } + + public static List> GetRows(IEnumerable records) + { + var rows = new List>(); + + if (records == null) return rows; + + if (typeof(T).IsValueType || typeof(T) == typeof(string)) + { + rows.Add(GetSingleRow(records, typeof(T))); + return rows; + } + + foreach (var record in records) + { + var row = new List(); + foreach (var propertyGetter in PropertyGetters) + { + var value = propertyGetter(record) ?? ""; + + var valueStr = value as string; + var strValue = valueStr ?? TypeSerializer.SerializeToString(value); + + row.Add(strValue); + } + rows.Add(row); + } + + return rows; + } + + public static void WriteObject(TextWriter writer, object records) + { + if (writer == null) return; //AOT + + Write(writer, (IEnumerable)records); + } public static void WriteObjectRow(TextWriter writer, object record) { - WriteRow(writer, (T) record); + if (writer == null) return; //AOT + + WriteRow(writer, (T)record); } - public static void Write(TextWriter writer, IEnumerable records) - { - if (writer == null) return; //AOT - - if (typeof(T) == typeof(Dictionary)) - { - CsvDictionaryWriter.Write(writer, (IEnumerable>)records); - return; - } - - if (typeof(T) == typeof(Dictionary)) - { - CsvDictionaryWriter.Write(writer, (IEnumerable>)records); - return; - } - - if (OptimizedWriter != null) - { - OptimizedWriter(writer, records); - return; - } - - if (!CsvConfig.OmitHeaders && Headers.Count > 0) - { - var ranOnce = false; - foreach (var header in Headers) - { - CsvWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - - writer.Write(header); - } - writer.Write(CsvConfig.RowSeparatorString); - } - - if (records == null) return; - - if (typeof(T).IsValueType || typeof(T) == typeof(string)) - { - var singleRow = GetSingleRow(records, typeof(T)); - WriteRow(writer, singleRow); - return; - } - - var row = new string[Headers.Count]; - foreach (var record in records) - { - for (var i = 0; i < PropertyGetters.Count; i++) - { - var propertyGetter = PropertyGetters[i]; - var value = propertyGetter(record) ?? ""; - - var strValue = value.GetType() == typeof(string) - ? (string)value - : TypeSerializer.SerializeToString(value); - - row[i] = strValue; - } - WriteRow(writer, row); - } - } - - public static void WriteRow(TextWriter writer, T row) - { - if (writer == null) return; //AOT - - Write(writer, new[] { row }); - } + public static void Write(TextWriter writer, IEnumerable records) + { + if (writer == null) return; //AOT + if (records == null) return; + + if (typeof(T) == typeof(Dictionary) || typeof(T) == typeof(IDictionary)) + { + CsvDictionaryWriter.Write(writer, (IEnumerable>)records); + return; + } + + if (typeof(T).IsAssignableFrom(typeof(Dictionary))) //also does `object` + { + var dynamicList = records.Select(x => x.ToObjectDictionary()).ToList(); + CsvDictionaryWriter.Write(writer, dynamicList); + return; + } + + if (OptimizedWriter != null) + { + OptimizedWriter(writer, records); + return; + } + + var recordsList = records.ToList(); + + var headers = Headers; + var propGetters = PropertyGetters; + var treatAsSingleRow = typeof(T).IsValueType || typeof(T) == typeof(string); + + if (!treatAsSingleRow && JsConfig.ExcludeDefaultValues) + { + var hasValues = new bool[headers.Count]; + var defaultValues = new object[headers.Count]; + for (var i = 0; i < PropertyInfos.Count; i++) + { + defaultValues[i] = PropertyInfos[i].PropertyType.GetDefaultValue(); + } + + foreach (var record in recordsList) + { + for (var i = 0; i < propGetters.Count; i++) + { + var propGetter = propGetters[i]; + var value = propGetter(record); + + if (value != null && !value.Equals(defaultValues[i])) + hasValues[i] = true; + } + } + + if (hasValues.Any(x => x == false)) + { + var newHeaders = new List(); + var newGetters = new List>(); + + for (int i = 0; i < hasValues.Length; i++) + { + if (hasValues[i]) + { + newHeaders.Add(headers[i]); + newGetters.Add(propGetters[i]); + } + } + + headers = newHeaders; + propGetters = newGetters; + } + } + + if (!CsvConfig.OmitHeaders && headers.Count > 0) + { + var ranOnce = false; + foreach (var header in headers) + { + CsvWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + + writer.Write(header); + } + writer.Write(CsvConfig.RowSeparatorString); + } + + if (treatAsSingleRow) + { + var singleRow = GetSingleRow(recordsList, typeof(T)); + WriteRow(writer, singleRow); + return; + } + + var row = new string[headers.Count]; + foreach (var record in recordsList) + { + for (var i = 0; i < propGetters.Count; i++) + { + var propGetter = propGetters[i]; + var value = propGetter(record) ?? ""; + + var strValue = value is string s + ? s + : TypeSerializer.SerializeToString(value).StripQuotes(); + + row[i] = strValue; + } + WriteRow(writer, row); + } + } + + public static void WriteRow(TextWriter writer, T row) + { + if (writer == null) return; //AOT + + if (row is IEnumerable> kvps) + { + CsvDictionaryWriter.Write(writer, kvps); + } + else if (row is IEnumerable> kvpStrings) + { + CsvDictionaryWriter.Write(writer, kvpStrings); + } + else + { + Write(writer, new[] { row }); + } + } public static void WriteRow(TextWriter writer, IEnumerable row) { + if (writer == null) return; //AOT + var ranOnce = false; foreach (var field in row) { @@ -299,28 +395,30 @@ public static void WriteRow(TextWriter writer, IEnumerable row) writer.Write(field.ToCsvField()); } - writer.WriteLine(); + writer.Write(CsvConfig.RowSeparatorString); } - public static void Write(TextWriter writer, IEnumerable> rows) - { - if (Headers.Count > 0) - { - var ranOnce = false; - foreach (var header in Headers) - { - CsvWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); - - writer.Write(header); - } - writer.Write(CsvConfig.RowSeparatorString); - } - - foreach (var row in rows) - { - WriteRow(writer, row); - } - } - } + public static void Write(TextWriter writer, IEnumerable> rows) + { + if (writer == null) return; //AOT + + if (Headers.Count > 0) + { + var ranOnce = false; + foreach (var header in Headers) + { + CsvWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); + + writer.Write(header); + } + writer.Write(CsvConfig.RowSeparatorString); + } + + foreach (var row in rows) + { + WriteRow(writer, row); + } + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/DateTimeExtensions.cs b/src/ServiceStack.Text/DateTimeExtensions.cs index cb4dd6fb5..23910f6ac 100644 --- a/src/ServiceStack.Text/DateTimeExtensions.cs +++ b/src/ServiceStack.Text/DateTimeExtensions.cs @@ -5,138 +5,182 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; +using System.Globalization; using ServiceStack.Text.Common; namespace ServiceStack.Text { - /// - /// A fast, standards-based, serialization-issue free DateTime serailizer. - /// - public static class DateTimeExtensions - { - public const long UnixEpoch = 621355968000000000L; - private static readonly DateTime UnixEpochDateTimeUtc = new DateTime(UnixEpoch, DateTimeKind.Utc); - private static readonly DateTime UnixEpochDateTimeUnspecified = new DateTime(UnixEpoch, DateTimeKind.Unspecified); - - public static long ToUnixTime(this DateTime dateTime) - { - return (dateTime.ToStableUniversalTime().Ticks - UnixEpoch) / TimeSpan.TicksPerSecond; - } - - public static DateTime FromUnixTime(this double unixTime) - { - return UnixEpochDateTimeUtc + TimeSpan.FromSeconds(unixTime); - } - - public static long ToUnixTimeMs(this DateTime dateTime) - { - return (dateTime.ToStableUniversalTime().Ticks - UnixEpoch) / TimeSpan.TicksPerMillisecond; - } + /// + /// A fast, standards-based, serialization-issue free DateTime serializer. + /// + public static class DateTimeExtensions + { + public const long UnixEpoch = 621355968000000000L; + private static readonly DateTime UnixEpochDateTimeUtc = new DateTime(UnixEpoch, DateTimeKind.Utc); + private static readonly DateTime UnixEpochDateTimeUnspecified = new DateTime(UnixEpoch, DateTimeKind.Unspecified); + private static readonly DateTime MinDateTimeUtc = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static DateTime FromUnixTime(this int unixTime) + { + return UnixEpochDateTimeUtc + TimeSpan.FromSeconds(unixTime); + } + + public static DateTime FromUnixTime(this double unixTime) + { + return UnixEpochDateTimeUtc + TimeSpan.FromSeconds(unixTime); + } + + public static DateTime FromUnixTime(this long unixTime) + { + return UnixEpochDateTimeUtc + TimeSpan.FromSeconds(unixTime); + } + + public static long ToUnixTimeMsAlt(this DateTime dateTime) + { + return (dateTime.ToStableUniversalTime().Ticks - UnixEpoch) / TimeSpan.TicksPerMillisecond; + } + + public static long ToUnixTimeMs(this DateTimeOffset dateTimeOffset) => + (long)ToDateTimeSinceUnixEpoch(dateTimeOffset.UtcDateTime).TotalMilliseconds; + + public static long ToUnixTimeMs(this DateTime dateTime) + { + var universal = ToDateTimeSinceUnixEpoch(dateTime); + return (long)universal.TotalMilliseconds; + } + + public static long ToUnixTime(this DateTime dateTime) + { + return (dateTime.ToDateTimeSinceUnixEpoch().Ticks) / TimeSpan.TicksPerSecond; + } + + private static TimeSpan ToDateTimeSinceUnixEpoch(this DateTime dateTime) + { + var dtUtc = dateTime; + if (dateTime.Kind != DateTimeKind.Utc) + { + dtUtc = dateTime.Kind == DateTimeKind.Unspecified && dateTime > DateTime.MinValue && dateTime < DateTime.MaxValue + ? DateTime.SpecifyKind(dateTime.Subtract(DateTimeSerializer.LocalTimeZone.GetUtcOffset(dateTime)), DateTimeKind.Utc) + : dateTime.ToStableUniversalTime(); + } + + var universal = dtUtc.Subtract(UnixEpochDateTimeUtc); + return universal; + } public static long ToUnixTimeMs(this long ticks) { return (ticks - UnixEpoch) / TimeSpan.TicksPerMillisecond; } - public static DateTime FromUnixTimeMs(this double msSince1970) - { - return UnixEpochDateTimeUtc + TimeSpan.FromMilliseconds(msSince1970); - } - - public static DateTime FromUnixTimeMs(this long msSince1970) - { - return UnixEpochDateTimeUtc + TimeSpan.FromMilliseconds(msSince1970); - } - - public static DateTime FromUnixTimeMs(this long msSince1970, TimeSpan offset) - { - return UnixEpochDateTimeUnspecified + TimeSpan.FromMilliseconds(msSince1970) + offset; - } - - public static DateTime FromUnixTimeMs(this double msSince1970, TimeSpan offset) - { - return UnixEpochDateTimeUnspecified + TimeSpan.FromMilliseconds(msSince1970) + offset; - } - - public static DateTime FromUnixTimeMs(string msSince1970) - { - long ms; - if (long.TryParse(msSince1970, out ms)) return ms.FromUnixTimeMs(); - - // Do we really need to support fractional unix time ms time strings?? - return double.Parse(msSince1970).FromUnixTimeMs(); - } - - public static DateTime FromUnixTimeMs(string msSince1970, TimeSpan offset) - { - long ms; - if (long.TryParse(msSince1970, out ms)) return ms.FromUnixTimeMs(offset); - - // Do we really need to support fractional unix time ms time strings?? - return double.Parse(msSince1970).FromUnixTimeMs(offset); - } - - public static DateTime RoundToMs(this DateTime dateTime) - { - return new DateTime((dateTime.Ticks / TimeSpan.TicksPerMillisecond) * TimeSpan.TicksPerMillisecond); - } - - public static DateTime RoundToSecond(this DateTime dateTime) - { - return new DateTime((dateTime.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond); - } - - public static string ToShortestXsdDateTimeString(this DateTime dateTime) - { - return DateTimeSerializer.ToShortestXsdDateTimeString(dateTime); - } - - public static DateTime FromShortestXsdDateTimeString(this string xsdDateTime) - { - return DateTimeSerializer.ParseShortestXsdDateTime(xsdDateTime); - } - - public static bool IsEqualToTheSecond(this DateTime dateTime, DateTime otherDateTime) - { - return dateTime.ToStableUniversalTime().RoundToSecond().Equals(otherDateTime.ToStableUniversalTime().RoundToSecond()); - } - - public static string ToTimeOffsetString(this TimeSpan offset, bool includeColon = false) - { - var sign = offset < TimeSpan.Zero ? "-" : "+"; - var hours = Math.Abs(offset.Hours); - var minutes = Math.Abs(offset.Minutes); - var separator = includeColon ? ":" : ""; - return string.Format("{0}{1:00}{2}{3:00}", sign, hours, separator, minutes); - } - - public static TimeSpan FromTimeOffsetString(this string offsetString) - { - if (!offsetString.Contains(":")) - offsetString = offsetString.Insert(offsetString.Length - 2, ":"); - - offsetString = offsetString.TrimStart('+'); - - return TimeSpan.Parse(offsetString); - } - - public static DateTime ToStableUniversalTime(this DateTime dateTime) - { -#if SILVERLIGHT - // Silverlight 3, 4 and 5 all work ok with DateTime.ToUniversalTime, but have no TimeZoneInfo.ConverTimeToUtc implementation. - return dateTime.ToUniversalTime(); -#else - // .Net 2.0 - 3.5 has an issue with DateTime.ToUniversalTime, but works ok with TimeZoneInfo.ConvertTimeToUtc. - // .Net 4.0+ does this under the hood anyway. - return TimeZoneInfo.ConvertTimeToUtc(dateTime); +#if NET6_0 + public static long ToUnixTimeMs(this DateOnly dateOnly) => dateOnly.ToDateTime(default, DateTimeKind.Utc).ToUnixTimeMs(); + public static long ToUnixTime(this DateOnly dateOnly) => dateOnly.ToDateTime(default, DateTimeKind.Utc).ToUnixTime(); #endif - } + + public static DateTime FromUnixTimeMs(this double msSince1970) + { + return UnixEpochDateTimeUtc + TimeSpan.FromMilliseconds(msSince1970); + } + + public static DateTime FromUnixTimeMs(this long msSince1970) + { + return UnixEpochDateTimeUtc + TimeSpan.FromMilliseconds(msSince1970); + } + + public static DateTime FromUnixTimeMs(this long msSince1970, TimeSpan offset) + { + return DateTime.SpecifyKind(UnixEpochDateTimeUnspecified + TimeSpan.FromMilliseconds(msSince1970) + offset, DateTimeKind.Local); + } + + public static DateTime FromUnixTimeMs(this double msSince1970, TimeSpan offset) + { + return DateTime.SpecifyKind(UnixEpochDateTimeUnspecified + TimeSpan.FromMilliseconds(msSince1970) + offset, DateTimeKind.Local); + } + + public static DateTime FromUnixTimeMs(string msSince1970) + { + long ms; + if (long.TryParse(msSince1970, out ms)) return ms.FromUnixTimeMs(); + + // Do we really need to support fractional unix time ms time strings?? + return double.Parse(msSince1970).FromUnixTimeMs(); + } + + public static DateTime FromUnixTimeMs(string msSince1970, TimeSpan offset) + { + long ms; + if (long.TryParse(msSince1970, out ms)) return ms.FromUnixTimeMs(offset); + + // Do we really need to support fractional unix time ms time strings?? + return double.Parse(msSince1970).FromUnixTimeMs(offset); + } + + public static DateTime RoundToMs(this DateTime dateTime) + { + return new DateTime((dateTime.Ticks / TimeSpan.TicksPerMillisecond) * TimeSpan.TicksPerMillisecond, dateTime.Kind); + } + + public static DateTime RoundToSecond(this DateTime dateTime) + { + return new DateTime((dateTime.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond, dateTime.Kind); + } + + public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan) + { + return dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks)); + } + + public static string ToShortestXsdDateTimeString(this DateTime dateTime) + { + return DateTimeSerializer.ToShortestXsdDateTimeString(dateTime); + } + + public static DateTime FromShortestXsdDateTimeString(this string xsdDateTime) + { + return DateTimeSerializer.ParseShortestXsdDateTime(xsdDateTime); + } + + public static bool IsEqualToTheSecond(this DateTime dateTime, DateTime otherDateTime) + { + return dateTime.ToStableUniversalTime().RoundToSecond().Equals(otherDateTime.ToStableUniversalTime().RoundToSecond()); + } + + public static string ToTimeOffsetString(this TimeSpan offset, string seperator = "") + { + var hours = Math.Abs(offset.Hours).ToString(CultureInfo.InvariantCulture); + var minutes = Math.Abs(offset.Minutes).ToString(CultureInfo.InvariantCulture); + return (offset < TimeSpan.Zero ? "-" : "+") + + (hours.Length == 1 ? "0" + hours : hours) + + seperator + + (minutes.Length == 1 ? "0" + minutes : minutes); + } + + public static TimeSpan FromTimeOffsetString(this string offsetString) + { + if (!offsetString.Contains(":")) + offsetString = offsetString.Insert(offsetString.Length - 2, ":"); + + offsetString = offsetString.TrimStart('+'); + + return TimeSpan.Parse(offsetString); + } + + public static DateTime ToStableUniversalTime(this DateTime dateTime) + { + if (dateTime.Kind == DateTimeKind.Utc) + return dateTime; + if (dateTime == DateTime.MinValue) + return MinDateTimeUtc; + + return PclExport.Instance.ToStableUniversalTime(dateTime); + } public static string FmtSortableDate(this DateTime from) { @@ -150,8 +194,8 @@ public static string FmtSortableDateTime(this DateTime from) public static DateTime LastMonday(this DateTime from) { - var modayOfWeekBefore = from.Date.AddDays(-(int)from.DayOfWeek - 6); - return modayOfWeekBefore; + var mondayOfWeek = from.Date.AddDays(-(int)from.DayOfWeek + 1); + return mondayOfWeek; } public static DateTime StartOfLastMonth(this DateTime from) @@ -161,7 +205,8 @@ public static DateTime StartOfLastMonth(this DateTime from) public static DateTime EndOfLastMonth(this DateTime from) { - return new DateTime(from.Date.Year, from.Date.Month, 1).AddDays(-1); + return new DateTime(from.Date.Year, from.Date.Month, 1).AddDays(-1); } } -} \ No newline at end of file + +} diff --git a/src/ServiceStack.Text/DefaultMemory.cs b/src/ServiceStack.Text/DefaultMemory.cs new file mode 100644 index 000000000..cc05301b1 --- /dev/null +++ b/src/ServiceStack.Text/DefaultMemory.cs @@ -0,0 +1,1058 @@ +using System; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text.Common; +using ServiceStack.Text.Json; +using ServiceStack.Text.Pools; + +namespace ServiceStack.Text +{ + public sealed class DefaultMemory : MemoryProvider + { + private static DefaultMemory provider; + public static DefaultMemory Provider => provider ?? (provider = new DefaultMemory()); + private DefaultMemory() { } + + public static void Configure() => Instance = Provider; + + public override bool ParseBoolean(ReadOnlySpan value) + { + if (!value.TryParseBoolean(out bool result)) + throw new FormatException(BadFormat); + + return result; + } + + public override bool TryParseBoolean(ReadOnlySpan value, out bool result) + { + result = false; + + if (value.CompareIgnoreCase(bool.TrueString.AsSpan())) + { + result = true; + return true; + } + + return value.CompareIgnoreCase(bool.FalseString.AsSpan()); + } + + public override bool TryParseDecimal(ReadOnlySpan value, out decimal result) => + TryParseDecimal(value, allowThousands: true, out result); + + public override decimal ParseDecimal(ReadOnlySpan value) => ParseDecimal(value, allowThousands: true); + + public override decimal ParseDecimal(ReadOnlySpan value, bool allowThousands) + { + if (!TryParseDecimal(value, allowThousands, out var result)) + throw new FormatException(BadFormat); + + return result; + } + + public override bool TryParseFloat(ReadOnlySpan value, out float result) => float.TryParse( + value.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, + out result); + + public override float ParseFloat(ReadOnlySpan value) => float.Parse(value.ToString(), + NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override bool TryParseDouble(ReadOnlySpan value, out double result) => double.TryParse( + value.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, + out result); + + public override double ParseDouble(ReadOnlySpan value) => double.Parse(value.ToString(), + NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override sbyte ParseSByte(ReadOnlySpan value) => SignedInteger.ParseSByte(value); + + public override byte ParseByte(ReadOnlySpan value) => UnsignedInteger.ParseByte(value); + + public override short ParseInt16(ReadOnlySpan value) => SignedInteger.ParseInt16(value); + + public override ushort ParseUInt16(ReadOnlySpan value) => UnsignedInteger.ParseUInt16(value); + + public override int ParseInt32(ReadOnlySpan value) => SignedInteger.ParseInt32(value); + + public override uint ParseUInt32(ReadOnlySpan value) => UnsignedInteger.ParseUInt32(value); + + public override uint ParseUInt32(ReadOnlySpan value, NumberStyles style) => + uint.Parse(value.ToString(), style); + + public override long ParseInt64(ReadOnlySpan value) => SignedInteger.ParseInt64(value); + + public override ulong ParseUInt64(ReadOnlySpan value) => UnsignedInteger.ParseUInt64(value); + + internal static Exception CreateOverflowException(long maxValue) => + new OverflowException(string.Format(OverflowMessage, SignedMaxValueToIntType(maxValue))); + + internal static Exception CreateOverflowException(ulong maxValue) => + new OverflowException(string.Format(OverflowMessage, UnsignedMaxValueToIntType(maxValue))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string SignedMaxValueToIntType(long maxValue) + { + switch (maxValue) + { + case sbyte.MaxValue: + return nameof(SByte); + case short.MaxValue: + return nameof(Int16); + case int.MaxValue: + return nameof(Int32); + case long.MaxValue: + return nameof(Int64); + default: + return "Unknown"; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string UnsignedMaxValueToIntType(ulong maxValue) + { + switch (maxValue) + { + case byte.MaxValue: + return nameof(Byte); + case ushort.MaxValue: + return nameof(UInt16); + case uint.MaxValue: + return nameof(UInt32); + case ulong.MaxValue: + return nameof(UInt64); + default: + return "Unknown"; + } + } + + public static bool TryParseDecimal(ReadOnlySpan value, bool allowThousands, out decimal result) + { + result = 0; + + if (value.Length == 0) + return false; + + ulong preResult = 0; + bool isLargeNumber = false; + int i = 0; + int end = value.Length; + var state = ParseState.LeadingWhite; + bool negative = false; + bool noIntegerPart = false; + sbyte scale = 0; + + while (i < end) + { + var c = value[i++]; + + switch (state) + { + case ParseState.LeadingWhite: + if (JsonUtils.IsWhiteSpace(c)) + break; + + if (c == '-') + { + negative = true; + state = ParseState.Sign; + } + else if (c == '.') + { + noIntegerPart = true; + state = ParseState.FractionNumber; + + if (i == end) + return false; + } + else if (c == '0') + { + state = ParseState.DecimalPoint; + } + else if (c > '0' && c <= '9') + { + preResult = (ulong) (c - '0'); + state = ParseState.Number; + } + else return false; + + break; + case ParseState.Sign: + if (c == '.') + { + noIntegerPart = true; + state = ParseState.FractionNumber; + + if (i == end) + return false; + } + else if (c == '0') + { + state = ParseState.DecimalPoint; + } + else if (c > '0' && c <= '9') + { + preResult = (ulong) (c - '0'); + state = ParseState.Number; + } + else return false; + + break; + case ParseState.Number: + if (c == '.') + { + state = ParseState.FractionNumber; + } + else if (c >= '0' && c <= '9') + { + if (isLargeNumber) + { + checked + { + result = 10 * result + (c - '0'); + } + } + else + { + preResult = 10 * preResult + (ulong) (c - '0'); + if (preResult > ulong.MaxValue / 10 - 10) + { + isLargeNumber = true; + result = preResult; + } + } + } + else if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else if (allowThousands && c == ',') { } + else return false; + + break; + case ParseState.DecimalPoint: + if (c == '.') + { + state = ParseState.FractionNumber; + } + else return false; + + break; + case ParseState.FractionNumber: + if (JsonUtils.IsWhiteSpace(c)) + { + if (noIntegerPart) + return false; + state = ParseState.TrailingWhite; + } + else if (c == 'e' || c == 'E') + { + if (noIntegerPart && scale == 0) + return false; + state = ParseState.Exponent; + } + else if (c >= '0' && c <= '9') + { + if (isLargeNumber) + { + checked + { + result = 10 * result + (c - '0'); + } + } + else + { + preResult = 10 * preResult + (ulong) (c - '0'); + if (preResult > ulong.MaxValue / 10 - 10) + { + isLargeNumber = true; + result = preResult; + } + } + + scale++; + } + else return false; + + break; + case ParseState.Exponent: + bool expNegative = false; + if (c == '-') + { + if (i == end) + return false; + + expNegative = true; + c = value[i++]; + } + else if (c == '+') + { + if (i == end) + return false; + c = value[i++]; + } + + //skip leading zeroes + while (c == '0' && i < end) c = value[i++]; + + if (c > '0' && c <= '9') + { + var exp = SignedInteger.ParseInt64(value.Slice(i - 1, end - i + 1)); + if (exp < sbyte.MinValue || exp > sbyte.MaxValue) + return false; + + if (!expNegative) + { + exp = (sbyte) -exp; + } + + if (exp >= 0 || scale > -exp) + { + scale += (sbyte) exp; + } + else + { + for (int j = 0; j < -exp - scale; j++) + { + if (isLargeNumber) + { + checked + { + result = 10 * result; + } + } + else + { + preResult = 10 * preResult; + if (preResult > ulong.MaxValue / 10) + { + isLargeNumber = true; + result = preResult; + } + } + } + + scale = 0; + } + + //set i to end of string, because ParseInt16 eats number and all trailing whites + i = end; + } + else return false; + + break; + case ParseState.TrailingWhite: + if (!JsonUtils.IsWhiteSpace(c)) + return false; + break; + } + } + + if (!isLargeNumber) + { + var mid = (int) (preResult >> 32); + var lo = (int) (preResult & 0xffffffff); + result = new decimal(lo, mid, 0, negative, (byte) scale); + } + else + { + var bits = decimal.GetBits(result); + result = new decimal(bits[0], bits[1], bits[2], negative, (byte) scale); + } + + return true; + } + + private static readonly byte[] lo16 = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, + 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, + 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, + 13, 14, 15 + }; + + private static readonly byte[] hi16 = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 0, 16, + 32, 48, 64, 80, 96, 112, 128, 144, 255, 255, + 255, 255, 255, 255, 255, 160, 176, 192, 208, 224, + 240, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 160, 176, 192, + 208, 224, 240 + }; + + public override Guid ParseGuid(ReadOnlySpan value) + { + if (value.IsEmpty) + throw new FormatException(BadFormat); + + //Guid can be in one of 3 forms: + //1. General `{dddddddd-dddd-dddd-dddd-dddddddddddd}` or `(dddddddd-dddd-dddd-dddd-dddddddddddd)` 8-4-4-4-12 chars + //2. Hex `{0xdddddddd,0xdddd,0xdddd,{0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd}}` 8-4-4-8x2 chars + //3. No style `dddddddddddddddddddddddddddddddd` 32 chars + + int i = 0; + int end = value.Length; + while (i < end && JsonUtils.IsWhiteSpace(value[i])) i++; + + if (i == end) + throw new FormatException(BadFormat); + + var result = ParseGeneralStyleGuid(value.Slice(i, end - i), out var guidLen); + i += guidLen; + + while (i < end && JsonUtils.IsWhiteSpace(value[i])) i++; + + if (i < end) + throw new FormatException(BadFormat); + + return result; + } + + public override void Write(Stream stream, ReadOnlyMemory value) + { + var bytes = BufferPool.GetBuffer(Encoding.UTF8.GetMaxByteCount(value.Length)); + try + { + var chars = value.ToArray(); + int bytesCount = Encoding.UTF8.GetBytes(chars, 0, chars.Length, bytes, 0); + stream.Write(bytes, 0, bytesCount); + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + + public override void Write(Stream stream, ReadOnlyMemory value) + { + if (MemoryMarshal.TryGetArray(value, out var segment) && segment.Array != null) + { + byte[] bytes = BufferPool.GetBuffer(segment.Count); + try + { + stream.Write(segment.Array, 0, segment.Count); + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + else + { + var bytes = value.ToArray(); + stream.Write(bytes, 0, value.Length); + } + } + + public override Task WriteAsync(Stream stream, ReadOnlySpan value, CancellationToken token = default) + { + // encode the span into a buffer; this should never fail, so if it does: something + // is very very ill; don't stress about returning to the pool + byte[] bytes = BufferPool.GetBuffer(Encoding.UTF8.GetMaxByteCount(value.Length)); + var chars = value.ToArray(); + int bytesCount = Encoding.UTF8.GetBytes(chars, 0, chars.Length, bytes, 0); + // now do the write async - this returns to the pool + return WriteAsyncAndReturn(stream, bytes, 0, bytesCount, token); + } + + private static async Task WriteAsyncAndReturn(Stream stream, byte[] bytes, int offset, int count, CancellationToken token) + { + try + { + await stream.WriteAsync(bytes, offset, count, token).ConfigAwait(); + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + + public override Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default) => + WriteAsync(stream, value.Span, token); + + public override async Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default) + { + byte[] bytes = BufferPool.GetBuffer(value.Length); + try + { + value.CopyTo(bytes); + if (stream is MemoryStream ms) + { + // ReSharper disable once MethodHasAsyncOverloadWithCancellation + ms.Write(bytes, 0, value.Length); + } + else + { + await stream.WriteAsync(bytes, 0, value.Length, token).ConfigAwait(); + } + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + + public override object Deserialize(Stream stream, Type type, DeserializeStringSpanDelegate deserializer) + { + var fromPool = false; + + if (!(stream is MemoryStream ms)) + { + fromPool = true; + + if (stream.CanSeek) + stream.Position = 0; + + ms = stream.CopyToNewMemoryStream(); + } + + return Deserialize(ms, fromPool, type, deserializer); + } + + public override async Task DeserializeAsync(Stream stream, Type type, + DeserializeStringSpanDelegate deserializer) + { + var fromPool = false; + + if (!(stream is MemoryStream ms)) + { + fromPool = true; + + if (stream.CanSeek) + stream.Position = 0; + + ms = await stream.CopyToNewMemoryStreamAsync().ConfigAwait(); + } + + return Deserialize(ms, fromPool, type, deserializer); + } + + private static object Deserialize(MemoryStream ms, bool fromPool, Type type, + DeserializeStringSpanDelegate deserializer) + { + var bytes = ms.GetBufferAsBytes(); + var utf8 = CharPool.GetBuffer(Encoding.UTF8.GetCharCount(bytes, 0, (int) ms.Length)); + try + { + var charsWritten = Encoding.UTF8.GetChars(bytes, 0, (int) ms.Length, utf8, 0); + var ret = deserializer(type, new ReadOnlySpan(utf8, 0, charsWritten).WithoutBom()); + return ret; + } + finally + { + CharPool.ReleaseBufferToPool(ref utf8); + + if (fromPool) + ms.Dispose(); + } + } + + public override byte[] ParseBase64(ReadOnlySpan value) + { + return Convert.FromBase64String(value.ToString()); + } + + public override string ToBase64(ReadOnlyMemory value) + { + return MemoryMarshal.TryGetArray(value, out var segment) + ? Convert.ToBase64String(segment.Array, 0, segment.Count) + : Convert.ToBase64String(value.ToArray()); + } + + public override StringBuilder Append(StringBuilder sb, ReadOnlySpan value) + { + return sb.Append(value.ToArray()); + } + + public override int GetUtf8CharCount(ReadOnlySpan bytes) => + Encoding.UTF8.GetCharCount(bytes.ToArray()); //SLOW + + public override int GetUtf8ByteCount(ReadOnlySpan chars) => + Encoding.UTF8.GetByteCount(chars.ToArray()); //SLOW + + public override ReadOnlyMemory ToUtf8(ReadOnlySpan source) + { + var chars = source.ToArray(); + var bytes = new byte[Encoding.UTF8.GetByteCount(chars)]; + var bytesWritten = Encoding.UTF8.GetBytes(chars, 0, source.Length, bytes, 0); + return new ReadOnlyMemory(bytes, 0, bytesWritten); + } + + public override ReadOnlyMemory FromUtf8(ReadOnlySpan source) + { + var bytes = source.WithoutBom().ToArray(); + var chars = new char[Encoding.UTF8.GetCharCount(bytes)]; + var charsWritten = Encoding.UTF8.GetChars(bytes, 0, bytes.Length, chars, 0); + return new ReadOnlyMemory(chars, 0, charsWritten); + } + + public override int ToUtf8(ReadOnlySpan source, Span destination) + { + var chars = source.ToArray(); + var bytes = destination.ToArray(); + var bytesWritten = Encoding.UTF8.GetBytes(chars, 0, source.Length, bytes, 0); + new ReadOnlySpan(bytes, 0, bytesWritten).CopyTo(destination); + return bytesWritten; + } + + public override int FromUtf8(ReadOnlySpan source, Span destination) + { + var bytes = source.WithoutBom().ToArray(); + var chars = destination.ToArray(); + var charsWritten = Encoding.UTF8.GetChars(bytes, 0, bytes.Length, chars, 0); + new ReadOnlySpan(chars, 0, charsWritten).CopyTo(destination); + return charsWritten; + } + + public override byte[] ToUtf8Bytes(ReadOnlySpan source) => Encoding.UTF8.GetBytes(source.ToArray()); + + public override string FromUtf8Bytes(ReadOnlySpan source) => Encoding.UTF8.GetString(source.WithoutBom().ToArray()); + + public override MemoryStream ToMemoryStream(ReadOnlySpan source) => + MemoryStreamFactory.GetStream(source.ToArray()); + + private static Guid ParseGeneralStyleGuid(ReadOnlySpan value, out int len) + { + var buf = value; + var n = 0; + + int dash = 0; + len = 32; + bool hasParenthesis = false; + + if (value.Length < len) + throw new FormatException(BadFormat); + + var cs = value[0]; + if (cs == '{' || cs == '(') + { + n++; + len += 2; + hasParenthesis = true; + + if (buf[8 + n] != '-') + throw new FormatException(BadFormat); + } + + if (buf[8 + n] == '-') + { + if (buf[13 + n] != '-' + || buf[18 + n] != '-' + || buf[23 + n] != '-') + throw new FormatException(BadFormat); + + len += 4; + dash = 1; + } + + if (value.Length < len) + throw new FormatException(BadFormat); + + if (hasParenthesis) + { + var ce = buf[len - 1]; + + if ((cs != '{' || ce != '}') && (cs != '(' || ce != ')')) + throw new FormatException(BadFormat); + } + + int a; + short b, c; + byte d, e, f, g, h, i, j, k; + + byte a1 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte a2 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte a3 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte a4 = ParseHexByte(buf[n], buf[n + 1]); + a = (a1 << 24) + (a2 << 16) + (a3 << 8) + a4; + n += 2 + dash; + + byte b1 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte b2 = ParseHexByte(buf[n], buf[n + 1]); + b = (short) ((b1 << 8) + b2); + n += 2 + dash; + + byte c1 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte c2 = ParseHexByte(buf[n], buf[n + 1]); + c = (short) ((c1 << 8) + c2); + n += 2 + dash; + + d = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + e = ParseHexByte(buf[n], buf[n + 1]); + n += 2 + dash; + + f = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + g = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + h = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + i = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + j = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + k = ParseHexByte(buf[n], buf[n + 1]); + + return new Guid(a, b, c, d, e, f, g, h, i, j, k); + } + + private static byte ParseHexByte(char c1, char c2) + { + try + { + byte lo = lo16[c2]; + byte hi = hi16[c1]; + + if (lo == 255 || hi == 255) + throw new FormatException(BadFormat); + + return (byte) (hi + lo); + } + catch (IndexOutOfRangeException) + { + throw new FormatException(BadFormat); + } + } + } + + enum ParseState + { + LeadingWhite, + Sign, + Number, + DecimalPoint, + FractionNumber, + Exponent, + ExponentSign, + ExponentValue, + TrailingWhite + } + + internal static class SignedInteger where T : struct, IComparable, IEquatable, IConvertible + { + private static readonly TypeCode typeCode; + private static readonly long minValue; + private static readonly long maxValue; + + static SignedInteger() + { + typeCode = Type.GetTypeCode(typeof(T)); + + switch (typeCode) + { + case TypeCode.SByte: + minValue = sbyte.MinValue; + maxValue = sbyte.MaxValue; + break; + case TypeCode.Int16: + minValue = short.MinValue; + maxValue = short.MaxValue; + break; + case TypeCode.Int32: + minValue = int.MinValue; + maxValue = int.MaxValue; + break; + case TypeCode.Int64: + minValue = long.MinValue; + maxValue = long.MaxValue; + break; + default: + throw new NotSupportedException($"{typeof(T).Name} is not a signed integer"); + } + } + + internal static object ParseNullableObject(ReadOnlySpan value) + { + if (value.IsNullOrEmpty()) + return null; + + return ParseObject(value); + } + + internal static object ParseObject(ReadOnlySpan value) + { + var result = ParseInt64(value); + switch (typeCode) + { + case TypeCode.SByte: + return (sbyte) result; + case TypeCode.Int16: + return (short) result; + case TypeCode.Int32: + return (int) result; + default: + return result; + } + } + + public static sbyte ParseSByte(ReadOnlySpan value) => (sbyte) ParseInt64(value); + public static short ParseInt16(ReadOnlySpan value) => (short) ParseInt64(value); + public static int ParseInt32(ReadOnlySpan value) => (int) ParseInt64(value); + + public static long ParseInt64(ReadOnlySpan value) + { + if (value.IsEmpty) + throw new FormatException(MemoryProvider.BadFormat); + + long result = 0; + int i = 0; + int end = value.Length; + var state = ParseState.LeadingWhite; + bool negative = false; + + //skip leading whitespaces + while (i < end && JsonUtils.IsWhiteSpace(value[i])) i++; + + if (i == end) + throw new FormatException(MemoryProvider.BadFormat); + + //skip leading zeros + while (i < end && value[i] == '0') + { + state = ParseState.Number; + i++; + } + + while (i < end) + { + var c = value[i++]; + + switch (state) + { + case ParseState.LeadingWhite: + if (c == '-') + { + negative = true; + state = ParseState.Sign; + } + else if (c == '0') + { + state = ParseState.TrailingWhite; + } + else if (c > '0' && c <= '9') + { + result = -(c - '0'); + state = ParseState.Number; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + case ParseState.Sign: + if (c == '0') + { + state = ParseState.TrailingWhite; + } + else if (c > '0' && c <= '9') + { + result = -(c - '0'); + state = ParseState.Number; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + case ParseState.Number: + if (c >= '0' && c <= '9') + { + checked + { + result = 10 * result - (c - '0'); + } + + if (result < minValue + ) //check only minvalue, because in absolute value it's greater than maxvalue + throw DefaultMemory.CreateOverflowException(maxValue); + } + else if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + case ParseState.TrailingWhite: + if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + } + } + + if (state != ParseState.Number && state != ParseState.TrailingWhite) + throw new FormatException(MemoryProvider.BadFormat); + + if (negative) + return result; + + checked + { + result = -result; + } + + if (result > maxValue) + throw DefaultMemory.CreateOverflowException(maxValue); + + return result; + } + } + + internal static class UnsignedInteger where T : struct, IComparable, IEquatable, IConvertible + { + private static readonly TypeCode typeCode; + private static readonly ulong maxValue; + + static UnsignedInteger() + { + typeCode = Type.GetTypeCode(typeof(T)); + + switch (typeCode) + { + case TypeCode.Byte: + maxValue = byte.MaxValue; + break; + case TypeCode.UInt16: + maxValue = ushort.MaxValue; + break; + case TypeCode.UInt32: + maxValue = uint.MaxValue; + break; + case TypeCode.UInt64: + maxValue = ulong.MaxValue; + break; + default: + throw new NotSupportedException($"{typeof(T).Name} is not a signed integer"); + } + } + + internal static object ParseNullableObject(ReadOnlySpan value) + { + if (value.IsNullOrEmpty()) + return null; + + return ParseObject(value); + } + + internal static object ParseObject(ReadOnlySpan value) + { + var result = ParseUInt64(value); + switch (typeCode) + { + case TypeCode.Byte: + return (byte) result; + case TypeCode.UInt16: + return (ushort) result; + case TypeCode.UInt32: + return (uint) result; + default: + return result; + } + } + + public static byte ParseByte(ReadOnlySpan value) => (byte) ParseUInt64(value); + public static ushort ParseUInt16(ReadOnlySpan value) => (ushort) ParseUInt64(value); + public static uint ParseUInt32(ReadOnlySpan value) => (uint) ParseUInt64(value); + + internal static ulong ParseUInt64(ReadOnlySpan value) + { + if (value.IsEmpty) + throw new FormatException(MemoryProvider.BadFormat); + + ulong result = 0; + int i = 0; + int end = value.Length; + var state = ParseState.LeadingWhite; + + //skip leading whitespaces + while (i < end && JsonUtils.IsWhiteSpace(value[i])) i++; + + if (i == end) + throw new FormatException(MemoryProvider.BadFormat); + + //skip leading zeros + while (i < end && value[i] == '0') + { + state = ParseState.Number; + i++; + } + + while (i < end) + { + var c = value[i++]; + + switch (state) + { + case ParseState.LeadingWhite: + if (JsonUtils.IsWhiteSpace(c)) + break; + if (c == '0') + { + state = ParseState.TrailingWhite; + } + else if (c > '0' && c <= '9') + { + result = (ulong) (c - '0'); + state = ParseState.Number; + } + else throw new FormatException(MemoryProvider.BadFormat); + + + break; + case ParseState.Number: + if (c >= '0' && c <= '9') + { + checked + { + result = 10 * result + (ulong) (c - '0'); + } + + if (result > maxValue + ) //check only minvalue, because in absolute value it's greater than maxvalue + throw DefaultMemory.CreateOverflowException(maxValue); + } + else if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + case ParseState.TrailingWhite: + if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + } + } + + if (state != ParseState.Number && state != ParseState.TrailingWhite) + throw new FormatException(MemoryProvider.BadFormat); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Defer.cs b/src/ServiceStack.Text/Defer.cs new file mode 100644 index 000000000..2e29db4e4 --- /dev/null +++ b/src/ServiceStack.Text/Defer.cs @@ -0,0 +1,15 @@ +using System; + +namespace ServiceStack +{ + /// + /// Useful class for C# 8 using declaration to defer action til outside of scope, e.g: + /// using var defer = new Defer(() => response.Close()); + /// + public struct Defer : IDisposable + { + private readonly Action fn; + public Defer(Action fn) => this.fn = fn ?? throw new ArgumentNullException(nameof(fn)); + public void Dispose() => fn(); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/DirectStreamWriter.cs b/src/ServiceStack.Text/DirectStreamWriter.cs new file mode 100644 index 000000000..f090916fe --- /dev/null +++ b/src/ServiceStack.Text/DirectStreamWriter.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Text; + +namespace ServiceStack.Text +{ + public class DirectStreamWriter : TextWriter + { + private const int optimizedBufferLength = 256; + private const int maxBufferLength = 1024; + + private Stream stream; + private StreamWriter writer = null; + private byte[] curChar = new byte[1]; + private bool needFlush = false; + + private Encoding encoding; + public override Encoding Encoding => encoding; + + public DirectStreamWriter(Stream stream, Encoding encoding) + { + this.stream = stream; + this.encoding = encoding; + } + + public override void Write(string s) + { + if (s.IsNullOrEmpty()) + return; + + if (s.Length <= optimizedBufferLength) + { + if (needFlush) + { + writer.Flush(); + needFlush = false; + } + + byte[] buffer = Encoding.GetBytes(s); + stream.Write(buffer, 0, buffer.Length); + } else + { + if (writer == null) + writer = new StreamWriter(stream, Encoding, s.Length < maxBufferLength ? s.Length : maxBufferLength); + + writer.Write(s); + needFlush = true; + } + } + + public override void Write(char c) + { + if ((int)c < 128) + { + if (needFlush) + { + writer.Flush(); + needFlush = false; + } + + curChar[0] = (byte)c; + stream.Write(curChar, 0, 1); + } else + { + if (writer == null) + writer = new StreamWriter(stream, Encoding, optimizedBufferLength); + + writer.Write(c); + needFlush = true; + } + } + + public override void Flush() + { + if (writer != null) + { + writer.Flush(); + } + else + { + stream.Flush(); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/DynamicJson.cs b/src/ServiceStack.Text/DynamicJson.cs deleted file mode 100644 index 376e272b7..000000000 --- a/src/ServiceStack.Text/DynamicJson.cs +++ /dev/null @@ -1,90 +0,0 @@ -#if NET40 -using System.Collections.Generic; -using System.Dynamic; -using System.Linq; -using System.Text; -using ServiceStack.Text; - -namespace ServiceStack.Text -{ - public class DynamicJson : DynamicObject - { - private readonly IDictionary _hash = new Dictionary(); - - public static string Serialize(dynamic instance) - { - var json = JsonSerializer.SerializeToString(instance); - return json; - } - - public static dynamic Deserialize(string json) - { - // Support arbitrary nesting by using JsonObject - var deserialized = JsonSerializer.DeserializeFromString(json); - var hash = deserialized.ToDictionary, string, object>(entry => entry.Key, entry => entry.Value); - return new DynamicJson(hash); - } - - public DynamicJson(IEnumerable> hash) - { - _hash.Clear(); - foreach (var entry in hash) - { - _hash.Add(Underscored(entry.Key), entry.Value); - } - } - - public override bool TrySetMember(SetMemberBinder binder, object value) - { - var name = Underscored(binder.Name); - _hash[name] = value; - return _hash[name] == value; - } - - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - var name = Underscored(binder.Name); - return YieldMember(name, out result); - } - - public override string ToString() - { - return JsonSerializer.SerializeToString(_hash); - } - - private bool YieldMember(string name, out object result) - { - if (_hash.ContainsKey(name)) - { - var json = _hash[name].ToString(); - if (json.TrimStart(' ').StartsWith("{")) - { - result = Deserialize(json); - return true; - } - result = json; - return _hash[name] == result; - } - result = null; - return false; - } - - internal static string Underscored(IEnumerable pascalCase) - { - var sb = new StringBuilder(); - var i = 0; - foreach (var c in pascalCase) - { - if (char.IsUpper(c) && i > 0) - { - sb.Append("_"); - } - sb.Append(c); - i++; - } - return sb.ToString().ToLowerInvariant(); - } - } -} -#endif - diff --git a/src/ServiceStack.Text/DynamicNumber.cs b/src/ServiceStack.Text/DynamicNumber.cs new file mode 100644 index 000000000..33a637061 --- /dev/null +++ b/src/ServiceStack.Text/DynamicNumber.cs @@ -0,0 +1,777 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.CompilerServices; +using ServiceStack.Text; +using ServiceStack.Text.Common; + +namespace ServiceStack +{ + public interface IDynamicNumber + { + Type Type { get; } + object ConvertFrom(object value); + bool TryParse(string str, out object result); + string ToString(object value); + object DefaultValue { get; } + + object add(object lhs, object rhs); + object sub(object lhs, object rhs); + object mul(object lhs, object rhs); + object div(object lhs, object rhs); + object mod(object lhs, object rhs); + object pow(object lhs, object rhs); + object log(object lhs, object rhs); + object min(object lhs, object rhs); + object max(object lhs, object rhs); + int compareTo(object lhs, object rhs); + + object bitwiseAnd(object lhs, object rhs); + object bitwiseOr(object lhs, object rhs); + object bitwiseXOr(object lhs, object rhs); + object bitwiseLeftShift(object lhs, object rhs); + object bitwiseRightShift(object lhs, object rhs); + object bitwiseNot(object target); + } + + public class DynamicSByte : IDynamicNumber + { + public static DynamicSByte Instance = new DynamicSByte(); + public Type Type => typeof(sbyte); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte Convert(object value) => System.Convert.ToSByte(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToSByte(value); + + public bool TryParse(string str, out object result) + { + if (sbyte.TryParse(str, out sbyte value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(sbyte); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); + } + + public class DynamicByte : IDynamicNumber + { + public static DynamicByte Instance = new DynamicByte(); + public Type Type => typeof(byte); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte Convert(object value) => System.Convert.ToByte(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToByte(value); + + public bool TryParse(string str, out object result) + { + if (byte.TryParse(str, out byte value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(byte); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); + } + + public class DynamicShort : IDynamicNumber + { + public static DynamicShort Instance = new DynamicShort(); + public Type Type => typeof(short); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short Convert(object value) => System.Convert.ToInt16(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToInt16(value); + + public bool TryParse(string str, out object result) + { + if (short.TryParse(str, out short value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(short); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); + } + + public class DynamicUShort : IDynamicNumber + { + public static DynamicUShort Instance = new DynamicUShort(); + public Type Type => typeof(ushort); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort Convert(object value) => System.Convert.ToUInt16(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToUInt16(value); + + public bool TryParse(string str, out object result) + { + if (ushort.TryParse(str, out ushort value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(ushort); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); + } + + public class DynamicInt : IDynamicNumber + { + public static DynamicInt Instance = new DynamicInt(); + public Type Type => typeof(int); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Convert(object value) => System.Convert.ToInt32(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToInt32(value); + + public bool TryParse(string str, out object result) + { + if (int.TryParse(str, out int value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(int); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); + } + + public class DynamicUInt : IDynamicNumber + { + public static DynamicUInt Instance = new DynamicUInt(); + public Type Type => typeof(uint); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Convert(object value) => System.Convert.ToUInt32(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToUInt32(value); + + public bool TryParse(string str, out object result) + { + if (uint.TryParse(str, out uint value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(uint); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << (int)Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> (int)Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); + } + + public class DynamicLong : IDynamicNumber + { + public static DynamicLong Instance = new DynamicLong(); + public Type Type => typeof(long); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long Convert(object value) => System.Convert.ToInt64(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToInt64(value); + + public bool TryParse(string str, out object result) + { + if (long.TryParse(str, out long value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(long); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << (int)Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> (int)Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); + } + + public class DynamicULong : IDynamicNumber + { + public static DynamicULong Instance = new DynamicULong(); + public Type Type => typeof(ulong); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong Convert(object value) => System.Convert.ToUInt64(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToUInt64(value); + + public bool TryParse(string str, out object result) + { + if (ulong.TryParse(str, out ulong value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(ulong); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << (int)Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> (int)Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); + } + + public class DynamicFloat : IDynamicNumber + { + public static DynamicFloat Instance = new DynamicFloat(); + public Type Type => typeof(float); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Convert(object value) => System.Convert.ToSingle(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToSingle(value); + + public bool TryParse(string str, out object result) + { + if (str.AsSpan().TryParseFloat(out float value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString("r", CultureInfo.InvariantCulture); + public object DefaultValue => default(float); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseXOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseLeftShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseRightShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseNot(object target) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + } + + public class DynamicDouble : IDynamicNumber + { + public static DynamicDouble Instance = new DynamicDouble(); + public Type Type => typeof(double); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double Convert(object value) => System.Convert.ToDouble(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToDouble(value); + + public bool TryParse(string str, out object result) + { + if (str.AsSpan().TryParseDouble(out double value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString("r", CultureInfo.InvariantCulture); + public object DefaultValue => default(double); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseXOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseLeftShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseRightShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseNot(object target) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + } + + public class DynamicDecimal : IDynamicNumber + { + public static DynamicDecimal Instance = new DynamicDecimal(); + public Type Type => typeof(decimal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public decimal Convert(object value) => System.Convert.ToDecimal(this.ParseString(value) ?? value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object ConvertFrom(object value) => this.ParseString(value) + ?? System.Convert.ToDecimal(value); + + public bool TryParse(string str, out object result) + { + if (str.AsSpan().TryParseDecimal(out decimal value)) + { + result = value; + return true; + } + result = null; + return false; + } + + public string ToString(object value) => Convert(value).ToString(CultureInfo.InvariantCulture); + public object DefaultValue => default(decimal); + + public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); + public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); + public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); + public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow((double) Convert(lhs), (double) Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log((double) Convert(lhs), (double) Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseXOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseLeftShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseRightShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseNot(object target) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + } + + public static class DynamicNumber + { + static readonly Dictionary RankNumbers = new Dictionary + { + {1, DynamicSByte.Instance}, + {2, DynamicByte.Instance}, + {3, DynamicShort.Instance}, + {4, DynamicUShort.Instance}, + {5, DynamicInt.Instance}, + {6, DynamicUInt.Instance}, + {7, DynamicLong.Instance}, + {8, DynamicULong.Instance}, + {9, DynamicFloat.Instance}, + {10, DynamicDouble.Instance}, + {11, DynamicDecimal.Instance}, + }; + + public static bool IsNumber(Type type) => TryGetRanking(type, out _); + + public static bool TryGetRanking(Type type, out int ranking) + { + ranking = -1; + switch (type.GetTypeCode()) + { + case TypeCode.SByte: + ranking = 1; + break; + case TypeCode.Byte: + ranking = 2; + break; + case TypeCode.Int16: + ranking = 3; + break; + case TypeCode.UInt16: + ranking = 4; + break; + case TypeCode.Char: + case TypeCode.Int32: + ranking = 5; + break; + case TypeCode.UInt32: + ranking = 6; + break; + case TypeCode.Int64: + ranking = 7; + break; + case TypeCode.UInt64: + ranking = 8; + break; + case TypeCode.Single: + ranking = 9; + break; + case TypeCode.Double: + ranking = 10; + break; + case TypeCode.Decimal: + ranking = 11; + break; + } + + return ranking > 0; + } + + public static IDynamicNumber GetNumber(Type type) + { + if (!TryGetRanking(type, out int objIndex)) + return null; + + var maxNumber = RankNumbers[objIndex]; + return maxNumber; + } + + public static IDynamicNumber Get(object obj) + { + if (obj == null) + return null; + + if (obj is string lhsString && !TryParse(lhsString, out obj)) + return null; + + return TryGetRanking(obj.GetType(), out int lhsRanking) + ? RankNumbers[lhsRanking] + : null; + } + + public static IDynamicNumber GetNumber(object lhs, object rhs) + { + if (lhs == null || rhs == null) + return null; + + if (lhs is string lhsString && !TryParse(lhsString, out lhs)) + return null; + + if (rhs is string rhsString && !TryParse(rhsString, out rhs)) + return null; + + if (!TryGetRanking(lhs.GetType(), out int lhsRanking) || !TryGetRanking(rhs.GetType(), out int rhsRanking)) + return null; + + var maxRanking = Math.Max(lhsRanking, rhsRanking); + var maxNumber = RankNumbers[maxRanking]; + return maxNumber; + } + + public static IDynamicNumber AssertNumbers(string name, object lhs, object rhs) + { + var number = GetNumber(lhs, rhs); + if (number == null) + { + throw new ArgumentException($"Invalid numbers passed to {name}: " + + $"({lhs?.GetType().Name ?? "null"} '{lhs?.ToString().SubstringWithEllipsis(0, 100)}', " + + $"{rhs?.GetType().Name ?? "null"} '{rhs?.ToString().SubstringWithEllipsis(0, 100)}')"); + } + + return number; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Add(object lhs, object rhs) => AssertNumbers(nameof(Add), lhs, rhs).add(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Sub(object lhs, object rhs) => AssertNumbers(nameof(Subtract), lhs, rhs).sub(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Subtract(object lhs, object rhs) => AssertNumbers(nameof(Subtract), lhs, rhs).sub(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Mul(object lhs, object rhs) => AssertNumbers(nameof(Multiply), lhs, rhs).mul(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Multiply(object lhs, object rhs) => AssertNumbers(nameof(Multiply), lhs, rhs).mul(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Div(object lhs, object rhs) => AssertNumbers(nameof(Divide), lhs, rhs).div(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Divide(object lhs, object rhs) => AssertNumbers(nameof(Divide), lhs, rhs).div(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Mod(object lhs, object rhs) => AssertNumbers(nameof(Mod), lhs, rhs).mod(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Min(object lhs, object rhs) => AssertNumbers(nameof(Min), lhs, rhs).min(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Max(object lhs, object rhs) => AssertNumbers(nameof(Max), lhs, rhs).max(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Pow(object lhs, object rhs) => AssertNumbers(nameof(Pow), lhs, rhs).pow(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Log(object lhs, object rhs) => AssertNumbers(nameof(Log), lhs, rhs).log(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CompareTo(object lhs, object rhs) => AssertNumbers(nameof(CompareTo), lhs, rhs).compareTo(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseAnd(object lhs, object rhs) => AssertNumbers(nameof(BitwiseAnd), lhs, rhs).bitwiseAnd(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseOr(object lhs, object rhs) => AssertNumbers(nameof(BitwiseOr), lhs, rhs).bitwiseOr(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseXOr(object lhs, object rhs) => AssertNumbers(nameof(BitwiseXOr), lhs, rhs).bitwiseXOr(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseLeftShift(object lhs, object rhs) => AssertNumbers(nameof(BitwiseLeftShift), lhs, rhs).bitwiseLeftShift(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseRightShift(object lhs, object rhs) => AssertNumbers(nameof(BitwiseRightShift), lhs, rhs).bitwiseRightShift(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseNot(object lhs) => Get(lhs).bitwiseNot(lhs); + + public static bool TryParse(string strValue, out object result) + { + if (JsConfig.TryParseIntoBestFit) + return TryParseIntoBestFit(strValue, out result); + + result = null; + if (!(strValue?.Length > 0)) + return false; + + if (strValue.Length == 1) + { + int singleDigit = strValue[0]; + if (singleDigit >= '0' && singleDigit <= '9') + { + result = singleDigit - 48; // 0 + return true; + } + return false; + } + + var hasDecimal = strValue.IndexOf('.') >= 0; + if (!hasDecimal) + { + if (int.TryParse(strValue, out int intValue)) + { + result = intValue; + return true; + } + if (long.TryParse(strValue, out long longValue)) + { + result = longValue; + return true; + } + if (ulong.TryParse(strValue, out ulong ulongValue)) + { + result = ulongValue; + return true; + } + } + + var spanValue = strValue.AsSpan(); + if (spanValue.TryParseDouble(out double doubleValue)) + { + result = doubleValue; + return true; + } + + if (spanValue.TryParseDecimal(out decimal decimalValue)) + { + result = decimalValue; + return true; + } + + return false; + } + + public static bool TryParseIntoBestFit(string strValue, out object result) + { + result = null; + if (!(strValue?.Length > 0)) + return false; + + var segValue = strValue.AsSpan(); + result = segValue.ParseNumber(bestFit:true); + return result != null; + } + } + + internal static class DynamicNumberExtensions + { + internal static object ParseString(this IDynamicNumber number, object value) + { + if (value is string s) + return number.TryParse(s, out object x) ? x : null; + + if (value is char c) + return number.TryParse(c.ToString(), out object x) ? x : null; + + return null; + } + + } +} diff --git a/src/ServiceStack.Text/DynamicProxy.cs b/src/ServiceStack.Text/DynamicProxy.cs deleted file mode 100644 index 1a99f4275..000000000 --- a/src/ServiceStack.Text/DynamicProxy.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -#if !SILVERLIGHT && !MONOTOUCH -using System.Reflection.Emit; - -namespace ServiceStack.Text { - public static class DynamicProxy { - public static T GetInstanceFor () { - return (T)GetInstanceFor(typeof(T)); - } - - static readonly ModuleBuilder ModuleBuilder; - static readonly AssemblyBuilder DynamicAssembly; - - public static object GetInstanceFor (Type targetType) { - lock (DynamicAssembly) - { - var constructedType = DynamicAssembly.GetType(ProxyName(targetType)) ?? GetConstructedType(targetType); - var instance = Activator.CreateInstance(constructedType); - return instance; - } - } - - static string ProxyName(Type targetType) - { - return targetType.Name + "Proxy"; - } - - static DynamicProxy () { - var assemblyName = new AssemblyName("DynImpl"); - DynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); - ModuleBuilder = DynamicAssembly.DefineDynamicModule("DynImplModule"); - } - - static Type GetConstructedType (Type targetType) { - var typeBuilder = ModuleBuilder.DefineType(targetType.Name + "Proxy", TypeAttributes.Public); - - var ctorBuilder = typeBuilder.DefineConstructor( - MethodAttributes.Public, - CallingConventions.Standard, - new Type[] { }); - var ilGenerator = ctorBuilder.GetILGenerator(); - ilGenerator.Emit(OpCodes.Ret); - - IncludeType(targetType, typeBuilder); - - foreach (var face in targetType.GetInterfaces()) - IncludeType(face, typeBuilder); - - return typeBuilder.CreateType(); - } - - static void IncludeType (Type typeOfT, TypeBuilder typeBuilder) { - var methodInfos = typeOfT.GetMethods(); - foreach (var methodInfo in methodInfos) { - if (methodInfo.Name.StartsWith("set_")) continue; // we always add a set for a get. - - if (methodInfo.Name.StartsWith("get_")) { - BindProperty(typeBuilder, methodInfo); - } else { - BindMethod(typeBuilder, methodInfo); - } - } - - typeBuilder.AddInterfaceImplementation(typeOfT); - } - - static void BindMethod (TypeBuilder typeBuilder, MethodInfo methodInfo) { - var methodBuilder = typeBuilder.DefineMethod( - methodInfo.Name, - MethodAttributes.Public | MethodAttributes.Virtual, - methodInfo.ReturnType, - methodInfo.GetParameters().Select(p => p.GetType()).ToArray() - ); - var methodILGen = methodBuilder.GetILGenerator(); - if (methodInfo.ReturnType == typeof(void)) { - methodILGen.Emit(OpCodes.Ret); - } else { - if (methodInfo.ReturnType.IsValueType || methodInfo.ReturnType.IsEnum) { - MethodInfo getMethod = typeof(Activator).GetMethod("CreateInstance", - new[] { typeof(Type) }); - LocalBuilder lb = methodILGen.DeclareLocal(methodInfo.ReturnType); - methodILGen.Emit(OpCodes.Ldtoken, lb.LocalType); - methodILGen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); - methodILGen.Emit(OpCodes.Callvirt, getMethod); - methodILGen.Emit(OpCodes.Unbox_Any, lb.LocalType); - } else { - methodILGen.Emit(OpCodes.Ldnull); - } - methodILGen.Emit(OpCodes.Ret); - } - typeBuilder.DefineMethodOverride(methodBuilder, methodInfo); - } - - public static void BindProperty (TypeBuilder typeBuilder, MethodInfo methodInfo) { - // Backing Field - string propertyName = methodInfo.Name.Replace("get_", ""); - Type propertyType = methodInfo.ReturnType; - FieldBuilder backingField = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); - - //Getter - MethodBuilder backingGet = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | - MethodAttributes.SpecialName | MethodAttributes.Virtual | - MethodAttributes.HideBySig, propertyType, Type.EmptyTypes); - ILGenerator getIl = backingGet.GetILGenerator(); - - getIl.Emit(OpCodes.Ldarg_0); - getIl.Emit(OpCodes.Ldfld, backingField); - getIl.Emit(OpCodes.Ret); - - - //Setter - MethodBuilder backingSet = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | - MethodAttributes.SpecialName | MethodAttributes.Virtual | - MethodAttributes.HideBySig, null, new[] { propertyType }); - - ILGenerator setIl = backingSet.GetILGenerator(); - - setIl.Emit(OpCodes.Ldarg_0); - setIl.Emit(OpCodes.Ldarg_1); - setIl.Emit(OpCodes.Stfld, backingField); - setIl.Emit(OpCodes.Ret); - - // Property - PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, null); - propertyBuilder.SetGetMethod(backingGet); - propertyBuilder.SetSetMethod(backingSet); - } - } -} -#endif diff --git a/src/ServiceStack.Text/Env.cs b/src/ServiceStack.Text/Env.cs index 35db6aca4..003d2d40a 100644 --- a/src/ServiceStack.Text/Env.cs +++ b/src/ServiceStack.Text/Env.cs @@ -1,40 +1,328 @@ +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace ServiceStack.Text { - public static class Env - { - static Env() - { - var platform = (int)Environment.OSVersion.Platform; - IsUnix = (platform == 4) || (platform == 6) || (platform == 128); + public static class Env + { + static Env() + { + if (PclExport.Instance == null) + throw new ArgumentException("PclExport.Instance needs to be initialized"); - IsMono = AssemblyUtils.FindType("Mono.Runtime") != null; +#if NETCORE + IsNetStandard = true; + try + { + IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + IsOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + IsNetCore3 = RuntimeInformation.FrameworkDescription.StartsWith(".NET Core 3"); + + var fxDesc = RuntimeInformation.FrameworkDescription; + IsMono = fxDesc.Contains("Mono"); + IsNetCore = fxDesc.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase); + } + catch (Exception) {} //throws PlatformNotSupportedException in AWS lambda + IsUnix = IsOSX || IsLinux; + HasMultiplePlatformTargets = true; + IsUWP = IsRunningAsUwp(); +#elif NETFX + IsNetFramework = true; + switch (Environment.OSVersion.Platform) + { + case PlatformID.Win32NT: + case PlatformID.Win32S: + case PlatformID.Win32Windows: + case PlatformID.WinCE: + IsWindows = true; + break; + } + + var platform = (int)Environment.OSVersion.Platform; + IsUnix = platform == 4 || platform == 6 || platform == 128; - IsMonoTouch = AssemblyUtils.FindType("MonoTouch.Foundation.NSObject") != null; + if (File.Exists(@"/System/Library/CoreServices/SystemVersion.plist")) + IsOSX = true; + var osType = File.Exists(@"/proc/sys/kernel/ostype") + ? File.ReadAllText(@"/proc/sys/kernel/ostype") + : null; + IsLinux = osType?.IndexOf("Linux", StringComparison.OrdinalIgnoreCase) >= 0; + try + { + IsMono = AssemblyUtils.FindType("Mono.Runtime") != null; + } + catch (Exception) {} - SupportsExpressions = SupportsEmit = !IsMonoTouch; + SupportsDynamic = true; +#endif - ServerUserAgent = "ServiceStack/" + - ServiceStackVersion + " " - + Environment.OSVersion.Platform - + (IsMono ? "/Mono" : "/.NET") - + (IsMonoTouch ? " MonoTouch" : ""); - } +#if NETCORE + IsNetStandard = false; + IsNetCore = true; + SupportsDynamic = true; + IsNetCore21 = true; +#endif +#if NET6_0 + IsNet6 = true; +#endif +#if NETSTANDARD2_0 + IsNetStandard20 = true; +#endif - public static decimal ServiceStackVersion = 3.932m; + if (!IsUWP) + { + try + { + IsAndroid = AssemblyUtils.FindType("Android.Manifest") != null; + if (IsOSX && IsMono) + { + var runtimeDir = RuntimeEnvironment.GetRuntimeDirectory(); + //iOS detection no longer trustworthy so assuming iOS based on some current heuristics. TODO: improve iOS detection + IsIOS = runtimeDir.StartsWith("/private/var") || + runtimeDir.Contains("/CoreSimulator/Devices/"); + } + } + catch (Exception) {} + } + + SupportsExpressions = true; + SupportsEmit = !(IsUWP || IsIOS); - public static bool IsUnix { get; set; } + if (!SupportsEmit) + { + ReflectionOptimizer.Instance = ExpressionReflectionOptimizer.Provider; + } - public static bool IsMono { get; set; } + VersionString = ServiceStackVersion.ToString(CultureInfo.InvariantCulture); - public static bool IsMonoTouch { get; set; } + ServerUserAgent = "ServiceStack/" + + VersionString + " " + + PclExport.Instance.PlatformName + + (IsLinux ? "/Linux" : IsOSX ? "/macOS" : IsUnix ? "/Unix" : IsWindows ? "/Windows" : "/UnknownOS") + + (IsIOS ? "/iOS" : IsAndroid ? "/Android" : IsUWP ? "/UWP" : "") + + (IsNet6 ? "/net6" : IsNetStandard20 ? "/std2.0" : IsNetFramework ? "/netfx" : "") + (IsMono ? "/Mono" : "") + + $"/{LicenseUtils.Info}"; + __releaseDate = new DateTime(2001,01,01); + } - public static bool SupportsExpressions { get; set; } + public static string VersionString { get; set; } - public static bool SupportsEmit { get; set; } + public static decimal ServiceStackVersion = 6.03m; - public static string ServerUserAgent { get; set; } - } + public static bool IsLinux { get; set; } + + public static bool IsOSX { get; set; } + + public static bool IsUnix { get; set; } + + public static bool IsWindows { get; set; } + + public static bool IsMono { get; set; } + + public static bool IsIOS { get; set; } + + public static bool IsAndroid { get; set; } + + public static bool IsNetNative { get; set; } + + public static bool IsUWP { get; private set; } + + public static bool IsNetStandard { get; set; } + + public static bool IsNetCore21 { get; set; } + public static bool IsNet6 { get; set; } + public static bool IsNetStandard20 { get; set; } + + public static bool IsNetFramework { get; set; } + + public static bool IsNetCore { get; set; } + + public static bool IsNetCore3 { get; set; } + + public static bool SupportsExpressions { get; private set; } + + public static bool SupportsEmit { get; private set; } + + public static bool SupportsDynamic { get; private set; } + + private static bool strictMode; + public static bool StrictMode + { + get => strictMode; + set => Config.Instance.ThrowOnError = strictMode = value; + } + + public static string ServerUserAgent { get; set; } + + public static bool HasMultiplePlatformTargets { get; set; } + + private static readonly DateTime __releaseDate; + public static DateTime GetReleaseDate() + { + return __releaseDate; + } + + [Obsolete("Use ReferenceAssemblyPath")] + public static string ReferenceAssembyPath => ReferenceAssemblyPath; + + private static string referenceAssemblyPath; + + public static string ReferenceAssemblyPath + { + get + { + if (!IsMono && referenceAssemblyPath == null) + { + var programFilesPath = PclExport.Instance.GetEnvironmentVariable("ProgramFiles(x86)") ?? @"C:\Program Files (x86)"; + var netFxReferenceBasePath = programFilesPath + @"\Reference Assemblies\Microsoft\Framework\.NETFramework\"; + if ((netFxReferenceBasePath + @"v4.5.2\").DirectoryExists()) + referenceAssemblyPath = netFxReferenceBasePath + @"v4.5.2\"; + else if ((netFxReferenceBasePath + @"v4.5.1\").DirectoryExists()) + referenceAssemblyPath = netFxReferenceBasePath + @"v4.5.1\"; + else if ((netFxReferenceBasePath + @"v4.5\").DirectoryExists()) + referenceAssemblyPath = netFxReferenceBasePath + @"v4.5\"; + else if ((netFxReferenceBasePath + @"v4.0\").DirectoryExists()) + referenceAssemblyPath = netFxReferenceBasePath + @"v4.0\"; + else + { + var v4Dirs = PclExport.Instance.GetDirectoryNames(netFxReferenceBasePath, "v4*"); + if (v4Dirs.Length == 0) + { + var winPath = PclExport.Instance.GetEnvironmentVariable("SYSTEMROOT") ?? @"C:\Windows"; + var gacPath = winPath + @"\Microsoft.NET\Framework\"; + v4Dirs = PclExport.Instance.GetDirectoryNames(gacPath, "v4*"); + } + if (v4Dirs.Length > 0) + { + referenceAssemblyPath = v4Dirs[v4Dirs.Length - 1] + @"\"; //latest v4 + } + else + { + throw new FileNotFoundException( + "Could not infer .NET Reference Assemblies path, e.g '{0}'.\n".Fmt(netFxReferenceBasePath + @"v4.0\") + + "Provide path manually 'Env.ReferenceAssemblyPath'."); + } + } + } + return referenceAssemblyPath; + } + set => referenceAssemblyPath = value; + } + +#if NETCORE + private static bool IsRunningAsUwp() + { + try + { + IsNetNative = RuntimeInformation.FrameworkDescription.StartsWith(".NET Native", StringComparison.OrdinalIgnoreCase); + return IsInAppContainer || IsNetNative; + } + catch (Exception) {} + return false; + } + + private static bool IsWindows7OrLower + { + get + { + int versionMajor = Environment.OSVersion.Version.Major; + int versionMinor = Environment.OSVersion.Version.Minor; + double version = versionMajor + (double)versionMinor / 10; + return version <= 6.1; + } + } + + // From: https://github.com/dotnet/corefx/blob/master/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Windows.cs + private static int s_isInAppContainer = -1; + private static bool IsInAppContainer + { + // This actually checks whether code is running in a modern app. + // Currently this is the only situation where we run in app container. + // If we want to distinguish the two cases in future, + // EnvironmentHelpers.IsAppContainerProcess in desktop code shows how to check for the AC token. + get + { + if (s_isInAppContainer != -1) + return s_isInAppContainer == 1; + + if (!IsWindows || IsWindows7OrLower) + { + s_isInAppContainer = 0; + return false; + } + + byte[] buffer = TypeConstants.EmptyByteArray; + uint bufferSize = 0; + try + { + int result = GetCurrentApplicationUserModelId(ref bufferSize, buffer); + switch (result) + { + case 15703: // APPMODEL_ERROR_NO_APPLICATION + s_isInAppContainer = 0; + break; + case 0: // ERROR_SUCCESS + case 122: // ERROR_INSUFFICIENT_BUFFER + // Success is actually insufficient buffer as we're really only looking for + // not NO_APPLICATION and we're not actually giving a buffer here. The + // API will always return NO_APPLICATION if we're not running under a + // WinRT process, no matter what size the buffer is. + s_isInAppContainer = 1; + break; + default: + throw new InvalidOperationException($"Failed to get AppId, result was {result}."); + } + } + catch (Exception e) + { + // We could catch this here, being friendly with older portable surface area should we + // desire to use this method elsewhere. + if (e.GetType().FullName.Equals("System.EntryPointNotFoundException", StringComparison.Ordinal)) + { + // API doesn't exist, likely pre Win8 + s_isInAppContainer = 0; + } + else + { + throw; + } + } + + return s_isInAppContainer == 1; + } + } + + [DllImport("kernel32.dll", ExactSpelling = true)] + private static extern int GetCurrentApplicationUserModelId(ref uint applicationUserModelIdLength, byte[] applicationUserModelId); + #endif + + public const bool ContinueOnCapturedContext = false; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredTaskAwaitable ConfigAwait(this Task task) => + task.ConfigureAwait(ContinueOnCapturedContext); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredTaskAwaitable ConfigAwait(this Task task) => + task.ConfigureAwait(ContinueOnCapturedContext); + +#if NETCORE + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredValueTaskAwaitable ConfigAwait(this ValueTask task) => + task.ConfigureAwait(ContinueOnCapturedContext); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredValueTaskAwaitable ConfigAwait(this ValueTask task) => + task.ConfigureAwait(ContinueOnCapturedContext); +#endif + + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Extensions/ServiceStackExtensions.cs b/src/ServiceStack.Text/Extensions/ServiceStackExtensions.cs new file mode 100644 index 000000000..14c422526 --- /dev/null +++ b/src/ServiceStack.Text/Extensions/ServiceStackExtensions.cs @@ -0,0 +1,42 @@ +using System; + +namespace ServiceStack.Extensions +{ + /// + /// Move conflicting extension methods into 'ServiceStack.Extensions' namespace + /// + public static class ServiceStackExtensions + { + //Ambiguous definitions in .NET Core 3.0 System MemoryExtensions.cs + public static ReadOnlyMemory Trim(this ReadOnlyMemory span) + { + return span.TrimStart().TrimEnd(); + } + + public static ReadOnlyMemory TrimStart(this ReadOnlyMemory value) + { + if (value.IsEmpty) return TypeConstants.NullStringMemory; + var span = value.Span; + int start = 0; + for (; start < span.Length; start++) + { + if (!char.IsWhiteSpace(span[start])) + break; + } + return value.Slice(start); + } + + public static ReadOnlyMemory TrimEnd(this ReadOnlyMemory value) + { + if (value.IsEmpty) return TypeConstants.NullStringMemory; + var span = value.Span; + int end = span.Length - 1; + for (; end >= 0; end--) + { + if (!char.IsWhiteSpace(span[end])) + break; + } + return value.Slice(0, end + 1); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/HashSet.cs b/src/ServiceStack.Text/HashSet.cs deleted file mode 100644 index fad0977d8..000000000 --- a/src/ServiceStack.Text/HashSet.cs +++ /dev/null @@ -1,88 +0,0 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// Mijail Cisneros (cisneros@mijail.ru) -// -// Copyright 2012 Liquidbit Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace ServiceStack.Text.WP -{ - /// - /// A hashset implementation that uses an IDictionary - /// - public class HashSet : ICollection, IEnumerable, IEnumerable - { - private readonly Dictionary _dict; - - public HashSet() - { - _dict = new Dictionary(); - } - - public HashSet(IEnumerable collection) - { - if (collection == null) - throw new ArgumentNullException("collection"); - - _dict = new Dictionary(collection.Count()); - foreach (T item in collection) - Add(item); - } - - public void Add(T item) - { - _dict.Add(item, 0); - } - - public void Clear() - { - _dict.Clear(); - } - - public bool Contains(T item) - { - return _dict.ContainsKey(item); - } - - public void CopyTo(T[] array, int arrayIndex) - { - _dict.Keys.CopyTo(array, arrayIndex); - } - - public bool Remove(T item) - { - return _dict.Remove(item); - } - - public IEnumerator GetEnumerator() - { - return _dict.Keys.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _dict.Keys.GetEnumerator(); - } - - public int Count - { - get { return _dict.Keys.Count(); } - } - - public bool IsReadOnly - { - get { return false; } - } - } -} diff --git a/src/ServiceStack.Text/HttpHeaders.cs b/src/ServiceStack.Text/HttpHeaders.cs new file mode 100644 index 000000000..8cdfeace3 --- /dev/null +++ b/src/ServiceStack.Text/HttpHeaders.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; + +namespace ServiceStack; + +public static class HttpHeaders +{ + public const string XParamOverridePrefix = "X-Param-Override-"; + + public const string XHttpMethodOverride = "X-Http-Method-Override"; + + public const string XAutoBatchCompleted = "X-AutoBatch-Completed"; // How many requests were completed before first failure + + public const string XTag = "X-Tag"; + + public const string XUserAuthId = "X-UAId"; + + public const string XTrigger = "X-Trigger"; // Trigger Events on UserAgent + + public const string XForwardedFor = "X-Forwarded-For"; // IP Address + + public const string XForwardedPort = "X-Forwarded-Port"; // 80 + + public const string XForwardedProtocol = "X-Forwarded-Proto"; // http or https + + public const string XRealIp = "X-Real-IP"; + + public const string XLocation = "X-Location"; + + public const string XStatus = "X-Status"; + + public const string XPoweredBy = "X-Powered-By"; + + public const string Referer = "Referer"; + + public const string CacheControl = "Cache-Control"; + + public const string IfModifiedSince = "If-Modified-Since"; + + public const string IfUnmodifiedSince = "If-Unmodified-Since"; + + public const string IfNoneMatch = "If-None-Match"; + + public const string IfMatch = "If-Match"; + + public const string LastModified = "Last-Modified"; + + public const string Accept = "Accept"; + + public const string AcceptEncoding = "Accept-Encoding"; + + public const string ContentType = "Content-Type"; + + public const string ContentEncoding = "Content-Encoding"; + + public const string ContentLength = "Content-Length"; + + public const string ContentDisposition = "Content-Disposition"; + + public const string Location = "Location"; + + public const string SetCookie = "Set-Cookie"; + + public const string ETag = "ETag"; + + public const string Age = "Age"; + + public const string Expires = "Expires"; + + public const string Vary = "Vary"; + + public const string Authorization = "Authorization"; + + public const string WwwAuthenticate = "WWW-Authenticate"; + + public const string AllowOrigin = "Access-Control-Allow-Origin"; + + public const string AllowMethods = "Access-Control-Allow-Methods"; + + public const string AllowHeaders = "Access-Control-Allow-Headers"; + + public const string AllowCredentials = "Access-Control-Allow-Credentials"; + + public const string ExposeHeaders = "Access-Control-Expose-Headers"; + + public const string AccessControlMaxAge = "Access-Control-Max-Age"; + + public const string Origin = "Origin"; + + public const string RequestMethod = "Access-Control-Request-Method"; + + public const string RequestHeaders = "Access-Control-Request-Headers"; + + public const string AcceptRanges = "Accept-Ranges"; + + public const string ContentRange = "Content-Range"; + + public const string Range = "Range"; + + public const string SOAPAction = "SOAPAction"; + + public const string Allow = "Allow"; + + public const string AcceptCharset = "Accept-Charset"; + + public const string AcceptLanguage = "Accept-Language"; + + public const string Connection = "Connection"; + + public const string Cookie = "Cookie"; + + public const string ContentLanguage = "Content-Language"; + + public const string Expect = "Expect"; + + public const string Pragma = "Pragma"; + + public const string ProxyAuthenticate = "Proxy-Authenticate"; + + public const string ProxyAuthorization = "Proxy-Authorization"; + + public const string ProxyConnection = "Proxy-Connection"; + + public const string SetCookie2 = "Set-Cookie2"; + + public const string TE = "TE"; + + public const string Trailer = "Trailer"; + + public const string TransferEncoding = "Transfer-Encoding"; + + public const string Upgrade = "Upgrade"; + + public const string Via = "Via"; + + public const string Warning = "Warning"; + + public const string Date = "Date"; + public const string Host = "Host"; + public const string UserAgent = "User-Agent"; + + public static HashSet RestrictedHeaders = new(StringComparer.OrdinalIgnoreCase) + { + Accept, + Connection, + ContentLength, + ContentType, + Date, + Expect, + Host, + IfModifiedSince, + Range, + Referer, + TransferEncoding, + UserAgent, + ProxyConnection, + }; +} + + +public static class CompressionTypes +{ + public static readonly string[] AllCompressionTypes = + { +#if NET6_0_OR_GREATER + Brotli, +#endif + Deflate, + GZip, + }; + +#if NET6_0_OR_GREATER + public const string Default = Brotli; +#else + public const string Default = Deflate; +#endif + + public const string Brotli = "br"; + public const string Deflate = "deflate"; + public const string GZip = "gzip"; + + public static bool IsValid(string compressionType) + { +#if NET6_0_OR_GREATER + return compressionType is Deflate or GZip or Brotli; +#else + return compressionType is Deflate or GZip; +#endif + } + + public static void AssertIsValid(string compressionType) + { + if (!IsValid(compressionType)) + { + throw new NotSupportedException(compressionType + + " is not a supported compression type. Valid types: " + string.Join(", ", AllCompressionTypes)); + } + } + + public static string GetExtension(string compressionType) + { + switch (compressionType) + { + case Brotli: + case Deflate: + case GZip: + return "." + compressionType; + default: + throw new NotSupportedException( + "Unknown compressionType: " + compressionType); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/HttpMethods.cs b/src/ServiceStack.Text/HttpMethods.cs new file mode 100644 index 000000000..af2427998 --- /dev/null +++ b/src/ServiceStack.Text/HttpMethods.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace ServiceStack; + +public static class HttpMethods +{ + static readonly string[] allVerbs = { + "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616 + "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518 + "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT", + "MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY", // RFC 3253 + "ORDERPATCH", // RFC 3648 + "ACL", // RFC 3744 + "PATCH", // https://datatracker.ietf.org/doc/draft-dusseault-http-patch/ + "SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/ + "BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY", + "POLL", "SUBSCRIBE", "UNSUBSCRIBE" //MS Exchange WebDav: http://msdn.microsoft.com/en-us/library/aa142917.aspx + }; + + public static HashSet AllVerbs = new(allVerbs); + + public static bool Exists(string httpMethod) => AllVerbs.Contains(httpMethod.ToUpper()); + public static bool HasVerb(string httpVerb) => Exists(httpVerb); + + public const string Get = "GET"; + public const string Put = "PUT"; + public const string Post = "POST"; + public const string Delete = "DELETE"; + public const string Options = "OPTIONS"; + public const string Head = "HEAD"; + public const string Patch = "PATCH"; +} \ No newline at end of file diff --git a/src/ServiceStack.Text/HttpRequestConfig.cs b/src/ServiceStack.Text/HttpRequestConfig.cs new file mode 100644 index 000000000..0f9ec75e9 --- /dev/null +++ b/src/ServiceStack.Text/HttpRequestConfig.cs @@ -0,0 +1,64 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ServiceStack; + +public class HttpRequestConfig +{ + public string? Accept { get; set; } + public string? UserAgent { get; set; } + public string? ContentType { get; set; } + public string? Referer { get; set; } + public string? Expect { get; set; } + public string[]? TransferEncoding { get; set; } + public bool? TransferEncodingChunked { get; set; } + public NameValue? Authorization { get; set; } + public LongRange? Range { get; set; } + public List Headers { get; set; } = new(); + + public void SetAuthBearer(string value) => Authorization = new("Bearer", value); + public void SetAuthBasic(string name, string value) => + Authorization = new("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(name + ":" + value))); + public void SetRange(long from, long? to = null) => Range = new(from, to); + + public void AddHeader(string name, string value) => Headers.Add(new(name, value)); +} + +public record NameValue +{ + public NameValue(string name, string value) + { + this.Name = name; + this.Value = value; + } + + public string Name { get; } + public string Value { get; } + + public void Deconstruct(out string name, out string value) + { + name = this.Name; + value = this.Value; + } +} + +public record LongRange +{ + public LongRange(long from, long? to = null) + { + this.From = from; + this.To = to; + } + + public long From { get; } + public long? To { get; } + + public void Deconstruct(out long from, out long? to) + { + from = this.From; + to = this.To; + } +} diff --git a/src/ServiceStack.Text/HttpStatus.cs b/src/ServiceStack.Text/HttpStatus.cs new file mode 100644 index 000000000..a60925b96 --- /dev/null +++ b/src/ServiceStack.Text/HttpStatus.cs @@ -0,0 +1,90 @@ +namespace ServiceStack.Text; + +public static class HttpStatus +{ + public static string GetStatusDescription(int statusCode) + { + if (statusCode is >= 100 and < 600) + { + int i = statusCode / 100; + int j = statusCode % 100; + + if (j < Descriptions[i].Length) + return Descriptions[i][j]; + } + + return string.Empty; + } + + private static readonly string[][] Descriptions = + { + null, + new[] + { + /* 100 */ "Continue", + /* 101 */ "Switching Protocols", + /* 102 */ "Processing" + }, + new[] + { + /* 200 */ "OK", + /* 201 */ "Created", + /* 202 */ "Accepted", + /* 203 */ "Non-Authoritative Information", + /* 204 */ "No Content", + /* 205 */ "Reset Content", + /* 206 */ "Partial Content", + /* 207 */ "Multi-Status" + }, + new[] + { + /* 300 */ "Multiple Choices", + /* 301 */ "Moved Permanently", + /* 302 */ "Found", + /* 303 */ "See Other", + /* 304 */ "Not Modified", + /* 305 */ "Use Proxy", + /* 306 */ string.Empty, + /* 307 */ "Temporary Redirect" + }, + new[] + { + /* 400 */ "Bad Request", + /* 401 */ "Unauthorized", + /* 402 */ "Payment Required", + /* 403 */ "Forbidden", + /* 404 */ "Not Found", + /* 405 */ "Method Not Allowed", + /* 406 */ "Not Acceptable", + /* 407 */ "Proxy Authentication Required", + /* 408 */ "Request Timeout", + /* 409 */ "Conflict", + /* 410 */ "Gone", + /* 411 */ "Length Required", + /* 412 */ "Precondition Failed", + /* 413 */ "Request Entity Too Large", + /* 414 */ "Request-Uri Too Long", + /* 415 */ "Unsupported Media Type", + /* 416 */ "Requested Range Not Satisfiable", + /* 417 */ "Expectation Failed", + /* 418 */ string.Empty, + /* 419 */ string.Empty, + /* 420 */ string.Empty, + /* 421 */ string.Empty, + /* 422 */ "Unprocessable Entity", + /* 423 */ "Locked", + /* 424 */ "Failed Dependency" + }, + new[] + { + /* 500 */ "Internal Server Error", + /* 501 */ "Not Implemented", + /* 502 */ "Bad Gateway", + /* 503 */ "Service Unavailable", + /* 504 */ "Gateway Timeout", + /* 505 */ "Http Version Not Supported", + /* 506 */ string.Empty, + /* 507 */ "Insufficient Storage" + } + }; +} \ No newline at end of file diff --git a/src/ServiceStack.Text/HttpUtils.HttpClient.cs b/src/ServiceStack.Text/HttpUtils.HttpClient.cs new file mode 100644 index 000000000..a90a6d862 --- /dev/null +++ b/src/ServiceStack.Text/HttpUtils.HttpClient.cs @@ -0,0 +1,1172 @@ +#if NET6_0_OR_GREATER +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text; + +namespace ServiceStack; + +public static partial class HttpUtils +{ + private class HttpClientFactory + { + private readonly Lazy lazyHandler; + internal HttpClientFactory(Func handler) => + lazyHandler = new Lazy(() => handler(), LazyThreadSafetyMode.ExecutionAndPublication); + public HttpClient CreateClient() => new(lazyHandler.Value, disposeHandler: false); + } + + // Ok to use HttpClientHandler which now uses SocketsHttpHandler + // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs#L16 + public static Func HttpClientHandlerFactory { get; set; } = () => new() { + UseDefaultCredentials = true, + AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.Deflate | DecompressionMethods.GZip, + }; + + // This was the least desirable end to this sadness https://github.com/dotnet/aspnetcore/issues/28385 + // Requires + // public static IHttpClientFactory ClientFactory { get; set; } = new ServiceCollection() + // .AddHttpClient() + // .Configure(options => + // options.HttpMessageHandlerBuilderActions.Add(builder => builder.PrimaryHandler = HandlerFactory)) + // .BuildServiceProvider().GetRequiredService(); + + // Escape & BYO IHttpClientFactory + private static HttpClientFactory? clientFactory; + public static Func CreateClient { get; set; } = () => { + try + { + clientFactory ??= new(HttpClientHandlerFactory); + return clientFactory.CreateClient(); + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + return new HttpClient(); + } + }; + + public static HttpClient Create() => CreateClient(); + + public static string GetJsonFromUrl(this string url, + Action? requestFilter = null, Action? responseFilter = null) + { + return url.GetStringFromUrl(accept:MimeTypes.Json, requestFilter, responseFilter); + } + + public static Task GetJsonFromUrlAsync(this string url, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(accept:MimeTypes.Json, requestFilter, responseFilter, token: token); + } + + public static string GetXmlFromUrl(this string url, + Action? requestFilter = null, Action? responseFilter = null) + { + return url.GetStringFromUrl(accept:MimeTypes.Xml, requestFilter, responseFilter); + } + + public static Task GetXmlFromUrlAsync(this string url, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(accept:MimeTypes.Xml, requestFilter, responseFilter, token: token); + } + + public static string GetCsvFromUrl(this string url, + Action? requestFilter = null, Action? responseFilter = null) + { + return url.GetStringFromUrl(accept:MimeTypes.Csv, requestFilter, responseFilter); + } + + public static Task GetCsvFromUrlAsync(this string url, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(accept:MimeTypes.Csv, requestFilter, responseFilter, token: token); + } + + public static string GetStringFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Get, accept: accept, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetStringFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Get, accept: accept, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostStringToUrl(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostStringToUrlAsync(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostToUrl(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostToUrlAsync(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostToUrl(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method:HttpMethods.Post, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostToUrlAsync(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method:HttpMethods.Post, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostJsonToUrl(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostJsonToUrlAsync(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostJsonToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostJsonToUrlAsync(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostXmlToUrl(this string url, string xml, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostXmlToUrlAsync(this string url, string xml, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, requestBody: xml, contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostCsvToUrl(this string url, string csv, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostCsvToUrlAsync(this string url, string csv, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, requestBody: csv, contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutStringToUrl(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutStringToUrlAsync(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutToUrl(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutToUrlAsync(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutToUrl(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method:HttpMethods.Put, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutToUrlAsync(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method:HttpMethods.Put, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutJsonToUrl(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutJsonToUrlAsync(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutJsonToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutJsonToUrlAsync(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutXmlToUrl(this string url, string xml, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutXmlToUrlAsync(this string url, string xml, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, requestBody: xml, contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutCsvToUrl(this string url, string csv, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutCsvToUrlAsync(this string url, string csv, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, requestBody: csv, contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchStringToUrl(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Patch, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchStringToUrlAsync(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Patch, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchToUrl(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Patch, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchToUrlAsync(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Patch, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchToUrl(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method:HttpMethods.Patch, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchToUrlAsync(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method:HttpMethods.Patch, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchJsonToUrl(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Patch, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchJsonToUrlAsync(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Patch, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchJsonToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Patch, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchJsonToUrlAsync(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Patch, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string DeleteFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Delete, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task DeleteFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Delete, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string OptionsFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Options, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task OptionsFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Options, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string HeadFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Head, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task HeadFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Head, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string SendStringToUrl(this string url, string method = HttpMethods.Post, + string? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return Create().SendStringToUrl(url, method:method, requestBody:requestBody, + contentType:contentType, accept:accept, requestFilter:requestFilter, responseFilter:responseFilter); + } + + public static string SendStringToUrl(this HttpClient client, string url, string method = HttpMethods.Post, + string? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new StringContent(requestBody, UseEncoding); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = client.Send(httpReq); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes.Content.ReadAsStream().ReadToEnd(UseEncoding); + } + + public static Task SendStringToUrlAsync(this string url, + string method = HttpMethods.Post, string? requestBody = null, + string? contentType = null, string accept = "*/*", Action? requestFilter = null, + Action? responseFilter = null, CancellationToken token = default) + { + return Create().SendStringToUrlAsync(url, method:method, requestBody:requestBody, contentType:contentType, accept:accept, + requestFilter:requestFilter, responseFilter:responseFilter, token); + } + + public static async Task SendStringToUrlAsync(this HttpClient client, string url, string method = HttpMethods.Post, + string? requestBody = null, + string? contentType = null, string accept = "*/*", Action? requestFilter = null, + Action? responseFilter = null, CancellationToken token = default) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new StringContent(requestBody, UseEncoding); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return await httpRes.Content.ReadAsStringAsync(token).ConfigAwait(); + } + + public static byte[] GetBytesFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return url.SendBytesToUrl(method:HttpMethods.Get, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetBytesFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.SendBytesToUrlAsync(method:HttpMethods.Get, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, + token: token); + } + + public static byte[] PostBytesToUrl(this string url, byte[]? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendBytesToUrl(url, method:HttpMethods.Post, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostBytesToUrlAsync(this string url, byte[]? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendBytesToUrlAsync(url, method:HttpMethods.Post, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static byte[] PutBytesToUrl(this string url, byte[]? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendBytesToUrl(url, method:HttpMethods.Put, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutBytesToUrlAsync(this string url, byte[]? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendBytesToUrlAsync(url, method:HttpMethods.Put, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static byte[] SendBytesToUrl(this string url, string method = HttpMethods.Post, + byte[]? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return Create().SendBytesToUrl(url, method:method, requestBody:requestBody, contentType:contentType, accept:accept, + requestFilter:requestFilter, responseFilter:responseFilter); + } + + public static byte[] SendBytesToUrl(this HttpClient client, string url, string method = HttpMethods.Post, + byte[]? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new ReadOnlyMemoryContent(requestBody); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = client.Send(httpReq); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes.Content.ReadAsStream().ReadFully(); + } + + public static Task SendBytesToUrlAsync(this string url, string method = HttpMethods.Post, + byte[]? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return Create().SendBytesToUrlAsync(url, method, requestBody, contentType, accept, + requestFilter, responseFilter, token); + } + + public static async Task SendBytesToUrlAsync(this HttpClient client, string url, string method = HttpMethods.Post, + byte[]? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new ReadOnlyMemoryContent(requestBody); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return await (await httpRes.Content.ReadAsStreamAsync(token).ConfigAwait()).ReadFullyAsync(token).ConfigAwait(); + } + + public static Stream GetStreamFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return url.SendStreamToUrl(method:HttpMethods.Get, accept: accept, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetStreamFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.SendStreamToUrlAsync(method:HttpMethods.Get, accept: accept, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static Stream PostStreamToUrl(this string url, Stream? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStreamToUrl(url, method:HttpMethods.Post, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostStreamToUrlAsync(this string url, Stream? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStreamToUrlAsync(url, method:HttpMethods.Post, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static Stream PutStreamToUrl(this string url, Stream? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStreamToUrl(url, method:HttpMethods.Put, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutStreamToUrlAsync(this string url, Stream? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStreamToUrlAsync(url, method:HttpMethods.Put, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static Stream SendStreamToUrl(this string url, string method = HttpMethods.Post, + Stream? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return Create().SendStreamToUrl(url, method:method, requestBody:requestBody, contentType:contentType, accept:accept, + requestFilter:requestFilter, responseFilter:responseFilter); + } + + public static Stream SendStreamToUrl(this HttpClient client, string url, string method = HttpMethods.Post, + Stream? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new StreamContent(requestBody); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = client.Send(httpReq); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes.Content.ReadAsStream(); + } + + public static Task SendStreamToUrlAsync(this string url, string method = HttpMethods.Post, + Stream? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return Create().SendStreamToUrlAsync(url, method:method, requestBody:requestBody, contentType:contentType, accept:accept, + requestFilter:requestFilter, responseFilter:responseFilter, token); + } + + public static async Task SendStreamToUrlAsync(this HttpClient client, string url, string method = HttpMethods.Post, + Stream? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new StreamContent(requestBody); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return await httpRes.Content.ReadAsStreamAsync(token).ConfigAwait(); + } + + public static HttpStatusCode? GetResponseStatus(this string url) + { + try + { + var client = Create(); + var httpReq = new HttpRequestMessage(new HttpMethod(HttpMethods.Get), url); + httpReq.Headers.Add(HttpHeaders.Accept, "*/*"); + var httpRes = client.Send(httpReq); + return httpRes.StatusCode; + } + catch (Exception ex) + { + return ex.GetStatus(); + } + } + + public static HttpResponseMessage? GetErrorResponse(this string url) + { + var client = Create(); + var httpReq = new HttpRequestMessage(new HttpMethod(HttpMethods.Get), url); + httpReq.Headers.Add(HttpHeaders.Accept, "*/*"); + var httpRes = client.Send(httpReq); + return httpRes.IsSuccessStatusCode + ? null + : httpRes; + } + + public static async Task GetErrorResponseAsync(this string url, CancellationToken token=default) + { + var client = Create(); + var httpReq = new HttpRequestMessage(new HttpMethod(HttpMethods.Get), url); + httpReq.Headers.Add(HttpHeaders.Accept, "*/*"); + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + return httpRes.IsSuccessStatusCode + ? null + : httpRes; + } + + public static string ReadToEnd(this HttpResponseMessage webRes) + { + using var stream = webRes.Content.ReadAsStream(); + return stream.ReadToEnd(UseEncoding); + } + + public static Task ReadToEndAsync(this HttpResponseMessage webRes) + { + using var stream = webRes.Content.ReadAsStream(); + return stream.ReadToEndAsync(UseEncoding); + } + + public static IEnumerable ReadLines(this HttpResponseMessage webRes) + { + using var stream = webRes.Content.ReadAsStream(); + using var reader = new StreamReader(stream, UseEncoding, true, 1024, leaveOpen: true); + string? line; + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } + + public static HttpResponseMessage UploadFile(this HttpRequestMessage httpReq, Stream fileStream, + string fileName, string mimeType, string accept = "*/*", string method = HttpMethods.Post, string field = "file", + Action? requestFilter = null, Action? responseFilter = null) + { + return Create().UploadFile(httpReq, fileStream, fileName, mimeType, accept, method, field, + requestFilter, responseFilter); + } + + + public static HttpResponseMessage UploadFile(this HttpClient client, HttpRequestMessage httpReq, Stream fileStream, + string fileName, string? mimeType = null, string accept = "*/*", string method = HttpMethods.Post, string field = "file", + Action? requestFilter = null, Action? responseFilter = null) + { + if (httpReq.RequestUri == null) + throw new ArgumentException(nameof(httpReq.RequestUri)); + + httpReq.Method = new HttpMethod(method); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + requestFilter?.Invoke(httpReq); + + using var content = new MultipartFormDataContent(); + var fileBytes = fileStream.ReadFully(); + using var fileContent = new ByteArrayContent(fileBytes, 0, fileBytes.Length); + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "file", + FileName = fileName + }; + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(mimeType ?? MimeTypes.GetMimeType(fileName)); + content.Add(fileContent, "file", fileName); + + var httpRes = client.Send(httpReq); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes; + } + + public static Task UploadFileAsync(this HttpRequestMessage httpReq, Stream fileStream, + string fileName, + string? mimeType = null, string accept = "*/*", string method = HttpMethods.Post, string field = "file", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return Create().UploadFileAsync(httpReq, fileStream, fileName, mimeType, accept, method, field, + requestFilter, responseFilter, token); + } + + public static async Task UploadFileAsync(this HttpClient client, + HttpRequestMessage httpReq, Stream fileStream, string fileName, + string? mimeType = null, string accept = "*/*", string method = HttpMethods.Post, string field = "file", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + if (httpReq.RequestUri == null) + throw new ArgumentException(nameof(httpReq.RequestUri)); + + httpReq.Method = new HttpMethod(method); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + requestFilter?.Invoke(httpReq); + + using var content = new MultipartFormDataContent(); + var fileBytes = await fileStream.ReadFullyAsync(token).ConfigAwait(); + using var fileContent = new ByteArrayContent(fileBytes, 0, fileBytes.Length); + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "file", + FileName = fileName + }; + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(mimeType ?? MimeTypes.GetMimeType(fileName)); + content.Add(fileContent, "file", fileName); + + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes; + } + + public static void UploadFile(this HttpRequestMessage httpReq, Stream fileStream, string fileName) + { + if (fileName == null) + throw new ArgumentNullException(nameof(fileName)); + var mimeType = MimeTypes.GetMimeType(fileName); + if (mimeType == null) + throw new ArgumentException("Mime-type not found for file: " + fileName); + + UploadFile(httpReq, fileStream, fileName, mimeType); + } + + public static async Task UploadFileAsync(this HttpRequestMessage webRequest, Stream fileStream, string fileName, + CancellationToken token = default) + { + if (fileName == null) + throw new ArgumentNullException(nameof(fileName)); + var mimeType = MimeTypes.GetMimeType(fileName); + if (mimeType == null) + throw new ArgumentException("Mime-type not found for file: " + fileName); + + await UploadFileAsync(webRequest, fileStream, fileName, mimeType, token: token).ConfigAwait(); + } + + public static string PostXmlToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: data.ToXml(), contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PostCsvToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: data.ToCsv(), contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PutXmlToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: data.ToXml(), contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PutCsvToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: data.ToCsv(), contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static HttpResponseMessage PostFileToUrl(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var webReq = new HttpRequestMessage(HttpMethod.Post, url); + using var fileStream = uploadFileInfo.OpenRead(); + var fileName = uploadFileInfo.Name; + + return webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, + method: HttpMethods.Post, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static async Task PostFileToUrlAsync(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + var webReq = new HttpRequestMessage(HttpMethod.Post, url); + await using var fileStream = uploadFileInfo.OpenRead(); + var fileName = uploadFileInfo.Name; + + return await webReq.UploadFileAsync(fileStream, fileName, uploadFileMimeType, accept: accept, + method: HttpMethods.Post, requestFilter: requestFilter, responseFilter: responseFilter, token: token).ConfigAwait(); + } + + public static HttpResponseMessage PutFileToUrl(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var webReq = new HttpRequestMessage(HttpMethod.Put, url); + using var fileStream = uploadFileInfo.OpenRead(); + var fileName = uploadFileInfo.Name; + + return webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, + method: HttpMethods.Post, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static async Task PutFileToUrlAsync(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + var webReq = new HttpRequestMessage(HttpMethod.Put, url); + await using var fileStream = uploadFileInfo.OpenRead(); + var fileName = uploadFileInfo.Name; + + return await webReq.UploadFileAsync(fileStream, fileName, uploadFileMimeType, accept: accept, + method: HttpMethods.Post, requestFilter: requestFilter, responseFilter: responseFilter, token: token).ConfigAwait(); + } + + public static void AddHeader(this HttpRequestMessage res, string name, string value) => + res.WithHeader(name, value); + + /// + /// Returns first Request Header in HttpRequestMessage Headers and Content.Headers + /// + public static string? GetHeader(this HttpRequestMessage req, string name) + { + if (RequestHeadersResolver.TryGetValue(name, out var fn)) + return fn(req); + + return req.Headers.TryGetValues(name, out var headers) + ? headers.FirstOrDefault() + : req.Content?.Headers.TryGetValues(name, out var contentHeaders) == true + ? contentHeaders!.FirstOrDefault() + : null; + } + + public static Dictionary> RequestHeadersResolver { get; set; } = new(StringComparer.OrdinalIgnoreCase) { + }; + public static Dictionary> ResponseHeadersResolver { get; set; } = new(StringComparer.OrdinalIgnoreCase) { + }; + + /// + /// Returns first Response Header in HttpResponseMessage Headers and Content.Headers + /// + public static string? GetHeader(this HttpResponseMessage res, string name) + { + if (ResponseHeadersResolver.TryGetValue(name, out var fn)) + return fn(res); + + return res.Headers.TryGetValues(name, out var headers) + ? headers.FirstOrDefault() + : res.Content?.Headers.TryGetValues(name, out var contentHeaders) == true + ? contentHeaders!.FirstOrDefault() + : null; + } + + public static HttpRequestMessage WithHeader(this HttpRequestMessage httpReq, string name, string value) + { + if (name.Equals(HttpHeaders.Authorization, StringComparison.OrdinalIgnoreCase)) + { + httpReq.Headers.Authorization = + new AuthenticationHeaderValue(value.LeftPart(' '), value.RightPart(' ')); + } + else if (name.Equals(HttpHeaders.ContentType, StringComparison.OrdinalIgnoreCase)) + { + if (httpReq.Content == null) + throw new NotSupportedException("Can't set ContentType before Content is populated"); + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(value); + } + else if (name.Equals(HttpHeaders.Referer, StringComparison.OrdinalIgnoreCase)) + { + httpReq.Headers.Referrer = new Uri(value); + } + else if (name.Equals(HttpHeaders.UserAgent, StringComparison.OrdinalIgnoreCase)) + { + httpReq.Headers.UserAgent.ParseAdd(value); + } + else + { + httpReq.Headers.Add(name, value); + } + return httpReq; + } + + /// + /// Populate HttpRequestMessage with a simpler, untyped API + /// Syntax compatible with HttpWebRequest + /// + public static HttpRequestMessage With(this HttpRequestMessage httpReq, Action configure) + { + var config = new HttpRequestConfig(); + configure(config); + + var headers = config.Headers; + + if (config.Accept != null) + { + httpReq.Headers.Accept.Clear(); //override or consistent behavior + httpReq.Headers.Accept.Add(new(config.Accept)); + } + if (config.UserAgent != null) + headers.Add(new(HttpHeaders.UserAgent, config.UserAgent)); + if (config.ContentType != null) + { + if (httpReq.Content == null) + throw new NotSupportedException("Can't set ContentType before Content is populated"); + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(config.ContentType); + } + if (config.Referer != null) + httpReq.Headers.Referrer = new Uri(config.Referer); + if (config.Authorization != null) + httpReq.Headers.Authorization = + new AuthenticationHeaderValue(config.Authorization.Name, config.Authorization.Value); + if (config.Range != null) + httpReq.Headers.Range = new RangeHeaderValue(config.Range.From, config.Range.To); + if (config.Expect != null) + httpReq.Headers.Expect.Add(new(config.Expect)); + + if (config.TransferEncodingChunked != null) + httpReq.Headers.TransferEncodingChunked = config.TransferEncodingChunked.Value; + else if (config.TransferEncoding?.Length > 0) + { + foreach (var enc in config.TransferEncoding) + { + httpReq.Headers.TransferEncoding.Add(new(enc)); + } + } + + foreach (var entry in headers) + { + httpReq.WithHeader(entry.Name, entry.Value); + } + + return httpReq; + } + + public static void DownloadFileTo(this string downloadUrl, string fileName, + List? headers = null) + { + var client = Create(); + var httpReq = new HttpRequestMessage(HttpMethod.Get, downloadUrl) + .With(c => { + c.Accept = "*/*"; + if (headers != null) c.Headers = headers; + }); + + var httpRes = client.Send(httpReq); + httpRes.EnsureSuccessStatusCode(); + + using var fs = new FileStream(fileName, FileMode.CreateNew); + var bytes = httpRes.Content.ReadAsStream().ReadFully(); + fs.Write(bytes); + } +} + +public static class HttpClientExt +{ + /// + /// Case-insensitive, trimmed compare of two content types from start to ';', i.e. without charset suffix + /// + public static bool MatchesContentType(this HttpResponseMessage res, string matchesContentType) => + MimeTypes.MatchesContentType(res.GetHeader(HttpHeaders.ContentType), matchesContentType); + + public static long? GetContentLength(this HttpResponseMessage res) => + res.Content.Headers.ContentLength; +} + +#endif diff --git a/src/ServiceStack.Text/HttpUtils.WebRequest.cs b/src/ServiceStack.Text/HttpUtils.WebRequest.cs new file mode 100644 index 000000000..0f776e07b --- /dev/null +++ b/src/ServiceStack.Text/HttpUtils.WebRequest.cs @@ -0,0 +1,1246 @@ +#if !NET6_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text; + +namespace ServiceStack; + +public static partial class HttpUtils +{ + [ThreadStatic] + public static IHttpResultsFilter ResultsFilter; + + public static string GetJsonFromUrl(this string url, + Action requestFilter = null, Action responseFilter = null) + { + return url.GetStringFromUrl(MimeTypes.Json, requestFilter, responseFilter); + } + + public static Task GetJsonFromUrlAsync(this string url, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(MimeTypes.Json, requestFilter, responseFilter, token: token); + } + + public static string GetXmlFromUrl(this string url, + Action requestFilter = null, Action responseFilter = null) + { + return url.GetStringFromUrl(MimeTypes.Xml, requestFilter, responseFilter); + } + + public static Task GetXmlFromUrlAsync(this string url, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(MimeTypes.Xml, requestFilter, responseFilter, token: token); + } + + public static string GetCsvFromUrl(this string url, + Action requestFilter = null, Action responseFilter = null) + { + return url.GetStringFromUrl(MimeTypes.Csv, requestFilter, responseFilter); + } + + public static Task GetCsvFromUrlAsync(this string url, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(MimeTypes.Csv, requestFilter, responseFilter, token: token); + } + + public static string GetStringFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetStringFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, + token: token); + } + + public static string PostStringToUrl(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostStringToUrlAsync(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostToUrl(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostToUrlAsync(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostToUrl(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method: "POST", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostToUrlAsync(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method: "POST", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostJsonToUrl(this string url, string json, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostJsonToUrlAsync(this string url, string json, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostJsonToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostJsonToUrlAsync(this string url, object data, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostXmlToUrl(this string url, string xml, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostXmlToUrlAsync(this string url, string xml, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", requestBody: xml, contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostCsvToUrl(this string url, string csv, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostCsvToUrlAsync(this string url, string csv, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", requestBody: csv, contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutStringToUrl(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutStringToUrlAsync(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutToUrl(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutToUrlAsync(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutToUrl(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method: "PUT", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutToUrlAsync(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method: "PUT", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutJsonToUrl(this string url, string json, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutJsonToUrlAsync(this string url, string json, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutJsonToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutJsonToUrlAsync(this string url, object data, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutXmlToUrl(this string url, string xml, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutXmlToUrlAsync(this string url, string xml, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", requestBody: xml, contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutCsvToUrl(this string url, string csv, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutCsvToUrlAsync(this string url, string csv, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", requestBody: csv, contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchStringToUrl(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PATCH", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchStringToUrlAsync(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PATCH", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchToUrl(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PATCH", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchToUrlAsync(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PATCH", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchToUrl(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method: "PATCH", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchToUrlAsync(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method: "PATCH", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchJsonToUrl(this string url, string json, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PATCH", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchJsonToUrlAsync(this string url, string json, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PATCH", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchJsonToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PATCH", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchJsonToUrlAsync(this string url, object data, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PATCH", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string DeleteFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "DELETE", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task DeleteFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "DELETE", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string OptionsFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "OPTIONS", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task OptionsFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "OPTIONS", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string HeadFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "HEAD", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task HeadFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "HEAD", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string SendStringToUrl(this string url, string method = null, + string requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + return SendStringToUrl(webReq, method, requestBody, contentType, accept, requestFilter, responseFilter); + } + + public static async Task SendStringToUrlAsync(this string url, string method = null, + string requestBody = null, + string contentType = null, string accept = "*/*", Action requestFilter = null, + Action responseFilter = null, CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + return await SendStringToUrlAsync(webReq, method, requestBody, contentType, accept, requestFilter, responseFilter); + } + + public static string SendStringToUrl(this HttpWebRequest webReq, string method, string requestBody, string contentType, + string accept, Action requestFilter, Action responseFilter) + { + if (method != null) + webReq.Method = method; + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + return ResultsFilter.GetString(webReq, requestBody); + } + + if (requestBody != null) + { + using var reqStream = PclExport.Instance.GetRequestStream(webReq); + using var writer = new StreamWriter(reqStream, UseEncoding); + writer.Write(requestBody); + } + else if (method != null && HasRequestBody(method)) + { + webReq.ContentLength = 0; + } + + using var webRes = webReq.GetResponse(); + using var stream = webRes.GetResponseStream(); + responseFilter?.Invoke((HttpWebResponse)webRes); + return stream.ReadToEnd(UseEncoding); + } + + public static async Task SendStringToUrlAsync(this HttpWebRequest webReq, + string method, string requestBody, string contentType, string accept, + Action requestFilter, Action responseFilter) + { + if (method != null) + webReq.Method = method; + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + var result = ResultsFilter.GetString(webReq, requestBody); + return result; + } + + if (requestBody != null) + { + using var reqStream = PclExport.Instance.GetRequestStream(webReq); + using var writer = new StreamWriter(reqStream, UseEncoding); + await writer.WriteAsync(requestBody).ConfigAwait(); + } + else if (method != null && HasRequestBody(method)) + { + webReq.ContentLength = 0; + } + + using var webRes = await webReq.GetResponseAsync().ConfigAwait(); + responseFilter?.Invoke((HttpWebResponse)webRes); + using var stream = webRes.GetResponseStream(); + return await stream.ReadToEndAsync().ConfigAwait(); + } + + public static byte[] GetBytesFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return url.SendBytesToUrl(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetBytesFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.SendBytesToUrlAsync(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, + token: token); + } + + public static byte[] PostBytesToUrl(this string url, byte[] requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendBytesToUrl(url, method: "POST", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostBytesToUrlAsync(this string url, byte[] requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendBytesToUrlAsync(url, method: "POST", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static byte[] PutBytesToUrl(this string url, byte[] requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendBytesToUrl(url, method: "PUT", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutBytesToUrlAsync(this string url, byte[] requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendBytesToUrlAsync(url, method: "PUT", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static byte[] SendBytesToUrl(this string url, string method = null, + byte[] requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + return SendBytesToUrl(webReq, method, requestBody, contentType, accept, requestFilter, responseFilter); + } + + public static async Task SendBytesToUrlAsync(this string url, string method = null, + byte[] requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + return await SendBytesToUrlAsync(webReq, method, requestBody, contentType, accept, requestFilter, responseFilter, token); + } + + public static byte[] SendBytesToUrl(this HttpWebRequest webReq, string method, byte[] requestBody, string contentType, + string accept, Action requestFilter, Action responseFilter) + { + if (method != null) + webReq.Method = method; + + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + return ResultsFilter.GetBytes(webReq, requestBody); + } + + if (requestBody != null) + { + using var req = PclExport.Instance.GetRequestStream(webReq); + req.Write(requestBody, 0, requestBody.Length); + } + + using var webRes = PclExport.Instance.GetResponse(webReq); + responseFilter?.Invoke((HttpWebResponse)webRes); + + using var stream = webRes.GetResponseStream(); + return stream.ReadFully(); + } + + public static async Task SendBytesToUrlAsync(this HttpWebRequest webReq, string method, byte[] requestBody, + string contentType, string accept, Action requestFilter, Action responseFilter, CancellationToken token) + { + if (method != null) + webReq.Method = method; + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + var result = ResultsFilter.GetBytes(webReq, requestBody); + return result; + } + + if (requestBody != null) + { + using var req = PclExport.Instance.GetRequestStream(webReq); + await req.WriteAsync(requestBody, 0, requestBody.Length, token).ConfigAwait(); + } + + var webRes = await webReq.GetResponseAsync().ConfigAwait(); + responseFilter?.Invoke((HttpWebResponse)webRes); + + using var stream = webRes.GetResponseStream(); + return await stream.ReadFullyAsync(token).ConfigAwait(); + } + + public static Stream GetStreamFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return url.SendStreamToUrl(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetStreamFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.SendStreamToUrlAsync(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, + token: token); + } + + public static Stream PostStreamToUrl(this string url, Stream requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStreamToUrl(url, method: "POST", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostStreamToUrlAsync(this string url, Stream requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStreamToUrlAsync(url, method: "POST", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static Stream PutStreamToUrl(this string url, Stream requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStreamToUrl(url, method: "PUT", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutStreamToUrlAsync(this string url, Stream requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStreamToUrlAsync(url, method: "PUT", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + /// + /// Returns HttpWebResponse Stream which must be disposed + /// + public static Stream SendStreamToUrl(this string url, string method = null, + Stream requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + if (method != null) + webReq.Method = method; + + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + return new MemoryStream(ResultsFilter.GetBytes(webReq, requestBody.ReadFully())); + } + + if (requestBody != null) + { + using var req = PclExport.Instance.GetRequestStream(webReq); + requestBody.CopyTo(req); + } + + var webRes = PclExport.Instance.GetResponse(webReq); + responseFilter?.Invoke((HttpWebResponse)webRes); + + var stream = webRes.GetResponseStream(); + return stream; + } + + /// + /// Returns HttpWebResponse Stream which must be disposed + /// + public static async Task SendStreamToUrlAsync(this string url, string method = null, + Stream requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + if (method != null) + webReq.Method = method; + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + return new MemoryStream(ResultsFilter.GetBytes(webReq, + await requestBody.ReadFullyAsync(token).ConfigAwait())); + } + + if (requestBody != null) + { + using var req = PclExport.Instance.GetRequestStream(webReq); + await requestBody.CopyToAsync(req, token).ConfigAwait(); + } + + var webRes = await webReq.GetResponseAsync().ConfigAwait(); + responseFilter?.Invoke((HttpWebResponse)webRes); + + var stream = webRes.GetResponseStream(); + return stream; + } + + public static HttpStatusCode? GetResponseStatus(this string url) + { + try + { + var webReq = WebRequest.CreateHttp(url); + using var webRes = PclExport.Instance.GetResponse(webReq); + var httpRes = webRes as HttpWebResponse; + return httpRes?.StatusCode; + } + catch (Exception ex) + { + return ex.GetStatus(); + } + } + + public static HttpWebResponse GetErrorResponse(this string url) + { + try + { + var webReq = WebRequest.Create(url); + using var webRes = PclExport.Instance.GetResponse(webReq); + webRes.ReadToEnd(); + return null; + } + catch (WebException webEx) + { + return (HttpWebResponse)webEx.Response; + } + } + + public static async Task GetErrorResponseAsync(this string url) + { + try + { + var webReq = WebRequest.Create(url); + using var webRes = await webReq.GetResponseAsync().ConfigAwait(); + await webRes.ReadToEndAsync().ConfigAwait(); + return null; + } + catch (WebException webEx) + { + return (HttpWebResponse)webEx.Response; + } + } + + public static void UploadFile(this WebRequest webRequest, Stream fileStream, string fileName, string mimeType, + string accept = null, Action requestFilter = null, string method = "POST", + string field = "file") + { + var httpReq = (HttpWebRequest)webRequest; + httpReq.Method = method; + + if (accept != null) + httpReq.Accept = accept; + + requestFilter?.Invoke(httpReq); + + var boundary = Guid.NewGuid().ToString("N"); + + httpReq.ContentType = "multipart/form-data; boundary=\"" + boundary + "\""; + + var boundaryBytes = ("\r\n--" + boundary + "--\r\n").ToAsciiBytes(); + + var headerBytes = GetHeaderBytes(fileName, mimeType, field, boundary); + + var contentLength = fileStream.Length + headerBytes.Length + boundaryBytes.Length; + PclExport.Instance.InitHttpWebRequest(httpReq, + contentLength: contentLength, allowAutoRedirect: false, keepAlive: false); + + if (ResultsFilter != null) + { + ResultsFilter.UploadStream(httpReq, fileStream, fileName); + return; + } + + using var outputStream = PclExport.Instance.GetRequestStream(httpReq); + outputStream.Write(headerBytes, 0, headerBytes.Length); + fileStream.CopyTo(outputStream, 4096); + outputStream.Write(boundaryBytes, 0, boundaryBytes.Length); + PclExport.Instance.CloseStream(outputStream); + } + + public static async Task UploadFileAsync(this WebRequest webRequest, Stream fileStream, string fileName, + string mimeType, + string accept = null, Action requestFilter = null, string method = "POST", + string field = "file", + CancellationToken token = default) + { + var httpReq = (HttpWebRequest)webRequest; + httpReq.Method = method; + + if (accept != null) + httpReq.Accept = accept; + + requestFilter?.Invoke(httpReq); + + var boundary = Guid.NewGuid().ToString("N"); + + httpReq.ContentType = "multipart/form-data; boundary=\"" + boundary + "\""; + + var boundaryBytes = ("\r\n--" + boundary + "--\r\n").ToAsciiBytes(); + + var headerBytes = GetHeaderBytes(fileName, mimeType, field, boundary); + + var contentLength = fileStream.Length + headerBytes.Length + boundaryBytes.Length; + PclExport.Instance.InitHttpWebRequest(httpReq, + contentLength: contentLength, allowAutoRedirect: false, keepAlive: false); + + if (ResultsFilter != null) + { + ResultsFilter.UploadStream(httpReq, fileStream, fileName); + return; + } + + using var outputStream = PclExport.Instance.GetRequestStream(httpReq); + await outputStream.WriteAsync(headerBytes, 0, headerBytes.Length, token).ConfigAwait(); + await fileStream.CopyToAsync(outputStream, 4096, token).ConfigAwait(); + await outputStream.WriteAsync(boundaryBytes, 0, boundaryBytes.Length, token).ConfigAwait(); + PclExport.Instance.CloseStream(outputStream); + } + + public static void UploadFile(this WebRequest webRequest, Stream fileStream, string fileName) + { + if (fileName == null) + throw new ArgumentNullException(nameof(fileName)); + var mimeType = MimeTypes.GetMimeType(fileName); + if (mimeType == null) + throw new ArgumentException("Mime-type not found for file: " + fileName); + + UploadFile(webRequest, fileStream, fileName, mimeType); + } + + public static async Task UploadFileAsync(this WebRequest webRequest, Stream fileStream, string fileName, + CancellationToken token = default) + { + if (fileName == null) + throw new ArgumentNullException(nameof(fileName)); + var mimeType = MimeTypes.GetMimeType(fileName); + if (mimeType == null) + throw new ArgumentException("Mime-type not found for file: " + fileName); + + await UploadFileAsync(webRequest, fileStream, fileName, mimeType, token: token).ConfigAwait(); + } + + public static string PostXmlToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: data.ToXml(), contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PostCsvToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: data.ToCsv(), contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PutXmlToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: data.ToXml(), contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PutCsvToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: data.ToCsv(), contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static WebResponse PostFileToUrl(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, + string accept = null, + Action requestFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, requestFilter: requestFilter, + method: "POST"); + } + + if (ResultsFilter != null) + return null; + + return webReq.GetResponse(); + } + + public static async Task PostFileToUrlAsync(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, + string accept = null, + Action requestFilter = null, CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + await webReq.UploadFileAsync(fileStream, fileName, uploadFileMimeType, accept: accept, + requestFilter: requestFilter, method: "POST", token: token).ConfigAwait(); + } + + if (ResultsFilter != null) + return null; + + return await webReq.GetResponseAsync().ConfigAwait(); + } + + public static WebResponse PutFileToUrl(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, + string accept = null, + Action requestFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, requestFilter: requestFilter, + method: "PUT"); + } + + if (ResultsFilter != null) + return null; + + return webReq.GetResponse(); + } + + public static async Task PutFileToUrlAsync(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, + string accept = null, + Action requestFilter = null, CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + await webReq.UploadFileAsync(fileStream, fileName, uploadFileMimeType, accept: accept, + requestFilter: requestFilter, method: "PUT", token: token).ConfigAwait(); + } + + if (ResultsFilter != null) + return null; + + return await webReq.GetResponseAsync().ConfigAwait(); + } + + public static WebResponse UploadFile(this WebRequest webRequest, + FileInfo uploadFileInfo, string uploadFileMimeType) + { + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + webRequest.UploadFile(fileStream, fileName, uploadFileMimeType); + } + + if (ResultsFilter != null) + return null; + + return webRequest.GetResponse(); + } + + public static async Task UploadFileAsync(this WebRequest webRequest, + FileInfo uploadFileInfo, string uploadFileMimeType) + { + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + await webRequest.UploadFileAsync(fileStream, fileName, uploadFileMimeType).ConfigAwait(); + } + + if (ResultsFilter != null) + return null; + + return await webRequest.GetResponseAsync().ConfigAwait(); + } + + private static byte[] GetHeaderBytes(string fileName, string mimeType, string field, string boundary) + { + var header = "\r\n--" + boundary + + $"\r\nContent-Disposition: form-data; name=\"{field}\"; filename=\"{fileName}\"\r\nContent-Type: {mimeType}\r\n\r\n"; + + var headerBytes = header.ToAsciiBytes(); + return headerBytes; + } + + public static void DownloadFileTo(this string downloadUrl, string fileName, + List headers = null) + { + var webClient = new WebClient(); + if (headers != null) + { + foreach (var header in headers) + { + webClient.Headers[header.Name] = header.Value; + } + } + webClient.DownloadFile(downloadUrl, fileName); + } + + public static void SetRange(this HttpWebRequest request, long from, long? to) + { + if (to != null) + request.AddRange(from, to.Value); + else + request.AddRange(from); + } + + public static void AddHeader(this HttpWebRequest res, string name, string value) => + res.Headers[name] = value; + public static string GetHeader(this HttpWebRequest res, string name) => + res.Headers.Get(name); + public static string GetHeader(this HttpWebResponse res, string name) => + res.Headers.Get(name); + + public static HttpWebRequest WithHeader(this HttpWebRequest httpReq, string name, string value) + { + httpReq.Headers[name] = value; + return httpReq; + } + + /// + /// Populate HttpRequestMessage with a simpler, untyped API + /// Syntax compatible with HttpClient's HttpRequestMessage + /// + public static HttpWebRequest With(this HttpWebRequest httpReq, Action configure) + { + var config = new HttpRequestConfig(); + configure(config); + + if (config.Accept != null) + httpReq.Accept = config.Accept; + + if (config.UserAgent != null) + httpReq.UserAgent = config.UserAgent; + + if (config.ContentType != null) + httpReq.ContentType = config.ContentType; + + if (config.Referer != null) + httpReq.Referer = config.Referer; + + if (config.Authorization != null) + httpReq.Headers[HttpHeaders.Authorization] = + config.Authorization.Name + " " + config.Authorization.Value; + + if (config.Range != null) + httpReq.SetRange(config.Range.From, config.Range.To); + + if (config.Expect != null) + httpReq.Expect = config.Expect; + + if (config.TransferEncodingChunked != null) + httpReq.TransferEncoding = "chunked"; + else if (config.TransferEncoding?.Length > 0) + httpReq.TransferEncoding = string.Join(", ", config.TransferEncoding); + + foreach (var entry in config.Headers) + { + httpReq.Headers[entry.Name] = entry.Value; + } + + return httpReq; + } +} + +public interface IHttpResultsFilter : IDisposable +{ + string GetString(HttpWebRequest webReq, string reqBody); + byte[] GetBytes(HttpWebRequest webReq, byte[] reqBody); + void UploadStream(HttpWebRequest webRequest, Stream fileStream, string fileName); +} + +public class HttpResultsFilter : IHttpResultsFilter +{ + private readonly IHttpResultsFilter previousFilter; + + public string StringResult { get; set; } + public byte[] BytesResult { get; set; } + + public Func StringResultFn { get; set; } + public Func BytesResultFn { get; set; } + public Action UploadFileFn { get; set; } + + public HttpResultsFilter(string stringResult = null, byte[] bytesResult = null) + { + StringResult = stringResult; + BytesResult = bytesResult; + + previousFilter = HttpUtils.ResultsFilter; + HttpUtils.ResultsFilter = this; + } + + public void Dispose() + { + HttpUtils.ResultsFilter = previousFilter; + } + + public string GetString(HttpWebRequest webReq, string reqBody) + { + return StringResultFn != null + ? StringResultFn(webReq, reqBody) + : StringResult; + } + + public byte[] GetBytes(HttpWebRequest webReq, byte[] reqBody) + { + return BytesResultFn != null + ? BytesResultFn(webReq, reqBody) + : BytesResult; + } + + public void UploadStream(HttpWebRequest webRequest, Stream fileStream, string fileName) + { + UploadFileFn?.Invoke(webRequest, fileStream, fileName); + } +} + +public static class HttpClientExt +{ + /// + /// Case-insensitive, trimmed compare of two content types from start to ';', i.e. without charset suffix + /// + public static bool MatchesContentType(this HttpWebResponse res, string matchesContentType) => + MimeTypes.MatchesContentType(res.Headers[HttpHeaders.ContentType], matchesContentType); + + /// + /// Returns null for unknown Content-length + /// Syntax + Behavior compatible with HttpClient HttpResponseMessage + /// + public static long? GetContentLength(this HttpWebResponse res) => + res.ContentLength == -1 ? null : res.ContentLength; +} +#endif diff --git a/src/ServiceStack.Text/HttpUtils.cs b/src/ServiceStack.Text/HttpUtils.cs new file mode 100644 index 000000000..d96edc574 --- /dev/null +++ b/src/ServiceStack.Text/HttpUtils.cs @@ -0,0 +1,357 @@ +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text; + +namespace ServiceStack; + +public static partial class HttpUtils +{ + public static string UserAgent = "ServiceStack.Text" + +#if NET6_0_OR_GREATER + "/net6" +#elif NETSTANDARD2_0 + "/std2.0" +#elif NETFX + "/net472" +#else + "/unknown" +#endif +; + + public static Encoding UseEncoding { get; set; } = new UTF8Encoding(false); + + public static string AddQueryParam(this string url, string key, object val, bool encode = true) + { + return url.AddQueryParam(key, val?.ToString(), encode); + } + + public static string AddQueryParam(this string url, object key, string val, bool encode = true) + { + return AddQueryParam(url, key?.ToString(), val, encode); + } + + public static string AddQueryParam(this string url, string key, string val, bool encode = true) + { + if (url == null) + url = ""; + + if (key == null || val == null) + return url; + + var prefix = string.Empty; + if (!url.EndsWith("?") && !url.EndsWith("&")) + { + prefix = url.IndexOf('?') == -1 ? "?" : "&"; + } + return url + prefix + key + "=" + (encode ? val.UrlEncode() : val); + } + + public static string SetQueryParam(this string url, string key, string val) + { + if (url == null) + url = ""; + + if (key == null || val == null) + return url; + + var qsPos = url.IndexOf('?'); + if (qsPos != -1) + { + var existingKeyPos = qsPos + 1 == url.IndexOf(key + "=", qsPos, StringComparison.Ordinal) + ? qsPos + : url.IndexOf("&" + key, qsPos, StringComparison.Ordinal); + + if (existingKeyPos != -1) + { + var endPos = url.IndexOf('&', existingKeyPos + 1); + if (endPos == -1) + endPos = url.Length; + + var newUrl = url.Substring(0, existingKeyPos + key.Length + 1) + + "=" + + val.UrlEncode() + + url.Substring(endPos); + return newUrl; + } + } + var prefix = qsPos == -1 ? "?" : "&"; + return url + prefix + key + "=" + val.UrlEncode(); + } + + public static string AddHashParam(this string url, string key, object val) + { + return url.AddHashParam(key, val?.ToString()); + } + + public static string AddHashParam(this string url, string key, string val) + { + if (url == null) + url = ""; + + if (key == null || val == null) + return url; + + var prefix = url.IndexOf('#') == -1 ? "#" : "/"; + return url + prefix + key + "=" + val.UrlEncode(); + } + + public static string SetHashParam(this string url, string key, string val) + { + if (url == null) + url = ""; + + if (key == null || val == null) + return url; + + var hPos = url.IndexOf('#'); + if (hPos != -1) + { + var existingKeyPos = hPos + 1 == url.IndexOf(key + "=", hPos, PclExport.Instance.InvariantComparison) + ? hPos + : url.IndexOf("/" + key, hPos, PclExport.Instance.InvariantComparison); + + if (existingKeyPos != -1) + { + var endPos = url.IndexOf('/', existingKeyPos + 1); + if (endPos == -1) + endPos = url.Length; + + var newUrl = url.Substring(0, existingKeyPos + key.Length + 1) + + "=" + + val.UrlEncode() + + url.Substring(endPos); + return newUrl; + } + } + var prefix = url.IndexOf('#') == -1 ? "#" : "/"; + return url + prefix + key + "=" + val.UrlEncode(); + } + + public static bool HasRequestBody(string httpMethod) + { + switch (httpMethod) + { + case HttpMethods.Get: + case HttpMethods.Delete: + case HttpMethods.Head: + case HttpMethods.Options: + return false; + } + + return true; + } + + public static Task GetRequestStreamAsync(this WebRequest request) + { + return GetRequestStreamAsync((HttpWebRequest)request); + } + + public static Task GetRequestStreamAsync(this HttpWebRequest request) + { + var tcs = new TaskCompletionSource(); + + try + { + request.BeginGetRequestStream(iar => + { + try + { + var response = request.EndGetRequestStream(iar); + tcs.SetResult(response); + } + catch (Exception exc) + { + tcs.SetException(exc); + } + }, null); + } + catch (Exception exc) + { + tcs.SetException(exc); + } + + return tcs.Task; + } + + public static Task ConvertTo(this Task task) where TDerived : TBase + { + var tcs = new TaskCompletionSource(); + task.ContinueWith(t => tcs.SetResult(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion); + task.ContinueWith(t => tcs.SetException(t.Exception.InnerExceptions), TaskContinuationOptions.OnlyOnFaulted); + task.ContinueWith(t => tcs.SetCanceled(), TaskContinuationOptions.OnlyOnCanceled); + return tcs.Task; + } + + public static Task GetResponseAsync(this WebRequest request) + { + return GetResponseAsync((HttpWebRequest)request).ConvertTo(); + } + + public static Task GetResponseAsync(this HttpWebRequest request) + { + var tcs = new TaskCompletionSource(); + + try + { + request.BeginGetResponse(iar => + { + try + { + var response = (HttpWebResponse)request.EndGetResponse(iar); + tcs.SetResult(response); + } + catch (Exception exc) + { + tcs.SetException(exc); + } + }, null); + } + catch (Exception exc) + { + tcs.SetException(exc); + } + + return tcs.Task; + } + + public static bool IsAny300(this Exception ex) + { + var status = ex.GetStatus(); + return status is >= HttpStatusCode.MultipleChoices and < HttpStatusCode.BadRequest; + } + + public static bool IsAny400(this Exception ex) + { + var status = ex.GetStatus(); + return status is >= HttpStatusCode.BadRequest and < HttpStatusCode.InternalServerError; + } + + public static bool IsAny500(this Exception ex) + { + var status = ex.GetStatus(); + return status >= HttpStatusCode.InternalServerError && (int)status < 600; + } + + public static bool IsNotModified(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.NotModified; + } + + public static bool IsBadRequest(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.BadRequest; + } + + public static bool IsNotFound(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.NotFound; + } + + public static bool IsUnauthorized(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.Unauthorized; + } + + public static bool IsForbidden(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.Forbidden; + } + + public static bool IsInternalServerError(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.InternalServerError; + } + + public static HttpStatusCode? GetStatus(this Exception ex) + { +#if NET6_0_OR_GREATER + if (ex is System.Net.Http.HttpRequestException httpEx) + return GetStatus(httpEx); +#endif + + if (ex is WebException webEx) + return GetStatus(webEx); + + if (ex is IHasStatusCode hasStatus) + return (HttpStatusCode)hasStatus.StatusCode; + + return null; + } + +#if NET6_0_OR_GREATER + public static HttpStatusCode? GetStatus(this System.Net.Http.HttpRequestException ex) => ex.StatusCode; +#endif + + public static HttpStatusCode? GetStatus(this WebException webEx) + { + var httpRes = webEx?.Response as HttpWebResponse; + return httpRes?.StatusCode; + } + + public static bool HasStatus(this Exception ex, HttpStatusCode statusCode) + { + return GetStatus(ex) == statusCode; + } + + public static string GetResponseBody(this Exception ex) + { + if (!(ex is WebException webEx) || webEx.Response == null || webEx.Status != WebExceptionStatus.ProtocolError) + return null; + + var errorResponse = (HttpWebResponse)webEx.Response; + using var responseStream = errorResponse.GetResponseStream(); + return responseStream.ReadToEnd(UseEncoding); + } + + public static async Task GetResponseBodyAsync(this Exception ex, CancellationToken token = default) + { + if (!(ex is WebException webEx) || webEx.Response == null || webEx.Status != WebExceptionStatus.ProtocolError) + return null; + + var errorResponse = (HttpWebResponse)webEx.Response; + using var responseStream = errorResponse.GetResponseStream(); + return await responseStream.ReadToEndAsync(UseEncoding).ConfigAwait(); + } + + public static string ReadToEnd(this WebResponse webRes) + { + using var stream = webRes.GetResponseStream(); + return stream.ReadToEnd(UseEncoding); + } + + public static Task ReadToEndAsync(this WebResponse webRes) + { + using var stream = webRes.GetResponseStream(); + return stream.ReadToEndAsync(UseEncoding); + } + + public static IEnumerable ReadLines(this WebResponse webRes) + { + using var stream = webRes.GetResponseStream(); + using var reader = new StreamReader(stream, UseEncoding, true, 1024, leaveOpen: true); + string line; + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } +} + +//Allow Exceptions to Customize HTTP StatusCode and StatusDescription returned +public interface IHasStatusCode +{ + int StatusCode { get; } +} + +public interface IHasStatusDescription +{ + string StatusDescription { get; } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/IStringSerializer.cs b/src/ServiceStack.Text/IStringSerializer.cs new file mode 100644 index 000000000..226f2a59c --- /dev/null +++ b/src/ServiceStack.Text/IStringSerializer.cs @@ -0,0 +1,11 @@ +using System; + +namespace ServiceStack.Text +{ + public interface IStringSerializer + { + To DeserializeFromString(string serializedText); + object DeserializeFromString(string serializedText, Type type); + string SerializeToString(TFrom from); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/ITracer.cs b/src/ServiceStack.Text/ITracer.cs index ea6198298..d177bb30e 100644 --- a/src/ServiceStack.Text/ITracer.cs +++ b/src/ServiceStack.Text/ITracer.cs @@ -2,15 +2,15 @@ namespace ServiceStack.Text { - public interface ITracer - { + public interface ITracer + { void WriteDebug(string error); void WriteDebug(string format, params object[] args); void WriteWarning(string warning); void WriteWarning(string format, params object[] args); - - void WriteError(Exception ex); - void WriteError(string error); - void WriteError(string format, params object[] args); - } + + void WriteError(Exception ex); + void WriteError(string error); + void WriteError(string format, params object[] args); + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/ITypeSerializer.Generic.cs b/src/ServiceStack.Text/ITypeSerializer.Generic.cs index 78917811f..7f3f8744f 100644 --- a/src/ServiceStack.Text/ITypeSerializer.Generic.cs +++ b/src/ServiceStack.Text/ITypeSerializer.Generic.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; @@ -15,43 +15,43 @@ namespace ServiceStack.Text { - public interface ITypeSerializer - { - /// - /// Determines whether this serializer can create the specified type from a string. - /// - /// The type. - /// - /// true if this instance [can create from string] the specified type; otherwise, false. - /// - bool CanCreateFromString(Type type); + public interface ITypeSerializer + { + /// + /// Determines whether this serializer can create the specified type from a string. + /// + /// The type. + /// + /// true if this instance [can create from string] the specified type; otherwise, false. + /// + bool CanCreateFromString(Type type); - /// - /// Parses the specified value. - /// - /// The value. - /// - T DeserializeFromString(string value); + /// + /// Parses the specified value. + /// + /// The value. + /// + T DeserializeFromString(string value); - /// - /// Deserializes from reader. - /// - /// The reader. - /// - T DeserializeFromReader(TextReader reader); + /// + /// Deserializes from reader. + /// + /// The reader. + /// + T DeserializeFromReader(TextReader reader); - /// - /// Serializes to string. - /// - /// The value. - /// - string SerializeToString(T value); + /// + /// Serializes to string. + /// + /// The value. + /// + string SerializeToString(T value); - /// - /// Serializes to writer. - /// - /// The value. - /// The writer. - void SerializeToWriter(T value, TextWriter writer); - } + /// + /// Serializes to writer. + /// + /// The value. + /// The writer. + void SerializeToWriter(T value, TextWriter writer); + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/JsAot.cs b/src/ServiceStack.Text/JsAot.cs new file mode 100644 index 000000000..103360426 --- /dev/null +++ b/src/ServiceStack.Text/JsAot.cs @@ -0,0 +1,225 @@ +#if __IOS__ || __ANDROID__ || WINDOWS_UWP || __MOBILE__ + +// When Linking "SDK and User Assemblies" in Xamarin you can copy this class to your project and call `JsAot.Run()` on Startup +// Alternative solution is to add 'ServiceStack.Text' to your "Skip linking assemblies" list which should contain: +// ServiceStack.Text;ServiceStack.Client;{Your}.ServiceModel + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using ServiceStack.Text; +using ServiceStack.Text.Common; +using Xamarin.Forms.Internals; + +namespace ServiceStack +{ + public static class JsAot + { + [Preserve] + public static void Init() {} + + /// + /// Provide hint to IOS AOT compiler to pre-compile generic classes for all your DTOs. + /// Just needs to be called once in a static constructor. + /// + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void Run() + { + try + { + RegisterForAot(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + + [Preserve(AllMembers = true)] + internal class Poco + { + public string Dummy { get; set; } + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static void RegisterForAot() + { + RegisterTypeForAot(); + + RegisterElement(); + + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + + RegisterElement(); + RegisterTypeForAot(); // used by DateTime + + // register built in structs + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void RegisterTypeForAot() + { + AotConfig.RegisterSerializers(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void RegisterElement() + { + AotConfig.RegisterSerializers(); + AotConfig.RegisterElement(); + AotConfig.RegisterElement(); + } + + [Preserve(AllMembers = true)] + internal class AotConfig + { + internal static JsReader jsonReader; + internal static JsWriter jsonWriter; + internal static JsReader jsvReader; + internal static JsWriter jsvWriter; + internal static Text.Json.JsonTypeSerializer jsonSerializer; + internal static Text.Jsv.JsvTypeSerializer jsvSerializer; + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + static AotConfig() + { + jsonSerializer = new Text.Json.JsonTypeSerializer(); + jsvSerializer = new Text.Jsv.JsvTypeSerializer(); + jsonReader = new JsReader(); + jsonWriter = new JsWriter(); + jsvReader = new JsReader(); + jsvWriter = new JsWriter(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static void RegisterSerializers() + { + Register(); + jsonSerializer.GetParseFn(); + jsonSerializer.GetWriteFn(); + jsonReader.GetParseFn(); + jsonWriter.GetWriteFn(); + + Register(); + jsvSerializer.GetParseFn(); + jsvSerializer.GetWriteFn(); + jsvReader.GetParseFn(); + jsvWriter.GetWriteFn(); + + CsvSerializer.InitAot(); + QueryStringWriter.WriteFn(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static ParseStringDelegate GetParseFn(Type type) + { + var parseFn = Text.Json.JsonTypeSerializer.Instance.GetParseFn(type); + return parseFn; + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static void Register() where TSerializer : ITypeSerializer + { + Text.Json.JsonReader.InitAot(); + Text.Json.JsonWriter.InitAot(); + + Text.Jsv.JsvReader.InitAot(); + Text.Jsv.JsvWriter.InitAot(); + + var hold = new object[] + { + new List(), + new T[0], + new Dictionary(), + new Dictionary(), + new HashSet(), + }; + + JsConfig.ExcludeTypeInfo = false; + + var r1 = JsConfig.OnDeserializedFn; + var r2 = JsConfig.HasDeserializeFn; + var r3 = JsConfig.SerializeFn; + var r4 = JsConfig.DeSerializeFn; + var r5 = TypeConfig.Properties; + + JsReader.InitAot(); + JsWriter.InitAot(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static void RegisterElement() where TSerializer : ITypeSerializer + { + DeserializeDictionary.ParseDictionary(null, null, null, null); + DeserializeDictionary.ParseDictionary(null, null, null, null); + + ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); + ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); + + // Include List deserialisations from the Register<> method above. This solves issue where List properties on responses deserialise to null. + // No idea why this is happening because there is no visible exception raised. Suspect IOS is swallowing an AOT exception somewhere. + DeserializeArrayWithElements.ParseGenericArray(null, null); + DeserializeListWithElements.ParseGenericList(null, null, null); + + // Cannot use the line below for some unknown reason - when trying to compile to run on device, mtouch bombs during native code compile. + // Something about this line or its inner workings is offensive to mtouch. Luckily this was not needed for my List issue. + // DeserializeCollection.ParseCollection(null, null, null); + + TranslateListWithElements.LateBoundTranslateToGenericICollection(null, typeof(List)); + TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, typeof(List)); + } + } + } + +} + +#endif diff --git a/src/ServiceStack.Text/JsConfig.cs b/src/ServiceStack.Text/JsConfig.cs index ef125a8e6..455ac33ec 100644 --- a/src/ServiceStack.Text/JsConfig.cs +++ b/src/ServiceStack.Text/JsConfig.cs @@ -1,415 +1,615 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Text; +using System.Threading; using ServiceStack.Text.Common; using ServiceStack.Text.Json; using ServiceStack.Text.Jsv; - -#if WINDOWS_PHONE -using ServiceStack.Text.WP; -#endif - namespace ServiceStack.Text { - public static class - JsConfig + public static class JsConfig { static JsConfig() { - //In-built default serialization, to Deserialize Color struct do: - //JsConfig.SerializeFn = c => c.ToString().Replace("Color ", "").Replace("[", "").Replace("]", ""); - //JsConfig.DeSerializeFn = System.Drawing.Color.FromName; Reset(); + LicenseUtils.Init(); } + // force deterministic initialization of static constructor + public static void InitStatics() { } + + /// + /// Mark JsConfig global config as initialized and assert it's no longer mutated + /// + /// + public static void Init() => Config.Init(); + + /// + /// Initialize global config and assert that it's no longer mutated + /// + /// + public static void Init(Config config) => Config.Init(config); + + public static bool HasInit => Config.HasInit; + public static JsConfigScope BeginScope() { - return new JsConfigScope(); + return new JsConfigScope(); //Populated with Config.Instance } - [ThreadStatic] - private static bool? tsConvertObjectTypesIntoStringDictionary; - private static bool? sConvertObjectTypesIntoStringDictionary; - public static bool ConvertObjectTypesIntoStringDictionary + public static JsConfigScope CreateScope(string config, JsConfigScope scope = null) { - get + if (string.IsNullOrEmpty(config)) + return scope; + + if (scope == null) + scope = BeginScope(); + + var items = config.Split(','); + foreach (var item in items) { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ConvertObjectTypesIntoStringDictionary: null) - ?? tsConvertObjectTypesIntoStringDictionary - ?? sConvertObjectTypesIntoStringDictionary - ?? false; + var parts = item.SplitOnFirst(':'); + var key = parts[0].ToLower(); + var value = parts.Length == 2 ? parts[1].ToLower() : null; + var boolValue = parts.Length == 1 || (value != "false" && value != "0"); + + switch (key) + { + case "cotisd": + case "convertobjecttypesintostringdictionary": + scope.ConvertObjectTypesIntoStringDictionary = boolValue; + break; + case "ttpptv": + case "trytoparseprimitivetypevalues": + scope.TryToParsePrimitiveTypeValues = boolValue; + break; + case "ttpnt": + case "trytoparsenumerictype": + scope.TryToParseNumericType = boolValue; + break; + case "edv": + case "excludedefaultvalues": + scope.ExcludeDefaultValues = boolValue; + break; + case "inv": + case "includenullvalues": + scope.IncludeNullValues = boolValue; + break; + case "invid": + case "includenullvaluesindictionaries": + scope.IncludeNullValuesInDictionaries = boolValue; + break; + case "ide": + case "includedefaultenums": + scope.IncludeDefaultEnums = boolValue; + break; + case "eti": + case "excludetypeinfo": + scope.ExcludeTypeInfo = boolValue; + break; + case "iti": + case "includetypeinfo": + scope.IncludeTypeInfo = boolValue; + break; + case "i": + case "pp": //pretty-print + case "indent": + scope.Indent = boolValue; + break; + case "eccn": + case "emitcamelcasenames": + scope.TextCase = boolValue ? TextCase.CamelCase : scope.TextCase; + break; + case "elun": + case "emitlowercaseunderscorenames": + scope.TextCase = boolValue ? TextCase.SnakeCase : scope.TextCase; + break; + case "pi": + case "preferinterfaces": + scope.PreferInterfaces = boolValue; + break; + case "tode": + case "throwondeserializationerror": + case "toe": + case "throwonerror": + scope.ThrowOnError = boolValue; + break; + case "teai": + case "treatenumasinteger": + scope.TreatEnumAsInteger = boolValue; + break; + case "sdtc": + case "skipdatetimeconversion": + scope.SkipDateTimeConversion = boolValue; + break; + case "auu": + case "alwaysuseutc": + scope.AlwaysUseUtc = boolValue; + break; + case "au": + case "assumeutc": + scope.AssumeUtc = boolValue; + break; + case "auo": + case "appendutcoffset": + scope.AppendUtcOffset = boolValue; + break; + case "ipf": + case "includepublicfields": + scope.IncludePublicFields = boolValue; + break; + case "dh": + case "datehandler": + switch (value) + { + case "timestampoffset": + case "to": + scope.DateHandler = DateHandler.TimestampOffset; + break; + case "dcjsc": + case "dcjscompatible": + scope.DateHandler = DateHandler.DCJSCompatible; + break; + case "iso8601": + scope.DateHandler = DateHandler.ISO8601; + break; + case "iso8601do": + case "iso8601dateonly": + scope.DateHandler = DateHandler.ISO8601DateOnly; + break; + case "iso8601dt": + case "iso8601datetime": + scope.DateHandler = DateHandler.ISO8601DateTime; + break; + case "rfc1123": + scope.DateHandler = DateHandler.RFC1123; + break; + case "ut": + case "unixtime": + scope.DateHandler = DateHandler.UnixTime; + break; + case "utm": + case "unixtimems": + scope.DateHandler = DateHandler.UnixTimeMs; + break; + } + break; + case "tsh": + case "timespanhandler": + switch (value) + { + case "df": + case "durationformat": + scope.TimeSpanHandler = TimeSpanHandler.DurationFormat; + break; + case "sf": + case "standardformat": + scope.TimeSpanHandler = TimeSpanHandler.StandardFormat; + break; + } + break; + case "pc": + case "propertyconvention": + switch (value) + { + case "l": + case "lenient": + scope.PropertyConvention = PropertyConvention.Lenient; + break; + case "s": + case "strict": + scope.PropertyConvention = PropertyConvention.Strict; + break; + } + break; + case "tc": + case "textcase": + switch (value) + { + case "d": + case "default": + scope.TextCase = TextCase.Default; + break; + case "pc": + case "pascalcase": + scope.TextCase = TextCase.PascalCase; + break; + case "cc": + case "camelcase": + scope.TextCase = TextCase.CamelCase; + break; + case "sc": + case "snakecase": + scope.TextCase = TextCase.SnakeCase; + break; + } + break; + } } - set + + return scope; + } + + public static UTF8Encoding UTF8Encoding { get; set; } = new UTF8Encoding(false); + + public static JsConfigScope With(Config config) => (JsConfigScope)new JsConfigScope().Populate(config); + + [Obsolete("Use JsConfig.With(new Config { })")] + public static JsConfigScope With( + bool? convertObjectTypesIntoStringDictionary = null, + bool? tryToParsePrimitiveTypeValues = null, + bool? tryToParseNumericType = null, + ParseAsType? parsePrimitiveFloatingPointTypes = null, + ParseAsType? parsePrimitiveIntegerTypes = null, + bool? excludeDefaultValues = null, + bool? includeNullValues = null, + bool? includeNullValuesInDictionaries = null, + bool? includeDefaultEnums = null, + bool? excludeTypeInfo = null, + bool? includeTypeInfo = null, + bool? emitCamelCaseNames = null, + bool? emitLowercaseUnderscoreNames = null, + DateHandler? dateHandler = null, + TimeSpanHandler? timeSpanHandler = null, + PropertyConvention? propertyConvention = null, + bool? preferInterfaces = null, + bool? throwOnDeserializationError = null, + string typeAttr = null, + string dateTimeFormat = null, + Func typeWriter = null, + Func typeFinder = null, + bool? treatEnumAsInteger = null, + bool? skipDateTimeConversion = null, + bool? alwaysUseUtc = null, + bool? assumeUtc = null, + bool? appendUtcOffset = null, + bool? escapeUnicode = null, + bool? includePublicFields = null, + int? maxDepth = null, + EmptyCtorFactoryDelegate modelFactory = null, + string[] excludePropertyReferences = null, + bool? useSystemParseMethods = null) //Unused + { + return new JsConfigScope { - tsConvertObjectTypesIntoStringDictionary = value; - if (!sConvertObjectTypesIntoStringDictionary.HasValue) sConvertObjectTypesIntoStringDictionary = value; - } + ConvertObjectTypesIntoStringDictionary = convertObjectTypesIntoStringDictionary ?? Config.Instance.ConvertObjectTypesIntoStringDictionary, + TryToParsePrimitiveTypeValues = tryToParsePrimitiveTypeValues ?? Config.Instance.TryToParsePrimitiveTypeValues, + TryToParseNumericType = tryToParseNumericType ?? Config.Instance.TryToParseNumericType, + + ParsePrimitiveFloatingPointTypes = parsePrimitiveFloatingPointTypes ?? Config.Instance.ParsePrimitiveFloatingPointTypes, + ParsePrimitiveIntegerTypes = parsePrimitiveIntegerTypes ?? Config.Instance.ParsePrimitiveIntegerTypes, + + ExcludeDefaultValues = excludeDefaultValues ?? Config.Instance.ExcludeDefaultValues, + IncludeNullValues = includeNullValues ?? Config.Instance.IncludeNullValues, + IncludeNullValuesInDictionaries = includeNullValuesInDictionaries ?? Config.Instance.IncludeNullValuesInDictionaries, + IncludeDefaultEnums = includeDefaultEnums ?? Config.Instance.IncludeDefaultEnums, + ExcludeTypeInfo = excludeTypeInfo ?? Config.Instance.ExcludeTypeInfo, + IncludeTypeInfo = includeTypeInfo ?? Config.Instance.IncludeTypeInfo, + EmitCamelCaseNames = emitCamelCaseNames ?? Config.Instance.EmitCamelCaseNames, + EmitLowercaseUnderscoreNames = emitLowercaseUnderscoreNames ?? Config.Instance.EmitLowercaseUnderscoreNames, + DateHandler = dateHandler ?? Config.Instance.DateHandler, + TimeSpanHandler = timeSpanHandler ?? Config.Instance.TimeSpanHandler, + PropertyConvention = propertyConvention ?? Config.Instance.PropertyConvention, + PreferInterfaces = preferInterfaces ?? Config.Instance.PreferInterfaces, + ThrowOnError = throwOnDeserializationError ?? Config.Instance.ThrowOnError, + DateTimeFormat = dateTimeFormat ?? Config.Instance.DateTimeFormat, + TypeAttr = typeAttr ?? Config.Instance.TypeAttr, + TypeWriter = typeWriter ?? Config.Instance.TypeWriter, + TypeFinder = typeFinder ?? Config.Instance.TypeFinder, + TreatEnumAsInteger = treatEnumAsInteger ?? Config.Instance.TreatEnumAsInteger, + SkipDateTimeConversion = skipDateTimeConversion ?? Config.Instance.SkipDateTimeConversion, + AlwaysUseUtc = alwaysUseUtc ?? Config.Instance.AlwaysUseUtc, + AssumeUtc = assumeUtc ?? Config.Instance.AssumeUtc, + AppendUtcOffset = appendUtcOffset ?? Config.Instance.AppendUtcOffset, + EscapeUnicode = escapeUnicode ?? Config.Instance.EscapeUnicode, + IncludePublicFields = includePublicFields ?? Config.Instance.IncludePublicFields, + MaxDepth = maxDepth ?? Config.Instance.MaxDepth, + ModelFactory = modelFactory ?? Config.Instance.ModelFactory, + ExcludePropertyReferences = excludePropertyReferences ?? Config.Instance.ExcludePropertyReferences, + }; + } + + public static string DateTimeFormat + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.DateTimeFormat : Config.Instance.DateTimeFormat; + set => Config.AssertNotInit().DateTimeFormat = value; + } + + public static bool ConvertObjectTypesIntoStringDictionary + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.ConvertObjectTypesIntoStringDictionary : Config.Instance.ConvertObjectTypesIntoStringDictionary; + set => Config.AssertNotInit().ConvertObjectTypesIntoStringDictionary = value; } - [ThreadStatic] - private static bool? tsTryToParsePrimitiveTypeValues; - private static bool? sTryToParsePrimitiveTypeValues; public static bool TryToParsePrimitiveTypeValues { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TryToParsePrimitiveTypeValues: null) - ?? tsTryToParsePrimitiveTypeValues - ?? sTryToParsePrimitiveTypeValues - ?? false; - } - set - { - tsTryToParsePrimitiveTypeValues = value; - if (!sTryToParsePrimitiveTypeValues.HasValue) sTryToParsePrimitiveTypeValues = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TryToParsePrimitiveTypeValues : Config.Instance.TryToParsePrimitiveTypeValues; + set => Config.AssertNotInit().TryToParsePrimitiveTypeValues = value; + } + + public static bool TryToParseNumericType + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.TryToParseNumericType : Config.Instance.TryToParseNumericType; + set => Config.AssertNotInit().TryToParseNumericType = value; + } + + public static bool TryParseIntoBestFit + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.TryParseIntoBestFit : Config.Instance.TryParseIntoBestFit; + set => Config.AssertNotInit().TryParseIntoBestFit = value; + } + + public static ParseAsType ParsePrimitiveFloatingPointTypes + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.ParsePrimitiveFloatingPointTypes : Config.Instance.ParsePrimitiveFloatingPointTypes; + set => Config.AssertNotInit().ParsePrimitiveFloatingPointTypes = value; + } + + public static ParseAsType ParsePrimitiveIntegerTypes + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.ParsePrimitiveIntegerTypes : Config.Instance.ParsePrimitiveIntegerTypes; + set => Config.AssertNotInit().ParsePrimitiveIntegerTypes = value; + } + + public static string[] ExcludePropertyReferences + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludePropertyReferences : Config.Instance.ExcludePropertyReferences; + set => Config.AssertNotInit().ExcludePropertyReferences = value; + } + + public static bool ExcludeDefaultValues + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeDefaultValues : Config.Instance.ExcludeDefaultValues; + set => Config.AssertNotInit().ExcludeDefaultValues = value; } - [ThreadStatic] - private static bool? tsIncludeNullValues; - private static bool? sIncludeNullValues; public static bool IncludeNullValues { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.IncludeNullValues: null) - ?? tsIncludeNullValues - ?? sIncludeNullValues - ?? false; - } - set - { - tsIncludeNullValues = value; - if (!sIncludeNullValues.HasValue) sIncludeNullValues = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludeNullValues : Config.Instance.IncludeNullValues; + set => Config.AssertNotInit().IncludeNullValues = value; + } + + public static bool IncludeNullValuesInDictionaries + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludeNullValuesInDictionaries : Config.Instance.IncludeNullValuesInDictionaries; + set => Config.AssertNotInit().IncludeNullValuesInDictionaries = value; + } + + public static bool IncludeDefaultEnums + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludeDefaultEnums : Config.Instance.IncludeDefaultEnums; + set => Config.AssertNotInit().IncludeDefaultEnums = value; } - [ThreadStatic] - private static bool? tsTreatEnumAsInteger; - private static bool? sTreatEnumAsInteger; public static bool TreatEnumAsInteger { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TreatEnumAsInteger: null) - ?? tsTreatEnumAsInteger - ?? sTreatEnumAsInteger - ?? false; - } - set - { - tsTreatEnumAsInteger = value; - if (!sTreatEnumAsInteger.HasValue) sTreatEnumAsInteger = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TreatEnumAsInteger : Config.Instance.TreatEnumAsInteger; + set => Config.AssertNotInit().TreatEnumAsInteger = value; } - [ThreadStatic] - private static bool? tsExcludeTypeInfo; - private static bool? sExcludeTypeInfo; public static bool ExcludeTypeInfo { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeTypeInfo: null) - ?? tsExcludeTypeInfo - ?? sExcludeTypeInfo - ?? false; - } - set - { - tsExcludeTypeInfo = value; - if (!sExcludeTypeInfo.HasValue) sExcludeTypeInfo = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeTypeInfo : Config.Instance.ExcludeTypeInfo; + set => Config.AssertNotInit().ExcludeTypeInfo = value; } - [ThreadStatic] - private static bool? tsForceTypeInfo; - private static bool? sForceTypeInfo; public static bool IncludeTypeInfo { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.IncludeTypeInfo: null) - ?? tsForceTypeInfo - ?? sForceTypeInfo - ?? false; - } - set - { - if (!tsForceTypeInfo.HasValue) tsForceTypeInfo = value; - if (!sForceTypeInfo.HasValue) sForceTypeInfo = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludeTypeInfo : Config.Instance.IncludeTypeInfo; + set => Config.AssertNotInit().IncludeTypeInfo = value; + } + + public static bool Indent + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.Indent : Config.Instance.Indent; + set => Config.AssertNotInit().Indent = value; } - [ThreadStatic] - private static string tsTypeAttr; - private static string sTypeAttr; public static string TypeAttr { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TypeAttr: null) - ?? tsTypeAttr - ?? sTypeAttr - ?? JsWriter.TypeAttr; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TypeAttr : Config.Instance.TypeAttr; set { - tsTypeAttr = value; - if (sTypeAttr == null) sTypeAttr = value; - JsonTypeAttrInObject = JsonTypeSerializer.GetTypeAttrInObject(value); - JsvTypeAttrInObject = JsvTypeSerializer.GetTypeAttrInObject(value); + var config = Config.AssertNotInit(); + config.TypeAttr = value; } } - [ThreadStatic] - private static string tsJsonTypeAttrInObject; - private static string sJsonTypeAttrInObject; - private static readonly string defaultJsonTypeAttrInObject = JsonTypeSerializer.GetTypeAttrInObject(TypeAttr); internal static string JsonTypeAttrInObject { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.JsonTypeAttrInObject: null) - ?? tsJsonTypeAttrInObject - ?? sJsonTypeAttrInObject - ?? defaultJsonTypeAttrInObject; - } - set - { - tsJsonTypeAttrInObject = value; - if (sJsonTypeAttrInObject == null) sJsonTypeAttrInObject = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.JsonTypeAttrInObject : Config.Instance.JsonTypeAttrInObject; } - [ThreadStatic] - private static string tsJsvTypeAttrInObject; - private static string sJsvTypeAttrInObject; - private static readonly string defaultJsvTypeAttrInObject = JsvTypeSerializer.GetTypeAttrInObject(TypeAttr); internal static string JsvTypeAttrInObject { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.JsvTypeAttrInObject: null) - ?? tsJsvTypeAttrInObject - ?? sJsvTypeAttrInObject - ?? defaultJsvTypeAttrInObject; - } - set - { - tsJsvTypeAttrInObject = value; - if (sJsvTypeAttrInObject == null) sJsvTypeAttrInObject = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.JsvTypeAttrInObject : Config.Instance.JsvTypeAttrInObject; } - [ThreadStatic] - private static Func tsTypeWriter; - private static Func sTypeWriter; public static Func TypeWriter { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TypeWriter: null) - ?? tsTypeWriter - ?? sTypeWriter - ?? AssemblyUtils.WriteType; - } - set - { - tsTypeWriter = value; - if (sTypeWriter == null) sTypeWriter = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TypeWriter : Config.Instance.TypeWriter; + set => Config.AssertNotInit().TypeWriter = value; } - [ThreadStatic] - private static Func tsTypeFinder; - private static Func sTypeFinder; public static Func TypeFinder { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TypeFinder: null) - ?? tsTypeFinder - ?? sTypeFinder - ?? AssemblyUtils.FindType; - } - set - { - tsTypeFinder = value; - if (sTypeFinder == null) sTypeFinder = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TypeFinder : Config.Instance.TypeFinder; + set => Config.AssertNotInit().TypeFinder = value; } - [ThreadStatic] - private static JsonDateHandler? tsDateHandler; - private static JsonDateHandler? sDateHandler; - public static JsonDateHandler DateHandler + public static Func ParsePrimitiveFn { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.DateHandler: null) - ?? tsDateHandler - ?? sDateHandler - ?? JsonDateHandler.TimestampOffset; - } - set - { - tsDateHandler = value; - if (!sDateHandler.HasValue) sDateHandler = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ParsePrimitiveFn : Config.Instance.ParsePrimitiveFn; + set => Config.AssertNotInit().ParsePrimitiveFn = value; + } + + public static DateHandler DateHandler + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.DateHandler : Config.Instance.DateHandler; + set => Config.AssertNotInit().DateHandler = value; } /// /// Sets which format to use when serializing TimeSpans /// - public static JsonTimeSpanHandler TimeSpanHandler { get; set; } + public static TimeSpanHandler TimeSpanHandler + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.TimeSpanHandler : Config.Instance.TimeSpanHandler; + set => Config.AssertNotInit().TimeSpanHandler = value; + } + + /// + /// Text case to use for property names (Default = PascalCase) + /// + public static TextCase TextCase + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.TextCase : Config.Instance.TextCase; + set => Config.AssertNotInit().TextCase = value; + } /// - /// if the is configured - /// to take advantage of specification, - /// to support user-friendly serialized formats, ie emitting camelCasing for JSON - /// and parsing member names and enum values in a case-insensitive manner. + /// Emitting camelCase for property names /// - [ThreadStatic] - private static bool? tsEmitCamelCaseNames; - private static bool? sEmitCamelCaseNames; + [Obsolete("Use TextCase = TextCase.CamelCase")] public static bool EmitCamelCaseNames { - // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.EmitCamelCaseNames: null) - ?? tsEmitCamelCaseNames - ?? sEmitCamelCaseNames - ?? false; - } - set - { - tsEmitCamelCaseNames = value; - if (!sEmitCamelCaseNames.HasValue) sEmitCamelCaseNames = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.EmitCamelCaseNames : Config.Instance.EmitCamelCaseNames; + set => Config.AssertNotInit().EmitCamelCaseNames = value; } /// - /// if the is configured - /// to support web-friendly serialized formats, ie emitting lowercase_underscore_casing for JSON + /// Emitting lowercase_underscore_casing for property names /// - [ThreadStatic] - private static bool? tsEmitLowercaseUnderscoreNames; - private static bool? sEmitLowercaseUnderscoreNames; + [Obsolete("Use TextCase = TextCase.SnakeCase")] public static bool EmitLowercaseUnderscoreNames { // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.EmitLowercaseUnderscoreNames: null) - ?? tsEmitLowercaseUnderscoreNames - ?? sEmitLowercaseUnderscoreNames - ?? false; - } - set - { - tsEmitLowercaseUnderscoreNames = value; - if (!sEmitLowercaseUnderscoreNames.HasValue) sEmitLowercaseUnderscoreNames = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.EmitLowercaseUnderscoreNames : Config.Instance.EmitLowercaseUnderscoreNames; + set => Config.AssertNotInit().EmitLowercaseUnderscoreNames = value; } + //Avoid multiple static property checks by getting snapshot of active config + public static Config GetConfig() => JsConfigScope.Current != null ? JsConfigScope.Current : Config.Instance; + /// /// Define how property names are mapped during deserialization /// - private static JsonPropertyConvention propertyConvention; - public static JsonPropertyConvention PropertyConvention + public static PropertyConvention PropertyConvention { - get { return propertyConvention; } - set - { - propertyConvention = value; - switch (propertyConvention) - { - case JsonPropertyConvention.ExactMatch: - DeserializeTypeRefJson.PropertyNameResolver = DeserializeTypeRefJson.DefaultPropertyNameResolver; - break; - case JsonPropertyConvention.Lenient: - DeserializeTypeRefJson.PropertyNameResolver = DeserializeTypeRefJson.LenientPropertyNameResolver; - break; - } - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.PropertyConvention : Config.Instance.PropertyConvention; + set => Config.AssertNotInit().PropertyConvention = value; } - /// /// Gets or sets a value indicating if the framework should throw serialization exceptions - /// or continue regardless of deserialization errors. If the framework + /// or continue regardless of serialization errors. If the framework /// will throw; otherwise, it will parse as many fields as possible. The default is . /// - [ThreadStatic] - private static bool? tsThrowOnDeserializationError; - private static bool? sThrowOnDeserializationError; - public static bool ThrowOnDeserializationError + public static bool ThrowOnError { // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ThrowOnDeserializationError: null) - ?? tsThrowOnDeserializationError - ?? sThrowOnDeserializationError - ?? false; - } - set - { - tsThrowOnDeserializationError = value; - if (!sThrowOnDeserializationError.HasValue) sThrowOnDeserializationError = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ThrowOnError : Config.Instance.ThrowOnError; + set => Config.AssertNotInit().ThrowOnError = value; } + [Obsolete("Renamed to ThrowOnError")] + public static bool ThrowOnDeserializationError + { + get => ThrowOnError; + set => ThrowOnError = value; + } + /// /// Gets or sets a value indicating if the framework should always convert to UTC format instead of local time. /// - [ThreadStatic] - private static bool? tsAlwaysUseUtc; - private static bool? sAlwaysUseUtc; public static bool AlwaysUseUtc + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.AlwaysUseUtc : Config.Instance.AlwaysUseUtc; + set => Config.AssertNotInit().AlwaysUseUtc = value; + } + + /// + /// Gets or sets a value indicating if the framework should skip automatic conversions. + /// Dates will be handled literally, any included timezone encoding will be lost and the date will be treaded as DateTimeKind.Local + /// Utc formatted input will result in DateTimeKind.Utc output. Any input without TZ data will be set DateTimeKind.Unspecified + /// This will take precedence over other flags like AlwaysUseUtc + /// JsConfig.DateHandler = DateHandler.ISO8601 should be used when set true for consistent de/serialization. + /// + public static bool SkipDateTimeConversion { // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.AlwaysUseUtc: null) - ?? tsAlwaysUseUtc - ?? sAlwaysUseUtc - ?? false; - } - set - { - tsAlwaysUseUtc = value; - if (!sAlwaysUseUtc.HasValue) sAlwaysUseUtc = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.SkipDateTimeConversion : Config.Instance.SkipDateTimeConversion; + set => Config.AssertNotInit().SkipDateTimeConversion = value; + } + + /// + /// Gets or sets a value indicating if the framework should always assume is in UTC format if Kind is Unspecified. + /// + public static bool AssumeUtc + { + // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case + get => JsConfigScope.Current != null ? JsConfigScope.Current.AssumeUtc : Config.Instance.AssumeUtc; + set => Config.AssertNotInit().AssumeUtc = value; + } + + /// + /// Gets or sets whether we should append the Utc offset when we serialize Utc dates. Defaults to no. + /// Only supported for when the JsConfig.DateHandler == JsonDateHandler.TimestampOffset + /// + public static bool AppendUtcOffset + { + // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case + get => JsConfigScope.Current != null ? JsConfigScope.Current.AppendUtcOffset : Config.Instance.AppendUtcOffset; + set => Config.AssertNotInit().AppendUtcOffset = value; + } + + /// + /// Gets or sets a value indicating if unicode symbols should be serialized as "\uXXXX". + /// + public static bool EscapeUnicode + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.EscapeUnicode : Config.Instance.EscapeUnicode; + set => Config.AssertNotInit().EscapeUnicode = value; + } + + /// + /// Gets or sets a value indicating if HTML entity chars [> < & = '] should be escaped as "\uXXXX". + /// + public static bool EscapeHtmlChars + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.EscapeHtmlChars : Config.Instance.EscapeHtmlChars; + set => Config.AssertNotInit().EscapeHtmlChars = value; + } + + /// + /// Gets or sets a value indicating if the framework should call an error handler when + /// an exception happens during the deserialization. + /// + /// Parameters have following meaning in order: deserialized entity, property name, parsed value, property type, caught exception. + public static DeserializationErrorDelegate OnDeserializationError + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.OnDeserializationError : Config.Instance.OnDeserializationError; + set => Config.AssertNotInit().OnDeserializationError = value; } internal static HashSet HasSerializeFn = new HashSet(); - internal static HashSet TreatValueAsRefTypes = new HashSet(); + internal static HashSet HasIncludeDefaultValue = new HashSet(); + + public static HashSet TreatValueAsRefTypes = new HashSet(); - [ThreadStatic] - private static bool? tsPreferInterfaces; - private static bool? sPreferInterfaces; /// - /// If set to true, Interface types will be prefered over concrete types when serializing. + /// If set to true, Interface types will be preferred over concrete types when serializing. /// public static bool PreferInterfaces { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.PreferInterfaces: null) - ?? tsPreferInterfaces - ?? sPreferInterfaces - ?? false; - } - set - { - tsPreferInterfaces = value; - if (!sPreferInterfaces.HasValue) sPreferInterfaces = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.PreferInterfaces : Config.Instance.PreferInterfaces; + set => Config.AssertNotInit().PreferInterfaces = value; } internal static bool TreatAsRefType(Type valueType) @@ -417,281 +617,212 @@ internal static bool TreatAsRefType(Type valueType) return TreatValueAsRefTypes.Contains(valueType.IsGenericType ? valueType.GetGenericTypeDefinition() : valueType); } - public static void Reset() + /// + /// If set to true, Interface types will be preferred over concrete types when serializing. + /// + public static bool IncludePublicFields { - ModelFactory = ReflectionExtensions.GetConstructorMethodToCache; - tsTryToParsePrimitiveTypeValues = sTryToParsePrimitiveTypeValues = null; - tsConvertObjectTypesIntoStringDictionary = sConvertObjectTypesIntoStringDictionary = null; - tsIncludeNullValues = sIncludeNullValues = null; - tsExcludeTypeInfo = sExcludeTypeInfo = null; - tsEmitCamelCaseNames = sEmitCamelCaseNames = null; - tsEmitLowercaseUnderscoreNames = sEmitLowercaseUnderscoreNames = null; - tsDateHandler = sDateHandler = null; - tsPreferInterfaces = sPreferInterfaces = null; - tsThrowOnDeserializationError = sThrowOnDeserializationError = null; - tsTypeAttr = sTypeAttr = null; - tsJsonTypeAttrInObject = sJsonTypeAttrInObject = null; - tsJsvTypeAttrInObject = sJsvTypeAttrInObject = null; - tsTypeWriter = sTypeWriter = null; - tsTypeFinder = sTypeFinder = null; - tsTreatEnumAsInteger = sTreatEnumAsInteger = null; - tsAlwaysUseUtc = sAlwaysUseUtc = null; - HasSerializeFn = new HashSet(); - TreatValueAsRefTypes = new HashSet { typeof(KeyValuePair<,>) }; - PropertyConvention = JsonPropertyConvention.ExactMatch; - } - -#if MONOTOUCH - /// - /// Provide hint to MonoTouch AOT compiler to pre-compile generic classes for all your DTOs. - /// Just needs to be called once in a static constructor. - /// - [MonoTouch.Foundation.Preserve] - public static void InitForAot() { - } - - [MonoTouch.Foundation.Preserve] - public static void RegisterForAot() - { - RegisterTypeForAot(); - - RegisterElement(); - - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - - //RegisterElement(); - - RegisterTypeForAot(); // used by DateTime - - // register built in structs - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - } - - [MonoTouch.Foundation.Preserve] - public static void RegisterTypeForAot() - { - AotConfig.RegisterSerializers(); - } - - [MonoTouch.Foundation.Preserve] - static void RegisterQueryStringWriter() - { - var i = 0; - if (QueryStringWriter.WriteFn() != null) i++; - } - - [MonoTouch.Foundation.Preserve] - internal static int RegisterElement() - { - var i = 0; - i += AotConfig.RegisterSerializers(); - AotConfig.RegisterElement(); - AotConfig.RegisterElement(); - return i; - } - - /// - /// Class contains Ahead-of-Time (AOT) explicit class declarations which is used only to workaround "-aot-only" exceptions occured on device only. - /// - [MonoTouch.Foundation.Preserve(AllMembers=true)] - internal class AotConfig - { - internal static JsReader jsonReader; - internal static JsWriter jsonWriter; - internal static JsReader jsvReader; - internal static JsWriter jsvWriter; - internal static JsonTypeSerializer jsonSerializer; - internal static JsvTypeSerializer jsvSerializer; - - static AotConfig() - { - jsonSerializer = new JsonTypeSerializer(); - jsvSerializer = new JsvTypeSerializer(); - jsonReader = new JsReader(); - jsonWriter = new JsWriter(); - jsvReader = new JsReader(); - jsvWriter = new JsWriter(); - } - - internal static int RegisterSerializers() - { - var i = 0; - i += Register(); - if (jsonSerializer.GetParseFn() != null) i++; - if (jsonSerializer.GetWriteFn() != null) i++; - if (jsonReader.GetParseFn() != null) i++; - if (jsonWriter.GetWriteFn() != null) i++; - - i += Register(); - if (jsvSerializer.GetParseFn() != null) i++; - if (jsvSerializer.GetWriteFn() != null) i++; - if (jsvReader.GetParseFn() != null) i++; - if (jsvWriter.GetWriteFn() != null) i++; - - - //RegisterCsvSerializer(); - RegisterQueryStringWriter(); - return i; - } - - internal static void RegisterCsvSerializer() - { - CsvSerializer.WriteFn(); - CsvSerializer.WriteObject(null, null); - CsvWriter.Write(null, default(IEnumerable)); - CsvWriter.WriteRow(null, default(T)); - } - - public static ParseStringDelegate GetParseFn(Type type) - { - var parseFn = JsonTypeSerializer.Instance.GetParseFn(type); - return parseFn; - } - - internal static int Register() where TSerializer : ITypeSerializer - { - var i = 0; - - if (JsonWriter.WriteFn() != null) i++; - if (JsonWriter.Instance.GetWriteFn() != null) i++; - if (JsonReader.Instance.GetParseFn() != null) i++; - if (JsonReader.Parse(null) != null) i++; - if (JsonReader.GetParseFn() != null) i++; - //if (JsWriter.GetTypeSerializer().GetWriteFn() != null) i++; - if (new List() != null) i++; - if (new T[0] != null) i++; - - JsConfig.ExcludeTypeInfo = false; - - if (JsConfig.OnDeserializedFn != null) i++; - if (JsConfig.HasDeserializeFn) i++; - if (JsConfig.SerializeFn != null) i++; - if (JsConfig.DeSerializeFn != null) i++; - //JsConfig.SerializeFn = arg => ""; - //JsConfig.DeSerializeFn = arg => default(T); - if (TypeConfig.Properties != null) i++; - -/* - if (WriteType.Write != null) i++; - if (WriteType.Write != null) i++; - - if (DeserializeBuiltin.Parse != null) i++; - if (DeserializeArray.Parse != null) i++; - DeserializeType.ExtractType(null); - DeserializeArrayWithElements.ParseGenericArray(null, null); - DeserializeCollection.ParseCollection(null, null, null); - DeserializeListWithElements.ParseGenericList(null, null, null); - - SpecializedQueueElements.ConvertToQueue(null); - SpecializedQueueElements.ConvertToStack(null); -*/ - - WriteListsOfElements.WriteList(null, null); - WriteListsOfElements.WriteIList(null, null); - WriteListsOfElements.WriteEnumerable(null, null); - WriteListsOfElements.WriteListValueType(null, null); - WriteListsOfElements.WriteIListValueType(null, null); - WriteListsOfElements.WriteGenericArrayValueType(null, null); - WriteListsOfElements.WriteArray(null, null); - - TranslateListWithElements.LateBoundTranslateToGenericICollection(null, null); - TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, null); - - QueryStringWriter.WriteObject(null, null); - return i; - } - - internal static void RegisterElement() where TSerializer : ITypeSerializer - { - DeserializeDictionary.ParseDictionary(null, null, null, null); - DeserializeDictionary.ParseDictionary(null, null, null, null); - - ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); - ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); - - // Include List deserialisations from the Register<> method above. This solves issue where List properties on responses deserialise to null. - // No idea why this is happening because there is no visible exception raised. Suspect MonoTouch is swallowing an AOT exception somewhere. - DeserializeArrayWithElements.ParseGenericArray(null, null); - DeserializeListWithElements.ParseGenericList(null, null, null); - - // Cannot use the line below for some unknown reason - when trying to compile to run on device, mtouch bombs during native code compile. - // Something about this line or its inner workings is offensive to mtouch. Luckily this was not needed for my List issue. - // DeserializeCollection.ParseCollection(null, null, null); - - TranslateListWithElements.LateBoundTranslateToGenericICollection(null, typeof(List)); - TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, typeof(List)); - } - } - -#endif + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludePublicFields : Config.Instance.IncludePublicFields; + set => Config.AssertNotInit().IncludePublicFields = value; + } + + /// + /// Sets the maximum depth to avoid circular dependencies + /// + public static int MaxDepth + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.MaxDepth : Config.Instance.MaxDepth; + set => Config.AssertNotInit().MaxDepth = value; + } /// /// Set this to enable your own type construction provider. /// This is helpful for integration with IoC containers where you need to call the container constructor. /// Return null if you don't know how to construct the type and the parameterless constructor will be used. /// - public static EmptyCtorFactoryDelegate ModelFactory { get; set; } - } + public static EmptyCtorFactoryDelegate ModelFactory + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.ModelFactory : Config.Instance.ModelFactory; + set => Config.AssertNotInit().ModelFactory = value; + } -#if MONOTOUCH - [MonoTouch.Foundation.Preserve(AllMembers=true)] - internal class Poco - { - public string Dummy { get; set; } + public static HashSet ExcludeTypes + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeTypes : Config.Instance.ExcludeTypes; + set => Config.AssertNotInit().ExcludeTypes = value; + } + + public static HashSet ExcludeTypeNames + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeTypeNames : Config.Instance.ExcludeTypeNames; + set => Config.AssertNotInit().ExcludeTypeNames = value; + } + + public static string[] IgnoreAttributesNamed + { + set => ReflectionExtensions.IgnoreAttributesNamed = value; + get => ReflectionExtensions.IgnoreAttributesNamed; + } + + public static HashSet AllowRuntimeTypeWithAttributesNamed { get; set; } + + public static HashSet AllowRuntimeTypeWithInterfacesNamed { get; set; } + + public static HashSet AllowRuntimeTypeInTypes { get; set; } + + public static HashSet AllowRuntimeTypeInTypesWithNamespaces { get; set; } + + public static Func AllowRuntimeType { get; set; } + + public static void Reset() + { + foreach (var rawSerializeType in HasSerializeFn.ToArray()) + { + Reset(rawSerializeType); + } + foreach (var rawSerializeType in HasIncludeDefaultValue.ToArray()) + { + Reset(rawSerializeType); + } + foreach (var uniqueType in __uniqueTypes.ToArray()) + { + Reset(uniqueType); + } + + Env.StrictMode = false; + Config.Reset(); + AutoMappingUtils.Reset(); + HasSerializeFn = new HashSet(); + HasIncludeDefaultValue = new HashSet(); + TreatValueAsRefTypes = new HashSet { typeof(KeyValuePair<,>) }; + __uniqueTypes = new HashSet(); + + //Called when writing each string, too expensive to maintain as scoped config + + AllowRuntimeType = null; + AllowRuntimeTypeWithAttributesNamed = new HashSet + { + nameof(DataContractAttribute), + nameof(RuntimeSerializableAttribute), + }; + AllowRuntimeTypeWithInterfacesNamed = new HashSet + { + "IConvertible", + "ISerializable", + "IRuntimeSerializable", + "IMeta", + "IReturn`1", + "IReturnVoid", + }; + AllowRuntimeTypeInTypesWithNamespaces = new HashSet + { + "ServiceStack.Messaging", + }; + AllowRuntimeTypeInTypes = new HashSet + { + "ServiceStack.RequestLogEntry" + }; + PlatformExtensions.ClearRuntimeAttributes(); + ReflectionExtensions.Reset(); + JsState.Reset(); + } + + static void Reset(Type cachesForType) + { + typeof(JsConfig<>).MakeGenericType(new[] { cachesForType }).InvokeReset(); + typeof(TypeConfig<>).MakeGenericType(new[] { cachesForType }).InvokeReset(); + } + + internal static void InvokeReset(this Type genericType) + { + var methodInfo = genericType.GetStaticMethod("Reset"); + methodInfo.Invoke(null, null); + } + + internal static HashSet __uniqueTypes = new HashSet(); + internal static int __uniqueTypesCount = 0; + + internal static void AddUniqueType(Type type) + { + if (__uniqueTypes.Contains(type)) + return; + + HashSet newTypes, snapshot; + do + { + snapshot = __uniqueTypes; + newTypes = new HashSet(__uniqueTypes) { type }; + __uniqueTypesCount = newTypes.Count; + + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref __uniqueTypes, newTypes, snapshot), snapshot)); + } } -#endif public class JsConfig { + static JsConfig() + { + // Run the type's static constructor (which may set OnDeserialized, etc.) before we cache any information about it + RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); + } + + internal static Config GetConfig() + { + var config = new Config().Populate(JsConfig.GetConfig()); + if (TextCase != TextCase.Default) + config.TextCase = TextCase; + return config; + } + /// /// Always emit type info for this type. Takes precedence over ExcludeTypeInfo /// - public static bool IncludeTypeInfo = false; + public static bool? IncludeTypeInfo = null; /// /// Never emit type info for this type /// - public static bool ExcludeTypeInfo = false; + public static bool? ExcludeTypeInfo = null; /// - /// if the is configured - /// to take advantage of specification, - /// to support user-friendly serialized formats, ie emitting camelCasing for JSON - /// and parsing member names and enum values in a case-insensitive manner. + /// Text case to use for property names (Default = PascalCase) /// - public static bool EmitCamelCaseNames = false; + public static TextCase TextCase { get; set; } + + /// + /// Emitting camelCase for property names + /// + [Obsolete("Use TextCase = TextCase.CamelCase")] + public static bool? EmitCamelCaseNames + { + get => TextCase == TextCase.CamelCase; + set => TextCase = value == true ? TextCase.CamelCase : TextCase; + } + + /// + /// Emitting lowercase_underscore_casing for property names + /// + [Obsolete("Use TextCase = TextCase.SnakeCase")] + public static bool? EmitLowercaseUnderscoreNames + { + get => TextCase == TextCase.SnakeCase; + set => TextCase = value == true ? TextCase.SnakeCase : TextCase; + } + + public static bool IncludeDefaultValue + { + get => JsConfig.HasIncludeDefaultValue.Contains(typeof(T)); + set + { + if (value) + JsConfig.HasIncludeDefaultValue.Add(typeof(T)); + else + JsConfig.HasIncludeDefaultValue.Remove(typeof(T)); + + ClearFnCaches(); + } + } /// /// Define custom serialization fn for BCL Structs @@ -699,7 +830,7 @@ public class JsConfig private static Func serializeFn; public static Func SerializeFn { - get { return serializeFn; } + get => serializeFn; set { serializeFn = value; @@ -707,15 +838,17 @@ public static Func SerializeFn JsConfig.HasSerializeFn.Add(typeof(T)); else JsConfig.HasSerializeFn.Remove(typeof(T)); + + ClearFnCaches(); } } /// /// Opt-in flag to set some Value Types to be treated as a Ref Type /// - public bool TreatValueAsRefTypes + public static bool TreatValueAsRefType { - get { return JsConfig.TreatValueAsRefTypes.Contains(typeof(T)); } + get => JsConfig.TreatValueAsRefTypes.Contains(typeof(T)); set { if (value) @@ -728,10 +861,7 @@ public bool TreatValueAsRefTypes /// /// Whether there is a fn (raw or otherwise) /// - public static bool HasSerializeFn - { - get { return serializeFn != null || rawSerializeFn != null; } - } + public static bool HasSerializeFn => !JsState.InSerializer() && (serializeFn != null || rawSerializeFn != null); /// /// Define custom raw serialization fn @@ -739,7 +869,7 @@ public static bool HasSerializeFn private static Func rawSerializeFn; public static Func RawSerializeFn { - get { return rawSerializeFn; } + get => rawSerializeFn; set { rawSerializeFn = value; @@ -747,6 +877,8 @@ public static Func RawSerializeFn JsConfig.HasSerializeFn.Add(typeof(T)); else JsConfig.HasSerializeFn.Remove(typeof(T)); + + ClearFnCaches(); } } @@ -756,30 +888,56 @@ public static Func RawSerializeFn private static Func onSerializingFn; public static Func OnSerializingFn { - get { return onSerializingFn; } - set { onSerializingFn = value; } + get => onSerializingFn; + set { onSerializingFn = value; RefreshWrite(); } + } + + /// + /// Define custom after serialization hook + /// + private static Action onSerializedFn; + public static Action OnSerializedFn + { + get => onSerializedFn; + set { onSerializedFn = value; RefreshWrite(); } } /// /// Define custom deserialization fn for BCL Structs /// - public static Func DeSerializeFn; + private static Func deSerializeFn; + public static Func DeSerializeFn + { + get => deSerializeFn; + set { deSerializeFn = value; RefreshRead(); } + } /// /// Define custom raw deserialization fn for objects /// - public static Func RawDeserializeFn; - - public static bool HasDeserializeFn + private static Func rawDeserializeFn; + public static Func RawDeserializeFn { - get { return DeSerializeFn != null || RawDeserializeFn != null; } + get => rawDeserializeFn; + set { rawDeserializeFn = value; RefreshRead(); } } + public static bool HasDeserializeFn => !JsState.InDeserializer() && (DeSerializeFn != null || RawDeserializeFn != null); + private static Func onDeserializedFn; public static Func OnDeserializedFn { - get { return onDeserializedFn; } - set { onDeserializedFn = value; } + get => onDeserializedFn; + set { onDeserializedFn = value; RefreshRead(); } + } + + public static bool HasDeserializingFn => OnDeserializingFn != null; + + private static Func onDeserializingFn; + public static Func OnDeserializingFn + { + get => onDeserializingFn; + set { onDeserializingFn = value; RefreshRead(); } } /// @@ -789,14 +947,35 @@ public static Func OnDeserializedFn public static void WriteFn(TextWriter writer, object obj) { - if (RawSerializeFn != null) + if (RawSerializeFn != null && !JsState.InSerializer()) { - writer.Write(RawSerializeFn((T)obj)); + JsState.RegisterSerializer(); + try + { + writer.Write(RawSerializeFn((T)obj)); + } + finally + { + JsState.UnRegisterSerializer(); + } + } + else if (SerializeFn != null && !JsState.InSerializer()) + { + JsState.RegisterSerializer(); + try + { + var serializer = JsWriter.GetTypeSerializer(); + serializer.WriteString(writer, SerializeFn((T)obj)); + } + finally + { + JsState.UnRegisterSerializer(); + } } else { - var serializer = JsWriter.GetTypeSerializer(); - serializer.WriteString(writer, SerializeFn((T)obj)); + var writerFn = JsonWriter.Instance.GetWriteFn(); + writerFn(writer, obj); } } @@ -807,37 +986,90 @@ public static object ParseFn(string str) internal static object ParseFn(ITypeSerializer serializer, string str) { - if (RawDeserializeFn != null) + if (RawDeserializeFn != null && !JsState.InDeserializer()) { - return RawDeserializeFn(str); + JsState.RegisterDeserializer(); + try + { + return RawDeserializeFn(str); + } + finally + { + JsState.UnRegisterDeserializer(); + } + } + else if (DeSerializeFn != null && !JsState.InDeserializer()) + { + JsState.RegisterDeserializer(); + try + { + return DeSerializeFn(serializer.UnescapeString(str)); + } + finally + { + JsState.UnRegisterDeserializer(); + } } else { - return DeSerializeFn(serializer.UnescapeString(str)); + var parseFn = JsonReader.Instance.GetParseFn(); + return parseFn(str); } } + + internal static void ClearFnCaches() + { + JsonWriter.Reset(); + JsvWriter.Reset(); + } + + public static void Reset() + { + RawSerializeFn = null; + DeSerializeFn = null; + ExcludePropertyNames = null; + TextCase = TextCase.Default; + IncludeTypeInfo = ExcludeTypeInfo = null; + } + + public static void RefreshRead() + { + JsonReader.Refresh(); + JsvReader.Refresh(); + } + + public static void RefreshWrite() + { + JsonWriter.Refresh(); + JsvWriter.Refresh(); + } } - public enum JsonPropertyConvention + public enum PropertyConvention { /// /// The property names on target types must match property names in the JSON source /// - ExactMatch, + Strict, /// /// The property names on target types may not match the property names in the JSON source /// Lenient } - public enum JsonDateHandler + public enum DateHandler { TimestampOffset, DCJSCompatible, - ISO8601 + ISO8601, + ISO8601DateOnly, + ISO8601DateTime, + RFC1123, + UnixTime, + UnixTimeMs, } - public enum JsonTimeSpanHandler + public enum TimeSpanHandler { /// /// Uses the xsd format like PT15H10M20S @@ -848,5 +1080,26 @@ public enum JsonTimeSpanHandler /// StandardFormat } + + public enum TextCase + { + /// + /// If unspecified uses PascalCase + /// + Default, + /// + /// PascalCase + /// + PascalCase, + /// + /// camelCase + /// + CamelCase, + /// + /// snake_case + /// + SnakeCase, + } + } diff --git a/src/ServiceStack.Text/JsConfigScope.cs b/src/ServiceStack.Text/JsConfigScope.cs index 498b52028..81603de19 100644 --- a/src/ServiceStack.Text/JsConfigScope.cs +++ b/src/ServiceStack.Text/JsConfigScope.cs @@ -1,76 +1,274 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; -using System.Diagnostics; +using System.Collections.Generic; +using System.Reflection; +using ServiceStack.Text.Json; +using ServiceStack.Text.Jsv; +using ServiceStack.Text.Common; namespace ServiceStack.Text { - public sealed class JsConfigScope : IDisposable + public sealed class JsConfigScope : Config, IDisposable { bool disposed; - JsConfigScope parent; + readonly JsConfigScope parent; - [ThreadStatic] - private static JsConfigScope head; +#if NETCORE + private static AsyncLocal head = new AsyncLocal(); +#else + [ThreadStatic] private static JsConfigScope head; +#endif internal JsConfigScope() { -#if !SILVERLIGHT - Thread.BeginThreadAffinity(); -#endif + PclExport.Instance.BeginThreadAffinity(); + +#if NETCORE + parent = head.Value; + head.Value = this; +#else parent = head; head = this; +#endif } - internal static JsConfigScope Current + internal static JsConfigScope Current => +#if NETCORE + head.Value; +#else + head; +#endif + + public void Dispose() { - get + if (!disposed) { - return head; + disposed = true; +#if NETCORE + head.Value = parent; +#else + head = parent; +#endif + + PclExport.Instance.EndThreadAffinity(); } } + } + + public class Config + { + private static Config instance; + internal static Config Instance => instance ??= new Config(Defaults); + internal static bool HasInit = false; + + public static Config AssertNotInit() => HasInit + ? throw new NotSupportedException("JsConfig can't be mutated after JsConfig.Init(). Use BeginScope() or CreateScope() to use custom config after Init().") + : Instance; - public static void DisposeCurrent() + private static string InitStackTrace = null; + + public static void Init() => Init(null); + public static void Init(Config config) { - if (head != null) - { - head.Dispose(); - } + if (HasInit && Env.StrictMode) + throw new NotSupportedException($"JsConfig has already been initialized at: {InitStackTrace}"); + + if (config != null) + instance = config; + + HasInit = true; + InitStackTrace = Environment.StackTrace; } - public void Dispose() + /// + /// Bypass Init checks. Only call on Startup. + /// + /// + public static void UnsafeInit(Config config) { - if (!disposed) - { - disposed = true; + if (config != null) + instance = config; + } - Debug.Assert(this == head, "Disposed out of order."); + internal static void Reset() + { + HasInit = false; + Instance.Populate(Defaults); + } - head = parent; -#if !SILVERLIGHT - Thread.EndThreadAffinity(); -#endif - } + public Config() + { + Populate(Instance); } - public bool? ConvertObjectTypesIntoStringDictionary { get; set; } - public bool? TryToParsePrimitiveTypeValues { get; set; } - public bool? IncludeNullValues { get; set; } - public bool? TreatEnumAsInteger { get; set; } - public bool? ExcludeTypeInfo { get; set; } - public bool? IncludeTypeInfo { get; set; } - public string TypeAttr { get; set; } - internal string JsonTypeAttrInObject { get; set; } - internal string JsvTypeAttrInObject { get; set; } + private Config(Config config) + { + if (config != null) //Defaults=null, instance=Defaults + Populate(config); + } + + public bool ConvertObjectTypesIntoStringDictionary { get; set; } + public bool TryToParsePrimitiveTypeValues { get; set; } + public bool TryToParseNumericType { get; set; } + public bool TryParseIntoBestFit { get; set; } + public ParseAsType ParsePrimitiveFloatingPointTypes { get; set; } + public ParseAsType ParsePrimitiveIntegerTypes { get; set; } + public bool ExcludeDefaultValues { get; set; } + public bool IncludeNullValues { get; set; } + public bool IncludeNullValuesInDictionaries { get; set; } + public bool IncludeDefaultEnums { get; set; } + public bool TreatEnumAsInteger { get; set; } + public bool ExcludeTypeInfo { get; set; } + public bool IncludeTypeInfo { get; set; } + public bool Indent { get; set; } + + private string typeAttr; + public string TypeAttr + { + get => typeAttr; + set + { + typeAttrSpan = null; + jsonTypeAttrInObject = null; + jsvTypeAttrInObject = null; + typeAttr = value; + } + } + ReadOnlyMemory? typeAttrSpan = null; + public ReadOnlyMemory TypeAttrMemory => typeAttrSpan ??= TypeAttr.AsMemory(); + public string DateTimeFormat { get; set; } + private string jsonTypeAttrInObject; + internal string JsonTypeAttrInObject => jsonTypeAttrInObject ??= JsonTypeSerializer.GetTypeAttrInObject(TypeAttr); + private string jsvTypeAttrInObject; + internal string JsvTypeAttrInObject => jsvTypeAttrInObject ??= JsvTypeSerializer.GetTypeAttrInObject(TypeAttr); + public Func TypeWriter { get; set; } public Func TypeFinder { get; set; } - public JsonDateHandler? DateHandler { get; set; } - public bool? EmitCamelCaseNames { get; set; } - public bool? EmitLowercaseUnderscoreNames { get; set; } - public bool? ThrowOnDeserializationError { get; set; } - public bool? AlwaysUseUtc { get; set; } - public bool? PreferInterfaces { get; set; } + public Func ParsePrimitiveFn { get; set; } + public DateHandler DateHandler { get; set; } + public TimeSpanHandler TimeSpanHandler { get; set; } + public PropertyConvention PropertyConvention { get; set; } + + public TextCase TextCase { get; set; } + + [Obsolete("Use TextCase = TextCase.CamelCase")] + public bool EmitCamelCaseNames + { + get => TextCase == TextCase.CamelCase; + set => TextCase = value ? TextCase.CamelCase : TextCase; + } + + [Obsolete("Use TextCase = TextCase.SnakeCase")] + public bool EmitLowercaseUnderscoreNames + { + get => TextCase == TextCase.SnakeCase; + set => TextCase = value ? TextCase.SnakeCase : TextCase.Default; + } + + public bool ThrowOnError { get; set; } + public bool SkipDateTimeConversion { get; set; } + public bool AlwaysUseUtc { get; set; } + public bool AssumeUtc { get; set; } + public bool AppendUtcOffset { get; set; } + public bool PreferInterfaces { get; set; } + public bool IncludePublicFields { get; set; } + public int MaxDepth { get; set; } + public DeserializationErrorDelegate OnDeserializationError { get; set; } + public EmptyCtorFactoryDelegate ModelFactory { get; set; } + public string[] ExcludePropertyReferences { get; set; } + public HashSet ExcludeTypes { get; set; } + public HashSet ExcludeTypeNames { get; set; } + public bool EscapeUnicode { get; set; } + public bool EscapeHtmlChars { get; set; } + + public static Config Defaults => new Config(null) { + ConvertObjectTypesIntoStringDictionary = false, + TryToParsePrimitiveTypeValues = false, + TryToParseNumericType = false, + TryParseIntoBestFit = false, + ParsePrimitiveFloatingPointTypes = ParseAsType.Decimal, + ParsePrimitiveIntegerTypes = ParseAsType.Byte | ParseAsType.SByte | ParseAsType.Int16 | ParseAsType.UInt16 | + ParseAsType.Int32 | ParseAsType.UInt32 | ParseAsType.Int64 | ParseAsType.UInt64, + ExcludeDefaultValues = false, + ExcludePropertyReferences = null, + IncludeNullValues = false, + IncludeNullValuesInDictionaries = false, + IncludeDefaultEnums = true, + TreatEnumAsInteger = false, + ExcludeTypeInfo = false, + IncludeTypeInfo = false, + Indent = false, + TypeAttr = JsWriter.TypeAttr, + DateTimeFormat = null, + TypeWriter = AssemblyUtils.WriteType, + TypeFinder = AssemblyUtils.FindType, + ParsePrimitiveFn = null, + DateHandler = Text.DateHandler.TimestampOffset, + TimeSpanHandler = Text.TimeSpanHandler.DurationFormat, + TextCase = TextCase.Default, + PropertyConvention = Text.PropertyConvention.Strict, + ThrowOnError = Env.StrictMode, + SkipDateTimeConversion = false, + AlwaysUseUtc = false, + AssumeUtc = false, + AppendUtcOffset = false, + EscapeUnicode = false, + EscapeHtmlChars = false, + PreferInterfaces = false, + IncludePublicFields = false, + MaxDepth = 50, + OnDeserializationError = null, + ModelFactory = ReflectionExtensions.GetConstructorMethodToCache, + ExcludeTypes = new HashSet { + typeof(System.IO.Stream), + typeof(System.Reflection.MethodBase), + }, + ExcludeTypeNames = new HashSet {} + }; + + public Config Populate(Config config) + { + ConvertObjectTypesIntoStringDictionary = config.ConvertObjectTypesIntoStringDictionary; + TryToParsePrimitiveTypeValues = config.TryToParsePrimitiveTypeValues; + TryToParseNumericType = config.TryToParseNumericType; + TryParseIntoBestFit = config.TryParseIntoBestFit; + ParsePrimitiveFloatingPointTypes = config.ParsePrimitiveFloatingPointTypes; + ParsePrimitiveIntegerTypes = config.ParsePrimitiveIntegerTypes; + ExcludeDefaultValues = config.ExcludeDefaultValues; + ExcludePropertyReferences = config.ExcludePropertyReferences; + IncludeNullValues = config.IncludeNullValues; + IncludeNullValuesInDictionaries = config.IncludeNullValuesInDictionaries; + IncludeDefaultEnums = config.IncludeDefaultEnums; + TreatEnumAsInteger = config.TreatEnumAsInteger; + ExcludeTypeInfo = config.ExcludeTypeInfo; + IncludeTypeInfo = config.IncludeTypeInfo; + Indent = config.Indent; + TypeAttr = config.TypeAttr; + DateTimeFormat = config.DateTimeFormat; + TypeWriter = config.TypeWriter; + TypeFinder = config.TypeFinder; + ParsePrimitiveFn = config.ParsePrimitiveFn; + DateHandler = config.DateHandler; + TimeSpanHandler = config.TimeSpanHandler; + TextCase = config.TextCase; + PropertyConvention = config.PropertyConvention; + ThrowOnError = config.ThrowOnError; + SkipDateTimeConversion = config.SkipDateTimeConversion; + AlwaysUseUtc = config.AlwaysUseUtc; + AssumeUtc = config.AssumeUtc; + AppendUtcOffset = config.AppendUtcOffset; + EscapeUnicode = config.EscapeUnicode; + EscapeHtmlChars = config.EscapeHtmlChars; + PreferInterfaces = config.PreferInterfaces; + IncludePublicFields = config.IncludePublicFields; + MaxDepth = config.MaxDepth; + OnDeserializationError = config.OnDeserializationError; + ModelFactory = config.ModelFactory; + ExcludeTypes = config.ExcludeTypes; + ExcludeTypeNames = config.ExcludeTypeNames; + return this; + } } + } + diff --git a/src/ServiceStack.Text/Json/JsonReader.Generic.cs b/src/ServiceStack.Text/Json/JsonReader.Generic.cs index 848d5799e..11faa59d3 100644 --- a/src/ServiceStack.Text/Json/JsonReader.Generic.cs +++ b/src/ServiceStack.Text/Json/JsonReader.Generic.cs @@ -1,87 +1,116 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; using System.Collections.Generic; -using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; using ServiceStack.Text.Common; namespace ServiceStack.Text.Json { - internal static class JsonReader - { - public static readonly JsReader Instance = new JsReader(); + public static class JsonReader + { + public static readonly JsReader Instance = new JsReader(); - private static Dictionary ParseFnCache = new Dictionary(); - - public static ParseStringDelegate GetParseFn(Type type) - { - ParseFactoryDelegate parseFactoryFn; - ParseFnCache.TryGetValue(type, out parseFactoryFn); + private static Dictionary ParseFnCache = new Dictionary(); - if (parseFactoryFn != null) return parseFactoryFn(); + internal static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSpanFn(type)(v.AsSpan()); + + internal static ParseStringSpanDelegate GetParseSpanFn(Type type) => v => GetParseStringSpanFn(type)(v); + + internal static ParseStringSpanDelegate GetParseStringSpanFn(Type type) + { + ParseFnCache.TryGetValue(type, out var parseFactoryFn); + + if (parseFactoryFn != null) + return parseFactoryFn(); var genericType = typeof(JsonReader<>).MakeGenericType(type); - var mi = genericType.GetMethod("GetParseFn", BindingFlags.Public | BindingFlags.Static); - parseFactoryFn = (ParseFactoryDelegate)Delegate.CreateDelegate(typeof(ParseFactoryDelegate), mi); + var mi = genericType.GetStaticMethod(nameof(GetParseStringSpanFn)); + parseFactoryFn = (ParseFactoryDelegate)mi.MakeDelegate(typeof(ParseFactoryDelegate)); Dictionary snapshot, newCache; do { snapshot = ParseFnCache; - newCache = new Dictionary(ParseFnCache); - newCache[type] = parseFactoryFn; + newCache = new Dictionary(ParseFnCache) + { + [type] = parseFactoryFn + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseFnCache, newCache, snapshot), snapshot)); - + return parseFactoryFn(); - } - } - - public static class JsonReader - { - private static readonly ParseStringDelegate ReadFn; - - static JsonReader() - { - ReadFn = JsonReader.Instance.GetParseFn(); - } - - public static ParseStringDelegate GetParseFn() - { - return ReadFn ?? Parse; - } - - public static object Parse(string value) - { - if (ReadFn == null) - { + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + Text.Json.JsonReader.Instance.GetParseFn(); + Text.Json.JsonReader.Parse(TypeConstants.NullStringSpan); + Text.Json.JsonReader.GetParseFn(); + Text.Json.JsonReader.GetParseStringSpanFn(); + } + } + + internal static class JsonReader + { + private static ParseStringSpanDelegate ReadFn; + + static JsonReader() + { + Refresh(); + } + + public static void Refresh() + { + JsConfig.InitStatics(); + + if (JsonReader.Instance == null) + return; + + ReadFn = JsonReader.Instance.GetParseStringSpanFn(); + JsConfig.AddUniqueType(typeof(T)); + } + + public static ParseStringDelegate GetParseFn() => ReadFn != null + ? (ParseStringDelegate)(v => ReadFn(v.AsSpan())) + : Parse; + + public static ParseStringSpanDelegate GetParseStringSpanFn() => ReadFn ?? Parse; + + public static object Parse(string value) => value != null + ? Parse(value.AsSpan()) + : null; + + public static object Parse(ReadOnlySpan value) + { + TypeConfig.Init(); + + value = value.WithoutBom(); + + if (ReadFn == null) + { if (typeof(T).IsAbstract || typeof(T).IsInterface) - { - if (string.IsNullOrEmpty(value)) return null; - var concreteType = DeserializeType.ExtractType(value); - if (concreteType != null) - { - return JsonReader.GetParseFn(concreteType)(value); - } - throw new NotSupportedException("Can not deserialize interface type: " - + typeof(T).Name); - } - } - return value == null - ? null - : ReadFn(value); - } - } + { + if (value.IsNullOrEmpty()) return null; + var concreteType = DeserializeType.ExtractType(value); + if (concreteType != null) + { + return JsonReader.GetParseStringSpanFn(concreteType)(value); + } + throw new NotSupportedException("Can not deserialize interface type: " + + typeof(T).Name); + } + + Refresh(); + } + + return !value.IsEmpty + ? ReadFn(value) + : null; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Json/JsonTypeSerializer.cs b/src/ServiceStack.Text/Json/JsonTypeSerializer.cs index 9a5958a68..4d38b238d 100644 --- a/src/ServiceStack.Text/Json/JsonTypeSerializer.cs +++ b/src/ServiceStack.Text/Json/JsonTypeSerializer.cs @@ -1,67 +1,35 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; using System.Globalization; using System.IO; -using System.Text; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using ServiceStack.Text.Common; namespace ServiceStack.Text.Json { - internal class JsonTypeSerializer + public struct JsonTypeSerializer : ITypeSerializer { public static ITypeSerializer Instance = new JsonTypeSerializer(); - public bool IncludeNullValues - { - get { return JsConfig.IncludeNullValues; } - } + public ObjectDeserializerDelegate ObjectDeserializer { get; set; } - public string TypeAttrInObject - { - get { return JsConfig.JsonTypeAttrInObject; } - } + public bool IncludeNullValues => JsConfig.IncludeNullValues; - internal static string GetTypeAttrInObject(string typeAttr) - { - return string.Format("{{\"{0}\":", typeAttr); - } + public bool IncludeNullValuesInDictionaries => JsConfig.IncludeNullValuesInDictionaries; - public static readonly bool[] WhiteSpaceFlags = new bool[' ' + 1]; + public string TypeAttrInObject => JsConfig.JsonTypeAttrInObject; - static JsonTypeSerializer() - { - WhiteSpaceFlags[' '] = true; - WhiteSpaceFlags['\t'] = true; - WhiteSpaceFlags['\r'] = true; - WhiteSpaceFlags['\n'] = true; - } + internal static string GetTypeAttrInObject(string typeAttr) => $"{{\"{typeAttr}\":"; - public WriteObjectDelegate GetWriteFn() - { - return JsonWriter.WriteFn(); - } + public WriteObjectDelegate GetWriteFn() => JsonWriter.WriteFn(); - public WriteObjectDelegate GetWriteFn(Type type) - { - return JsonWriter.GetWriteFn(type); - } + public WriteObjectDelegate GetWriteFn(Type type) => JsonWriter.GetWriteFn(type); - public TypeInfo GetTypeInfo(Type type) - { - return JsonWriter.GetTypeInfo(type); - } + public TypeInfo GetTypeInfo(Type type) => JsonWriter.GetTypeInfo(type); /// /// Shortcut escape when we're sure value doesn't contain any escaped chars @@ -105,7 +73,13 @@ public void WriteBuiltIn(TextWriter writer, object value) public void WriteObjectString(TextWriter writer, object value) { - JsonUtils.WriteString(writer, value != null ? value.ToString() : null); + JsonUtils.WriteString(writer, value?.ToString()); + } + + public void WriteFormattableObjectString(TextWriter writer, object value) + { + var formattable = value as IFormattable; + JsonUtils.WriteString(writer, formattable?.ToString(null, CultureInfo.InvariantCulture)); } public void WriteException(TextWriter writer, object value) @@ -115,7 +89,20 @@ public void WriteException(TextWriter writer, object value) public void WriteDateTime(TextWriter writer, object oDateTime) { - WriteRawString(writer, DateTimeSerializer.ToWcfJsonDate((DateTime)oDateTime)); + var dateTime = (DateTime)oDateTime; + switch (JsConfig.DateHandler) + { + case DateHandler.UnixTime: + writer.Write(dateTime.ToUnixTime()); + return; + case DateHandler.UnixTimeMs: + writer.Write(dateTime.ToUnixTimeMs()); + return; + } + + writer.Write(JsWriter.QuoteString); + DateTimeSerializer.WriteWcfJsonDate(writer, dateTime); + writer.Write(JsWriter.QuoteString); } public void WriteNullableDateTime(TextWriter writer, object dateTime) @@ -128,7 +115,9 @@ public void WriteNullableDateTime(TextWriter writer, object dateTime) public void WriteDateTimeOffset(TextWriter writer, object oDateTimeOffset) { - WriteRawString(writer, DateTimeSerializer.ToWcfJsonDateTimeOffset((DateTimeOffset)oDateTimeOffset)); + writer.Write(JsWriter.QuoteString); + DateTimeSerializer.WriteWcfJsonDateTimeOffset(writer, (DateTimeOffset)oDateTimeOffset); + writer.Write(JsWriter.QuoteString); } public void WriteNullableDateTimeOffset(TextWriter writer, object dateTimeOffset) @@ -141,7 +130,7 @@ public void WriteNullableDateTimeOffset(TextWriter writer, object dateTimeOffset public void WriteTimeSpan(TextWriter writer, object oTimeSpan) { - var stringValue = JsConfig.TimeSpanHandler == JsonTimeSpanHandler.StandardFormat + var stringValue = JsConfig.TimeSpanHandler == TimeSpanHandler.StandardFormat ? oTimeSpan.ToString() : DateTimeSerializer.ToXsdTimeSpanString((TimeSpan)oTimeSpan); WriteRawString(writer, stringValue); @@ -149,7 +138,6 @@ public void WriteTimeSpan(TextWriter writer, object oTimeSpan) public void WriteNullableTimeSpan(TextWriter writer, object oTimeSpan) { - if (oTimeSpan == null) return; WriteTimeSpan(writer, ((TimeSpan?)oTimeSpan).Value); } @@ -176,7 +164,7 @@ public void WriteChar(TextWriter writer, object charValue) if (charValue == null) writer.Write(JsonUtils.Null); else - WriteRawString(writer, ((char)charValue).ToString(CultureInfo.InvariantCulture)); + WriteString(writer, ((char)charValue).ToString()); } public void WriteByte(TextWriter writer, object byteValue) @@ -187,6 +175,14 @@ public void WriteByte(TextWriter writer, object byteValue) writer.Write((byte)byteValue); } + public void WriteSByte(TextWriter writer, object sbyteValue) + { + if (sbyteValue == null) + writer.Write(JsonUtils.Null); + else + writer.Write((sbyte)sbyteValue); + } + public void WriteInt16(TextWriter writer, object intValue) { if (intValue == null) @@ -255,7 +251,7 @@ public void WriteFloat(TextWriter writer, object floatValue) if (Equals(floatVal, float.MaxValue) || Equals(floatVal, float.MinValue)) writer.Write(floatVal.ToString("r", CultureInfo.InvariantCulture)); else - writer.Write(floatVal.ToString(CultureInfo.InvariantCulture)); + writer.Write(floatVal.ToString("r", CultureInfo.InvariantCulture)); } } @@ -283,188 +279,372 @@ public void WriteDecimal(TextWriter writer, object decimalValue) public void WriteEnum(TextWriter writer, object enumValue) { - if (enumValue == null) return; - if (JsConfig.TreatEnumAsInteger) - JsWriter.WriteEnumFlags(writer, enumValue); - else - WriteRawString(writer, enumValue.ToString()); + if (enumValue == null) + return; + var serializedValue = CachedTypeInfo.Get(enumValue.GetType()).EnumInfo.GetSerializedValue(enumValue); + if (serializedValue is string strEnum) + WriteRawString(writer, strEnum); + else + JsWriter.WriteEnumFlags(writer, enumValue); } - public void WriteEnumFlags(TextWriter writer, object enumFlagValue) + +#if NET6_0 + public void WriteDateOnly(TextWriter writer, object oDateOnly) { - JsWriter.WriteEnumFlags(writer, enumFlagValue); + var dateOnly = (DateOnly)oDateOnly; + switch (JsConfig.DateHandler) + { + case DateHandler.UnixTime: + writer.Write(dateOnly.ToUnixTime()); + break; + case DateHandler.UnixTimeMs: + writer.Write(dateOnly.ToUnixTimeMs()); + break; + default: + writer.Write(JsWriter.QuoteString); + writer.Write(dateOnly.ToString("O")); + writer.Write(JsWriter.QuoteString); + break; + } + } + + public void WriteNullableDateOnly(TextWriter writer, object oDateOnly) + { + if (oDateOnly == null) + writer.Write(JsonUtils.Null); + else + WriteDateOnly(writer, oDateOnly); + } + + public void WriteTimeOnly(TextWriter writer, object oTimeOnly) + { + var stringValue = JsConfig.TimeSpanHandler == TimeSpanHandler.StandardFormat + ? oTimeOnly.ToString() + : DateTimeSerializer.ToXsdTimeSpanString(((TimeOnly)oTimeOnly).ToTimeSpan()); + WriteRawString(writer, stringValue); } - public void WriteLinqBinary(TextWriter writer, object linqBinaryValue) + public void WriteNullableTimeOnly(TextWriter writer, object oTimeOnly) { -#if !MONOTOUCH && !SILVERLIGHT && !XBOX && !ANDROID - WriteRawString(writer, Convert.ToBase64String(((System.Data.Linq.Binary)linqBinaryValue).ToArray())); -#endif + if (oTimeOnly == null) return; + WriteTimeSpan(writer, ((TimeOnly?)oTimeOnly).Value.ToTimeSpan()); } +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ParseStringDelegate GetParseFn() { return JsonReader.Instance.GetParseFn(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ParseStringSpanDelegate GetParseStringSpanFn() + { + return JsonReader.Instance.GetParseStringSpanFn(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ParseStringDelegate GetParseFn(Type type) { return JsonReader.GetParseFn(type); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ParseStringSpanDelegate GetParseStringSpanFn(Type type) + { + return JsonReader.GetParseStringSpanFn(type); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ParseRawString(string value) { return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ParseString(ReadOnlySpan value) + { + return value.IsNullOrEmpty() ? null : ParseRawString(value.ToString()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ParseString(string value) { return string.IsNullOrEmpty(value) ? value : ParseRawString(value); } - internal static bool IsEmptyMap(string value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEmptyMap(ReadOnlySpan value, int i = 1) { - var i = 1; - for (; i < value.Length; i++) { var c = value[i]; if (c >= WhiteSpaceFlags.Length || !WhiteSpaceFlags[c]) break; } //Whitespace inline + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline if (value.Length == i) return true; return value[i++] == JsWriter.MapEndChar; } - internal static string ParseString(string json, ref int index) + internal static ReadOnlySpan ParseString(ReadOnlySpan json, ref int index) { var jsonLength = json.Length; + if (json[index] != JsonUtils.QuoteChar) - throw new Exception("Invalid unquoted string starting with: " + json.SafeSubstring(50)); + throw new Exception("Invalid unquoted string starting with: " + json.SafeSubstring(50).ToString()); - var startIndex = ++index; + var startIndex = ++index; do { - char c = json[index]; - if (c == JsonUtils.QuoteChar) break; - if (c != JsonUtils.EscapeChar) continue; - c = json[index++]; - if (c == 'u') + var c = json[index]; + + if (c == JsonUtils.QuoteChar) + break; + + if (c == JsonUtils.EscapeChar) { - index += 4; + index++; + if (json[index] == 'u') + index += 4; } + } while (index++ < jsonLength); + + if (index == jsonLength) + throw new Exception("Invalid unquoted string ending with: " + json.SafeSubstring(json.Length - 50, 50).ToString()); + index++; - return json.Substring(startIndex, Math.Min(index, jsonLength) - startIndex - 1); + var str = json.Slice(startIndex, Math.Min(index, jsonLength) - startIndex - 1); + if (str.Length == 0) + return TypeConstants.EmptyStringSpan; + + return str; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string UnescapeString(string value) { var i = 0; - return UnEscapeJsonString(value, ref i); + return UnescapeJsonString(value, ref i); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan UnescapeString(ReadOnlySpan value) + { + var i = 0; + return UnescapeJsonString(value, ref i); } - public string UnescapeSafeString(string value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object UnescapeStringAsObject(ReadOnlySpan value) { - if (string.IsNullOrEmpty(value)) return value; - return value[0] == JsonUtils.QuoteChar && value[value.Length - 1] == JsonUtils.QuoteChar - ? value.Substring(1, value.Length - 2) - : value; + var ignore = 0; + return UnescapeJsString(value, JsonUtils.QuoteChar, removeQuotes: true, ref ignore).Value(); + } - //if (value[0] != JsonUtils.QuoteChar) - // throw new Exception("Invalid unquoted string starting with: " + value.SafeSubstring(50)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string UnescapeSafeString(string value) => UnescapeSafeString(value.AsSpan()).ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan UnescapeSafeString(ReadOnlySpan value) + { + if (value.IsEmpty) + return value; - //return value.Substring(1, value.Length - 2); + if (value[0] == JsonUtils.QuoteChar && value[value.Length - 1] == JsonUtils.QuoteChar) + return value.Slice(1, value.Length - 2); + + return value; } - static readonly char[] IsSafeJsonChars = new[] { JsonUtils.QuoteChar, JsonUtils.EscapeChar }; + static readonly char[] IsSafeJsonChars = { JsonUtils.QuoteChar, JsonUtils.EscapeChar }; - internal static string ParseJsonString(string json, ref int index) + internal static ReadOnlySpan ParseJsonString(ReadOnlySpan json, ref int index) { - for (; index < json.Length; index++) { var ch = json[index]; if (ch >= WhiteSpaceFlags.Length || !WhiteSpaceFlags[ch]) break; } //Whitespace inline + for (; index < json.Length; index++) { var ch = json[index]; if (!JsonUtils.IsWhiteSpace(ch)) break; } //Whitespace inline - return UnEscapeJsonString(json, ref index); + return UnescapeJsonString(json, ref index); } - private static string UnEscapeJsonString(string json, ref int index) + private static string UnescapeJsonString(string json, ref int index) + { + return json != null + ? UnescapeJsonString(json.AsSpan(), ref index).ToString() + : null; + } + + private static ReadOnlySpan UnescapeJsonString(ReadOnlySpan json, ref int index) => + UnescapeJsString(json, JsonUtils.QuoteChar, removeQuotes:true, ref index); + + public static ReadOnlySpan UnescapeJsString(ReadOnlySpan json, char quoteChar) { - if (string.IsNullOrEmpty(json)) return json; + var ignore = 0; + return UnescapeJsString(json, quoteChar, removeQuotes:false, ref ignore); + } + + public static ReadOnlySpan UnescapeJsString(ReadOnlySpan json, char quoteChar, bool removeQuotes, ref int index) + { + if (json.IsNullOrEmpty()) return json; var jsonLength = json.Length; - var firstChar = json[index]; - if (firstChar == JsonUtils.QuoteChar) + var buffer = json; + + var firstChar = buffer[index]; + if (firstChar == quoteChar) { index++; //MicroOp: See if we can short-circuit evaluation (to avoid StringBuilder) - var strEndPos = json.IndexOfAny(IsSafeJsonChars, index); - if (strEndPos == -1) return json.Substring(index, jsonLength - index); - if (json[strEndPos] == JsonUtils.QuoteChar) + var jsonAtIndex = json.Slice(index); + var strEndPos = jsonAtIndex.IndexOfAny(IsSafeJsonChars); + if (strEndPos == -1) + return jsonAtIndex.Slice(0, jsonLength); + + if (jsonAtIndex[strEndPos] == quoteChar) + { + var potentialValue = jsonAtIndex.Slice(0, strEndPos); + index += strEndPos + 1; + return potentialValue.Length > 0 + ? potentialValue + : TypeConstants.EmptyStringSpan; + } + } + else + { + var i = index; + var end = jsonLength; + + while (i < end) { - var potentialValue = json.Substring(index, strEndPos - index); - index = strEndPos + 1; - return potentialValue; + var c = buffer[i]; + if (c == quoteChar || c == JsonUtils.EscapeChar) + break; + i++; } + if (i == end) + return buffer.Slice(index, jsonLength - index); } - var sb = new StringBuilder(jsonLength); + return Unescape(json, removeQuotes:removeQuotes, quoteChar:quoteChar); + } + + public static string Unescape(string input) => Unescape(input, true); + public static string Unescape(string input, bool removeQuotes) => Unescape(input.AsSpan(), removeQuotes).ToString(); - while (true) - { - if (index == jsonLength) break; + public static ReadOnlySpan Unescape(ReadOnlySpan input) => Unescape(input, true); - char c = json[index++]; - if (c == JsonUtils.QuoteChar) break; + public static ReadOnlySpan Unescape(ReadOnlySpan input, bool removeQuotes) => + Unescape(input, removeQuotes, JsonUtils.QuoteChar); + + public static ReadOnlySpan Unescape(ReadOnlySpan input, bool removeQuotes, char quoteChar) + { + var length = input.Length; + int start = 0; + int count = 0; + var output = StringBuilderThreadStatic.Allocate(); + for (; count < length;) + { + var c = input[count]; + if (removeQuotes) + { + if (c == quoteChar) + { + if (start != count) + { + output.Append(input.Slice(start, count - start)); + } + count++; + start = count; + continue; + } + } if (c == JsonUtils.EscapeChar) { - if (index == jsonLength) + if (start != count) { - break; + output.Append(input.Slice(start, count - start)); } - c = json[index++]; + start = count; + count++; + if (count >= length) continue; + + //we will always be parsing an escaped char here + c = input[count]; + switch (c) { - case '"': - sb.Append('"'); - break; - case '\\': - sb.Append('\\'); - break; - case '/': - sb.Append('/'); + case 'a': + output.Append('\a'); + count++; break; case 'b': - sb.Append('\b'); + output.Append('\b'); + count++; break; case 'f': - sb.Append('\f'); + output.Append('\f'); + count++; break; case 'n': - sb.Append('\n'); + output.Append('\n'); + count++; break; case 'r': - sb.Append('\r'); + output.Append('\r'); + count++; + break; + case 'v': + output.Append('\v'); + count++; break; case 't': - sb.Append('\t'); + output.Append('\t'); + count++; break; case 'u': - var remainingLength = jsonLength - index; - if (remainingLength >= 4) + if (count + 4 < length) { - var unicodeString = json.Substring(index, 4); - var unicodeIntVal = UInt32.Parse(unicodeString, NumberStyles.HexNumber); - sb.Append(ConvertFromUtf32((int) unicodeIntVal)); - index += 4; + var unicodeString = input.Slice(count + 1, 4); + var unicodeIntVal = MemoryProvider.Instance.ParseUInt32(unicodeString, NumberStyles.HexNumber); + output.Append(ConvertFromUtf32((int)unicodeIntVal)); + count += 5; } else { - break; + output.Append(c); } break; + case 'x': + if (count + 4 < length) + { + var unicodeString = input.Slice(count + 1, 4); + var unicodeIntVal = MemoryProvider.Instance.ParseUInt32(unicodeString, NumberStyles.HexNumber); + output.Append(ConvertFromUtf32((int)unicodeIntVal)); + count += 5; + } + else + if (count + 2 < length) + { + var unicodeString = input.Slice(count + 1, 2); + var unicodeIntVal = MemoryProvider.Instance.ParseUInt32(unicodeString, NumberStyles.HexNumber); + output.Append(ConvertFromUtf32((int)unicodeIntVal)); + count += 3; + } + else + { + output.Append(input.Slice(start, count - start)); + } + break; + default: + output.Append(c); + count++; + break; } + start = count; } else { - sb.Append(c); + count++; } } - - return sb.ToString(); + output.Append(input.Slice(start, length - start)); + return StringBuilderThreadStatic.ReturnAndFree(output).AsSpan(); } /// @@ -473,74 +653,134 @@ private static string UnEscapeJsonString(string json, ref int index) /// /// /// - private static string ConvertFromUtf32(int utf32) + public static string ConvertFromUtf32(int utf32) { if (utf32 < 0 || utf32 > 0x10FFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); + throw new ArgumentOutOfRangeException(nameof(utf32), "The argument must be from 0 to 0x10FFFF."); if (utf32 < 0x10000) return new string((char)utf32, 1); utf32 -= 0x10000; - return new string(new[] {(char) ((utf32 >> 10) + 0xD800), - (char) (utf32 % 0x0400 + 0xDC00)}); + return new string(new[] {(char) ((utf32 >> 10) + 0xD800), (char) (utf32 % 0x0400 + 0xDC00)}); + } + + public string EatTypeValue(string value, ref int i) + { + return EatValue(value, ref i); } - public string EatTypeValue(string value, ref int i) + public ReadOnlySpan EatTypeValue(ReadOnlySpan value, ref int i) { return EatValue(value, ref i); } - public bool EatMapStartChar(string value, ref int i) + public bool EatMapStartChar(string value, ref int i) => EatMapStartChar(value.AsSpan(), ref i); + + public bool EatMapStartChar(ReadOnlySpan value, ref int i) { - for (; i < value.Length; i++) { var c = value[i]; if (c >= WhiteSpaceFlags.Length || !WhiteSpaceFlags[c]) break; } //Whitespace inline + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline return value[i++] == JsWriter.MapStartChar; } - public string EatMapKey(string value, ref int i) + public string EatMapKey(string value, ref int i) => EatMapKey(value.AsSpan(), ref i).ToString(); + + public ReadOnlySpan EatMapKey(ReadOnlySpan value, ref int i) { - return ParseJsonString(value, ref i); + var valueLength = value.Length; + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline + + var tokenStartPos = i; + var valueChar = value[i]; + + switch (valueChar) + { + //If we are at the end, return. + case JsWriter.ItemSeperator: + case JsWriter.MapEndChar: + return default(ReadOnlySpan); + + //Is Within Quotes, i.e. "..." + case JsWriter.QuoteChar: + return ParseString(value, ref i); + } + + //Is Value + while (++i < valueLength) + { + valueChar = value[i]; + + if (valueChar == JsWriter.ItemSeperator + //If it doesn't have quotes it's either a keyword or number so also has a ws boundary + || (JsonUtils.IsWhiteSpace(valueChar)) + ) + { + break; + } + } + + return value.Slice(tokenStartPos, i - tokenStartPos); } - public bool EatMapKeySeperator(string value, ref int i) + public bool EatMapKeySeperator(string value, ref int i) => EatMapKeySeperator(value.AsSpan(), ref i); + + + public bool EatMapKeySeperator(ReadOnlySpan value, ref int i) { - for (; i < value.Length; i++) { var c = value[i]; if (c >= WhiteSpaceFlags.Length || !WhiteSpaceFlags[c]) break; } //Whitespace inline + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline if (value.Length == i) return false; return value[i++] == JsWriter.MapKeySeperator; } public bool EatItemSeperatorOrMapEndChar(string value, ref int i) { - for (; i < value.Length; i++) { var c = value[i]; if (c >= WhiteSpaceFlags.Length || !WhiteSpaceFlags[c]) break; } //Whitespace inline + return EatItemSeperatorOrMapEndChar(value.AsSpan(), ref i); + } - if (i == value.Length) return false; + public bool EatItemSeperatorOrMapEndChar(ReadOnlySpan value, ref int i) + { + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline - var success = value[i] == JsWriter.ItemSeperator - || value[i] == JsWriter.MapEndChar; + if (i == value.Length) return false; - i++; + var success = value[i] == JsWriter.ItemSeperator || value[i] == JsWriter.MapEndChar; if (success) { - for (; i < value.Length; i++) { var c = value[i]; if (c >= WhiteSpaceFlags.Length || !WhiteSpaceFlags[c]) break; } //Whitespace inline + i++; + + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline } + else if (Env.StrictMode) throw new Exception( + $"Expected '{JsWriter.ItemSeperator}' or '{JsWriter.MapEndChar}'"); return success; } + public void EatWhitespace(ReadOnlySpan value, ref int i) + { + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline + } + public void EatWhitespace(string value, ref int i) { - for (; i < value.Length; i++) { var c = value[i]; if (c >= WhiteSpaceFlags.Length || !WhiteSpaceFlags[c]) break; } //Whitespace inline + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline } public string EatValue(string value, ref int i) { + return EatValue(value.AsSpan(), ref i).ToString(); + } + + public ReadOnlySpan EatValue(ReadOnlySpan value, ref int i) + { + var buf = value; var valueLength = value.Length; - if (i == valueLength) return null; + if (i == valueLength) return default; - for (; i < value.Length; i++) { var c = value[i]; if (c >= WhiteSpaceFlags.Length || !WhiteSpaceFlags[c]) break; } //Whitespace inline - if (i == valueLength) return null; + while (i < valueLength && JsonUtils.IsWhiteSpace(buf[i])) i++; //Whitespace inline + if (i == valueLength) return default; var tokenStartPos = i; - var valueChar = value[i]; + var valueChar = buf[i]; var withinQuotes = false; var endsToEat = 1; @@ -549,7 +789,7 @@ public string EatValue(string value, ref int i) //If we are at the end, return. case JsWriter.ItemSeperator: case JsWriter.MapEndChar: - return null; + return default; //Is Within Quotes, i.e. "..." case JsWriter.QuoteChar: @@ -557,9 +797,9 @@ public string EatValue(string value, ref int i) //Is Type/Map, i.e. {...} case JsWriter.MapStartChar: - while (++i < valueLength && endsToEat > 0) + while (++i < valueLength) { - valueChar = value[i]; + valueChar = buf[i]; if (valueChar == JsonUtils.EscapeChar) { @@ -576,16 +816,19 @@ public string EatValue(string value, ref int i) if (valueChar == JsWriter.MapStartChar) endsToEat++; - if (valueChar == JsWriter.MapEndChar) - endsToEat--; + if (valueChar == JsWriter.MapEndChar && --endsToEat == 0) + { + i++; + break; + } } - return value.Substring(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); //Is List, i.e. [...] case JsWriter.ListStartChar: - while (++i < valueLength && endsToEat > 0) + while (++i < valueLength) { - valueChar = value[i]; + valueChar = buf[i]; if (valueChar == JsonUtils.EscapeChar) { @@ -602,29 +845,35 @@ public string EatValue(string value, ref int i) if (valueChar == JsWriter.ListStartChar) endsToEat++; - if (valueChar == JsWriter.ListEndChar) - endsToEat--; + if (valueChar == JsWriter.ListEndChar && --endsToEat == 0) + { + i++; + break; + } } - return value.Substring(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); } //Is Value while (++i < valueLength) { - valueChar = value[i]; + valueChar = buf[i]; if (valueChar == JsWriter.ItemSeperator || valueChar == JsWriter.MapEndChar //If it doesn't have quotes it's either a keyword or number so also has a ws boundary - || (valueChar < WhiteSpaceFlags.Length && WhiteSpaceFlags[valueChar]) + || JsonUtils.IsWhiteSpace(valueChar) ) { break; } } - var strValue = value.Substring(tokenStartPos, i - tokenStartPos); - return strValue == JsonUtils.Null ? null : strValue; + var strValue = value.Slice(tokenStartPos, i - tokenStartPos); + + return strValue.Equals(JsonUtils.Null.AsSpan(), StringComparison.Ordinal) + ? default + : strValue; } } diff --git a/src/ServiceStack.Text/Json/JsonUtils.cs b/src/ServiceStack.Text/Json/JsonUtils.cs index bc42265d2..91eb96a70 100644 --- a/src/ServiceStack.Text/Json/JsonUtils.cs +++ b/src/ServiceStack.Text/Json/JsonUtils.cs @@ -1,147 +1,240 @@ +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + using System; using System.IO; +using System.Runtime.CompilerServices; namespace ServiceStack.Text.Json { - public static class JsonUtils - { - public const char EscapeChar = '\\'; - public const char QuoteChar = '"'; - public const string Null = "null"; - public const string True = "true"; - public const string False = "false"; - - static readonly char[] EscapeChars = new[] - { - QuoteChar, '\n', '\r', '\t', '"', '\\', '\f', '\b', - }; - - private const int LengthFromLargestChar = '\\' + 1; - private static readonly bool[] EscapeCharFlags = new bool[LengthFromLargestChar]; - - static JsonUtils() - { - foreach (var escapeChar in EscapeChars) - { - EscapeCharFlags[escapeChar] = true; - } - } - - public static void WriteString(TextWriter writer, string value) - { - if (value == null) - { - writer.Write(JsonUtils.Null); - return; - } - if (!HasAnyEscapeChars(value)) - { - writer.Write(QuoteChar); - writer.Write(value); - writer.Write(QuoteChar); - return; - } - - var hexSeqBuffer = new char[4]; - writer.Write(QuoteChar); - - var len = value.Length; + public static class JsonUtils + { + public const long MaxInteger = 9007199254740992; + public const long MinInteger = -9007199254740992; + + public const char EscapeChar = '\\'; + + public const char QuoteChar = '"'; + public const string Null = "null"; + public const string True = "true"; + public const string False = "false"; + + public const char SpaceChar = ' '; + public const char TabChar = '\t'; + public const char CarriageReturnChar = '\r'; + public const char LineFeedChar = '\n'; + public const char FormFeedChar = '\f'; + public const char BackspaceChar = '\b'; + + /// + /// Micro-optimization keep pre-built char arrays saving a .ToCharArray() + function call (see .net implementation of .Write(string)) + /// + private static readonly char[] EscapedBackslash = { EscapeChar, EscapeChar }; + private static readonly char[] EscapedTab = { EscapeChar, 't' }; + private static readonly char[] EscapedCarriageReturn = { EscapeChar, 'r' }; + private static readonly char[] EscapedLineFeed = { EscapeChar, 'n' }; + private static readonly char[] EscapedFormFeed = { EscapeChar, 'f' }; + private static readonly char[] EscapedBackspace = { EscapeChar, 'b' }; + private static readonly char[] EscapedQuote = { EscapeChar, QuoteChar }; + + public static readonly char[] WhiteSpaceChars = { ' ', TabChar, CarriageReturnChar, LineFeedChar }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWhiteSpace(char c) + { + return c == ' ' || (c >= '\x0009' && c <= '\x000d') || c == '\x00a0' || c == '\x0085' || c == TypeConstants.NonWidthWhiteSpace; + } + + public static void WriteString(TextWriter writer, string value) + { + if (value == null) + { + writer.Write(Null); + return; + } + + var config = JsConfig.GetConfig(); + var escapeHtmlChars = config.EscapeHtmlChars; + var escapeUnicode = config.EscapeUnicode; + + if (!HasAnyEscapeChars(value, escapeHtmlChars)) + { + writer.Write(QuoteChar); + writer.Write(value); + writer.Write(QuoteChar); + return; + } + + var hexSeqBuffer = new char[4]; + writer.Write(QuoteChar); + + var len = value.Length; for (var i = 0; i < len; i++) { - switch (value[i]) + char c = value[i]; + + switch (c) { - case '\n': - writer.Write("\\n"); + case LineFeedChar: + writer.Write(EscapedLineFeed); continue; - case '\r': - writer.Write("\\r"); + case CarriageReturnChar: + writer.Write(EscapedCarriageReturn); continue; - case '\t': - writer.Write("\\t"); + case TabChar: + writer.Write(EscapedTab); continue; - case '"': - case '\\': - writer.Write('\\'); - writer.Write(value[i]); + case QuoteChar: + writer.Write(EscapedQuote); continue; - case '\f': - writer.Write("\\f"); + case EscapeChar: + writer.Write(EscapedBackslash); continue; - case '\b': - writer.Write("\\b"); + case FormFeedChar: + writer.Write(EscapedFormFeed); continue; + + case BackspaceChar: + writer.Write(EscapedBackspace); + continue; + } + + if (escapeHtmlChars) + { + switch (c) + { + case '<': + writer.Write("\\u003c"); + continue; + case '>': + writer.Write("\\u003e"); + continue; + case '&': + writer.Write("\\u0026"); + continue; + case '=': + writer.Write("\\u003d"); + continue; + case '\'': + writer.Write("\\u0027"); + continue; + } } - //Is printable char? - if (value[i] >= 32 && value[i] <= 126) + if (c.IsPrintable()) { - writer.Write(value[i]); + writer.Write(c); continue; } - // Default, turn into a \uXXXX sequence - IntToHex(value[i], hexSeqBuffer); - writer.Write("\\u"); - writer.Write(hexSeqBuffer); + // http://json.org/ spec requires any control char to be escaped + if (escapeUnicode || char.IsControl(c)) + { + // Default, turn into a \uXXXX sequence + IntToHex(c, hexSeqBuffer); + writer.Write("\\u"); + writer.Write(hexSeqBuffer); + } + else + { + writer.Write(c); + } + } + + writer.Write(QuoteChar); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsPrintable(this char c) + { + return c >= 32 && c <= 126; + } + + /// + /// Searches the string for one or more non-printable characters. + /// + /// The string to search. + /// + /// True if there are any characters that require escaping. False if the value can be written verbatim. + /// + /// Micro optimizations: since quote and backslash are the only printable characters requiring escaping, removed previous optimization + /// (using flags instead of value.IndexOfAny(EscapeChars)) in favor of two equality operations saving both memory and CPU time. + /// Also slightly reduced code size by re-arranging conditions. + /// TODO: Possible Linq-only solution requires profiling: return value.Any(c => !c.IsPrintable() || c == QuoteChar || c == EscapeChar); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool HasAnyEscapeChars(string value, bool escapeHtmlChars) + { + var len = value.Length; + for (var i = 0; i < len; i++) + { + var c = value[i]; + + // c is not printable + // OR c is a printable that requires escaping (quote and backslash). + if (!c.IsPrintable() || c == QuoteChar || c == EscapeChar) + return true; + + if (escapeHtmlChars && (c == '<' || c == '>' || c == '&' || c == '=' || c == '\\')) + return true; } + return false; + } + + // Micro optimized + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IntToHex(int intValue, char[] hex) + { + // TODO: test if unrolling loop is faster + for (var i = 3; i >= 0; i--) + { + var num = intValue & 0xF; // intValue % 16 + + // 0x30 + num == '0' + num + // 0x37 + num == 'A' + (num - 10) + hex[i] = (char)((num < 10 ? 0x30 : 0x37) + num); - writer.Write(QuoteChar); - } - - /// - /// micro optimizations: using flags instead of value.IndexOfAny(EscapeChars) - /// - /// - /// - private static bool HasAnyEscapeChars(string value) - { - var len = value.Length; - for (var i = 0; i < len; i++) - { - var c = value[i]; - - // non-printable - if (!(value[i] >= 32 && value[i] <= 126)) return true; - - if (c >= LengthFromLargestChar || !EscapeCharFlags[c]) continue; - return true; - } - return false; - } - - public static void IntToHex(int intValue, char[] hex) - { - for (var i = 0; i < 4; i++) - { - var num = intValue % 16; - - if (num < 10) - hex[3 - i] = (char)('0' + num); - else - hex[3 - i] = (char)('A' + (num - 10)); - - intValue >>= 4; - } - } - - public static bool IsJsObject(string value) - { - return !string.IsNullOrEmpty(value) - && value[0] == '{' - && value[value.Length - 1] == '}'; - } - - public static bool IsJsArray(string value) - { - return !string.IsNullOrEmpty(value) - && value[0] == '[' - && value[value.Length - 1] == ']'; - } - } - -} \ No newline at end of file + intValue >>= 4; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsJsObject(string value) + { + return !string.IsNullOrEmpty(value) + && value[0] == '{' + && value[value.Length - 1] == '}'; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsJsObject(ReadOnlySpan value) + { + return !value.IsNullOrEmpty() + && value[0] == '{' + && value[value.Length - 1] == '}'; + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsJsArray(string value) + { + return !string.IsNullOrEmpty(value) + && value[0] == '[' + && value[value.Length - 1] == ']'; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsJsArray(ReadOnlySpan value) + { + return !value.IsNullOrEmpty() + && value[0] == '[' + && value[value.Length - 1] == ']'; + } + + } +} diff --git a/src/ServiceStack.Text/Json/JsonWriter.Generic.cs b/src/ServiceStack.Text/Json/JsonWriter.Generic.cs index 40a6edf86..c5a9cc865 100644 --- a/src/ServiceStack.Text/Json/JsonWriter.Generic.cs +++ b/src/ServiceStack.Text/Json/JsonWriter.Generic.cs @@ -1,161 +1,231 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; using System.Collections.Generic; using System.IO; -using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; using ServiceStack.Text.Common; namespace ServiceStack.Text.Json { - internal static class JsonWriter - { - public static readonly JsWriter Instance = new JsWriter(); - - private static Dictionary WriteFnCache = new Dictionary(); - - public static WriteObjectDelegate GetWriteFn(Type type) - { - try - { - WriteObjectDelegate writeFn; - if (WriteFnCache.TryGetValue(type, out writeFn)) return writeFn; - - var genericType = typeof(JsonWriter<>).MakeGenericType(type); - var mi = genericType.GetMethod("WriteFn", BindingFlags.Public | BindingFlags.Static); - var writeFactoryFn = (Func)Delegate.CreateDelegate(typeof(Func), mi); - writeFn = writeFactoryFn(); - - Dictionary snapshot, newCache; - do - { - snapshot = WriteFnCache; - newCache = new Dictionary(WriteFnCache); - newCache[type] = writeFn; - - } while (!ReferenceEquals( - Interlocked.CompareExchange(ref WriteFnCache, newCache, snapshot), snapshot)); - - return writeFn; - } - catch (Exception ex) - { - Tracer.Instance.WriteError(ex); - throw; - } - } - - private static Dictionary JsonTypeInfoCache = new Dictionary(); - - public static TypeInfo GetTypeInfo(Type type) - { - try - { - TypeInfo writeFn; - if (JsonTypeInfoCache.TryGetValue(type, out writeFn)) return writeFn; - - var genericType = typeof(JsonWriter<>).MakeGenericType(type); - var mi = genericType.GetMethod("GetTypeInfo", BindingFlags.Public | BindingFlags.Static); - var writeFactoryFn = (Func)Delegate.CreateDelegate(typeof(Func), mi); - writeFn = writeFactoryFn(); - - Dictionary snapshot, newCache; - do - { - snapshot = JsonTypeInfoCache; - newCache = new Dictionary(JsonTypeInfoCache); - newCache[type] = writeFn; - - } while (!ReferenceEquals( - Interlocked.CompareExchange(ref JsonTypeInfoCache, newCache, snapshot), snapshot)); - - return writeFn; - } - catch (Exception ex) - { - Tracer.Instance.WriteError(ex); - throw; - } - } - - public static void WriteLateBoundObject(TextWriter writer, object value) - { - if (value == null) - { - writer.Write(JsonUtils.Null); - return; - } - - var type = value.GetType(); - var writeFn = type == typeof(object) - ? WriteType.WriteObjectType - : GetWriteFn(type); - - var prevState = JsState.IsWritingDynamic; - JsState.IsWritingDynamic = true; - writeFn(writer, value); - JsState.IsWritingDynamic = prevState; - } - - public static WriteObjectDelegate GetValueTypeToStringMethod(Type type) - { - return Instance.GetValueTypeToStringMethod(type); - } - } - - internal class TypeInfo - { - internal bool EncodeMapKey; - } - - /// - /// Implement the serializer using a more static approach - /// - /// - internal static class JsonWriter - { - internal static TypeInfo TypeInfo; - private static readonly WriteObjectDelegate CacheFn; - - public static WriteObjectDelegate WriteFn() - { - return CacheFn ?? WriteObject; - } - - public static TypeInfo GetTypeInfo() - { - return TypeInfo; - } - - static JsonWriter() - { - TypeInfo = new TypeInfo { - EncodeMapKey = typeof(T) == typeof(bool) || typeof(T).IsNumericType() - }; - - CacheFn = typeof(T) == typeof(object) - ? JsonWriter.WriteLateBoundObject + public static class JsonWriter + { + public static readonly JsWriter Instance = new JsWriter(); + + private static Dictionary WriteFnCache = new Dictionary(); + + internal static void RemoveCacheFn(Type forType) + { + Dictionary snapshot, newCache; + do + { + snapshot = WriteFnCache; + newCache = new Dictionary(WriteFnCache); + newCache.Remove(forType); + + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref WriteFnCache, newCache, snapshot), snapshot)); + } + + internal static WriteObjectDelegate GetWriteFn(Type type) + { + try + { + if (WriteFnCache.TryGetValue(type, out var writeFn)) return writeFn; + + var genericType = typeof(JsonWriter<>).MakeGenericType(type); + var mi = genericType.GetStaticMethod("WriteFn"); + var writeFactoryFn = (Func)mi.MakeDelegate(typeof(Func)); + writeFn = writeFactoryFn(); + + Dictionary snapshot, newCache; + do + { + snapshot = WriteFnCache; + newCache = new Dictionary(WriteFnCache) + { + [type] = writeFn + }; + + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref WriteFnCache, newCache, snapshot), snapshot)); + + return writeFn; + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + throw; + } + } + + private static Dictionary JsonTypeInfoCache = new Dictionary(); + + internal static TypeInfo GetTypeInfo(Type type) + { + try + { + if (JsonTypeInfoCache.TryGetValue(type, out var writeFn)) return writeFn; + + var genericType = typeof(JsonWriter<>).MakeGenericType(type); + var mi = genericType.GetStaticMethod("GetTypeInfo"); + var writeFactoryFn = (Func)mi.MakeDelegate(typeof(Func)); + writeFn = writeFactoryFn(); + + Dictionary snapshot, newCache; + do + { + snapshot = JsonTypeInfoCache; + newCache = new Dictionary(JsonTypeInfoCache) + { + [type] = writeFn + }; + + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref JsonTypeInfoCache, newCache, snapshot), snapshot)); + + return writeFn; + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + throw; + } + } + + internal static void WriteLateBoundObject(TextWriter writer, object value) + { + if (value == null) + { + writer.Write(JsonUtils.Null); + return; + } + + try + { + if (!JsState.Traverse(value)) + return; + + var type = value.GetType(); + var writeFn = type == typeof(object) + ? WriteType.WriteObjectType + : GetWriteFn(type); + + var prevState = JsState.IsWritingDynamic; + JsState.IsWritingDynamic = true; + writeFn(writer, value); + JsState.IsWritingDynamic = prevState; + } + finally + { + JsState.UnTraverse(); + } + } + + internal static WriteObjectDelegate GetValueTypeToStringMethod(Type type) + { + return Instance.GetValueTypeToStringMethod(type); + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + Text.Json.JsonWriter.WriteFn(); + Text.Json.JsonWriter.Instance.GetWriteFn(); + Text.Json.JsonWriter.Instance.GetValueTypeToStringMethod(typeof(T)); + JsWriter.GetTypeSerializer().GetWriteFn(); + } + } + + public class TypeInfo + { + internal bool EncodeMapKey; + } + + /// + /// Implement the serializer using a more static approach + /// + /// + public static class JsonWriter + { + internal static TypeInfo TypeInfo; + private static WriteObjectDelegate CacheFn; + + public static void Reset() + { + JsonWriter.RemoveCacheFn(typeof(T)); + Refresh(); + } + + public static void Refresh() + { + if (JsonWriter.Instance == null) + return; + + CacheFn = typeof(T) == typeof(object) + ? JsonWriter.WriteLateBoundObject : JsonWriter.Instance.GetWriteFn(); - } - - public static void WriteObject(TextWriter writer, object value) - { -#if MONOTOUCH - if (writer == null) return; -#endif - CacheFn(writer, value); - } - } + JsConfig.AddUniqueType(typeof(T)); + } + + public static WriteObjectDelegate WriteFn() + { + return CacheFn ?? WriteObject; + } + + public static TypeInfo GetTypeInfo() + { + return TypeInfo; + } + + static JsonWriter() + { + if (JsonWriter.Instance == null) + return; + + var isNumeric = typeof(T).IsNumericType(); + TypeInfo = new TypeInfo + { + EncodeMapKey = typeof(T) == typeof(bool) || isNumeric, + }; + + CacheFn = typeof(T) == typeof(object) + ? JsonWriter.WriteLateBoundObject + : JsonWriter.Instance.GetWriteFn(); + } + + public static void WriteObject(TextWriter writer, object value) + { + TypeConfig.Init(); + + try + { + if (!JsState.Traverse(value)) + return; + + CacheFn(writer, value); + } + finally + { + JsState.UnTraverse(); + } + } + + public static void WriteRootObject(TextWriter writer, object value) + { + GetRootObjectWriteFn(value)(writer, value); + } + + public static WriteObjectDelegate GetRootObjectWriteFn(object value) + { + TypeConfig.Init(); + JsonSerializer.OnSerialize?.Invoke(value); + + JsState.Depth = 0; + return CacheFn; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/JsonObject.cs b/src/ServiceStack.Text/JsonObject.cs index a8f50c56c..196a13d44 100644 --- a/src/ServiceStack.Text/JsonObject.cs +++ b/src/ServiceStack.Text/JsonObject.cs @@ -1,100 +1,141 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.IO; -using System.Text.RegularExpressions; using ServiceStack.Text.Common; using ServiceStack.Text.Json; namespace ServiceStack.Text { - public static class JsonExtensions - { - public static T JsonTo(this Dictionary map, string key) - { - return Get(map, key); - } + public static class JsonExtensions + { + public static T JsonTo(this Dictionary map, string key) + { + return Get(map, key); + } /// /// Get JSON string value converted to T /// - public static T Get(this Dictionary map, string key) - { - string strVal; - return map.TryGetValue(key, out strVal) ? JsonSerializer.DeserializeFromString(strVal) : default(T); - } + public static T Get(this Dictionary map, string key, T defaultValue = default) + { + if (map == null) + return default; + return map.TryGetValue(key, out var strVal) ? JsonSerializer.DeserializeFromString(strVal) : defaultValue; + } + + public static T[] GetArray(this Dictionary map, string key) + { + if (map == null) + return TypeConstants.EmptyArray; + return map.TryGetValue(key, out var value) + ? (map is JsonObject obj ? value.FromJson() : value.FromJsv()) + : TypeConstants.EmptyArray; + } /// /// Get JSON string value /// public static string Get(this Dictionary map, string key) - { - string strVal; - return map.TryGetValue(key, out strVal) ? JsonTypeSerializer.Instance.UnescapeString(strVal) : null; - } - - public static JsonArrayObjects ArrayObjects(this string json) - { - return Text.JsonArrayObjects.Parse(json); - } - - public static List ConvertAll(this JsonArrayObjects jsonArrayObjects, Func converter) - { - var results = new List(); - - foreach (var jsonObject in jsonArrayObjects) - { - results.Add(converter(jsonObject)); - } - - return results; - } - - public static T ConvertTo(this JsonObject jsonObject, Func converFn) - { - return jsonObject == null - ? default(T) - : converFn(jsonObject); - } - - public static Dictionary ToDictionary(this JsonObject jsonObject) - { - return jsonObject == null - ? new Dictionary() - : new Dictionary(jsonObject); - } - } - - public class JsonObject : Dictionary - { + { + if (map == null) + return null; + return map.TryGetValue(key, out var strVal) + ? JsonTypeSerializer.Instance.UnescapeString(strVal) + : null; + } + + public static JsonArrayObjects ArrayObjects(this string json) + { + return Text.JsonArrayObjects.Parse(json); + } + + public static List ConvertAll(this JsonArrayObjects jsonArrayObjects, Func converter) + { + var results = new List(); + + foreach (var jsonObject in jsonArrayObjects) + { + results.Add(converter(jsonObject)); + } + + return results; + } + + public static T ConvertTo(this JsonObject jsonObject, Func convertFn) + { + return jsonObject == null + ? default + : convertFn(jsonObject); + } + + public static Dictionary ToDictionary(this JsonObject jsonObject) + { + return jsonObject == null + ? new Dictionary() + : new Dictionary(jsonObject); + } + } + + public class JsonObject : Dictionary, IEnumerable> + { /// /// Get JSON string value /// public new string this[string key] { - get { return this.Get(key); } - set { base[key] = value; } + get => this.Get(key); + set => base[key] = value; + } + + public new Enumerator GetEnumerator() + { + var to = new Dictionary(); + foreach (var key in Keys) + { + to[key] = this[key]; + } + return to.GetEnumerator(); } - public static JsonObject Parse(string json) - { - return JsonSerializer.DeserializeFromString(json); - } + IEnumerator> IEnumerable>.GetEnumerator() + => GetEnumerator(); - public JsonArrayObjects ArrayObjects(string propertyName) - { - string strValue; - return this.TryGetValue(propertyName, out strValue) - ? JsonArrayObjects.Parse(strValue) - : null; - } + public Dictionary ToUnescapedDictionary() + { + var to = new Dictionary(); + var enumerateAsConcreteDict = (Dictionary)this; + foreach (var entry in enumerateAsConcreteDict) + { + to[entry.Key] = entry.Value; + } + return to; + } - public JsonObject Object(string propertyName) - { - string strValue; - return this.TryGetValue(propertyName, out strValue) - ? Parse(strValue) - : null; - } + public static JsonObject Parse(string json) + { + return JsonSerializer.DeserializeFromString(json); + } + + public static JsonArrayObjects ParseArray(string json) + { + return JsonArrayObjects.Parse(json); + } + + public JsonArrayObjects ArrayObjects(string propertyName) + { + return this.TryGetValue(propertyName, out var strValue) + ? JsonArrayObjects.Parse(strValue) + : null; + } + + public JsonObject Object(string propertyName) + { + return this.TryGetValue(propertyName, out var strValue) + ? Parse(strValue) + : null; + } /// /// Get unescaped string value @@ -111,11 +152,7 @@ public string Child(string key) { return base[key]; } -#if !SILVERLIGHT && !MONOTOUCH - static readonly Regex NumberRegEx = new Regex(@"^[0-9]*(?:\.[0-9]*)?$", RegexOptions.Compiled); -#else - static readonly Regex NumberRegEx = new Regex(@"^[0-9]*(?:\.[0-9]*)?$"); -#endif + /// /// Write JSON Array, Object, bool or number values as raw string /// @@ -127,10 +164,10 @@ public static void WriteValue(TextWriter writer, object value) var firstChar = strValue[0]; var lastChar = strValue[strValue.Length - 1]; if ((firstChar == JsWriter.MapStartChar && lastChar == JsWriter.MapEndChar) - || (firstChar == JsWriter.ListStartChar && lastChar == JsWriter.ListEndChar) + || (firstChar == JsWriter.ListStartChar && lastChar == JsWriter.ListEndChar) || JsonUtils.True == strValue || JsonUtils.False == strValue - || NumberRegEx.IsMatch(strValue)) + || IsJavaScriptNumber(strValue)) { writer.Write(strValue); return; @@ -138,34 +175,78 @@ public static void WriteValue(TextWriter writer, object value) } JsonUtils.WriteString(writer, strValue); } - } - public class JsonArrayObjects : List - { - public static JsonArrayObjects Parse(string json) - { - return JsonSerializer.DeserializeFromString(json); - } - } + private static bool IsJavaScriptNumber(string strValue) + { + var firstChar = strValue[0]; + if (firstChar == '0') + { + if (strValue.Length == 1) + return true; + if (!strValue.Contains(".")) + return false; + } - public struct JsonValue - { - private readonly string json; + if (!strValue.Contains(".")) + { + if (long.TryParse(strValue, out var longValue)) + { + return longValue < JsonUtils.MaxInteger && longValue > JsonUtils.MinInteger; + } + return false; + } - public JsonValue(string json) + if (double.TryParse(strValue, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) + { + return doubleValue < JsonUtils.MaxInteger && doubleValue > JsonUtils.MinInteger; + } + return false; + } + + public T ConvertTo() { - this.json = json; + return (T)this.ConvertTo(typeof(T)); } - public T As() + public object ConvertTo(Type type) { - return JsonSerializer.DeserializeFromString(json); + var map = new Dictionary(); + + foreach (var entry in this) + { + map[entry.Key] = entry.Value; + } + + return map.FromObjectDictionary(type); } - - public override string ToString() + } + + public class JsonArrayObjects : List + { + public static JsonArrayObjects Parse(string json) { - return json; + return JsonSerializer.DeserializeFromString(json); } } + public interface IValueWriter + { + void WriteTo(ITypeSerializer serializer, TextWriter writer); + } + + public struct JsonValue : IValueWriter + { + private readonly string json; + + public JsonValue(string json) + { + this.json = json; + } + + public T As() => JsonSerializer.DeserializeFromString(json); + + public override string ToString() => json; + + public void WriteTo(ITypeSerializer serializer, TextWriter writer) => writer.Write(json ?? JsonUtils.Null); + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/JsonSerializer.Generic.cs b/src/ServiceStack.Text/JsonSerializer.Generic.cs index fa054c31a..269757c98 100644 --- a/src/ServiceStack.Text/JsonSerializer.Generic.cs +++ b/src/ServiceStack.Text/JsonSerializer.Generic.cs @@ -5,79 +5,74 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.IO; using System.Text; +using System.Reflection; using ServiceStack.Text.Common; using ServiceStack.Text.Json; +using ServiceStack.Text.Pools; namespace ServiceStack.Text { - public class JsonSerializer : ITypeSerializer - { - public bool CanCreateFromString(Type type) - { - return JsonReader.GetParseFn(type) != null; - } + public class JsonSerializer : ITypeSerializer + { + public bool CanCreateFromString(Type type) + { + return JsonReader.GetParseFn(type) != null; + } - /// - /// Parses the specified value. - /// - /// The value. - /// - public T DeserializeFromString(string value) - { - if (string.IsNullOrEmpty(value)) return default(T); - return (T)JsonReader.Parse(value); - } + /// + /// Parses the specified value. + /// + /// The value. + /// + public T DeserializeFromString(string value) + { + if (string.IsNullOrEmpty(value)) return default(T); + return (T)JsonReader.Parse(value); + } - public T DeserializeFromReader(TextReader reader) - { - return DeserializeFromString(reader.ReadToEnd()); - } + public T DeserializeFromReader(TextReader reader) + { + return DeserializeFromString(reader.ReadToEnd()); + } - public string SerializeToString(T value) - { - if (value == null) return null; - if (typeof(T) == typeof(string)) return value as string; + public string SerializeToString(T value) + { + if (value == null) return null; if (typeof(T) == typeof(object) || typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; var result = JsonSerializer.SerializeToString(value, value.GetType()); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; + if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = prevState; return result; } - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb)) - { - JsonWriter.WriteObject(writer, value); - } - return sb.ToString(); - } + var writer = StringWriterThreadStatic.Allocate(); + JsonWriter.WriteObject(writer, value); + return StringWriterThreadStatic.ReturnAndFree(writer); + } - public void SerializeToWriter(T value, TextWriter writer) - { - if (value == null) return; - if (typeof(T) == typeof(string)) - { - writer.Write(value); - return; - } + public void SerializeToWriter(T value, TextWriter writer) + { + if (value == null) return; if (typeof(T) == typeof(object) || typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; JsonSerializer.SerializeToWriter(value, value.GetType(), writer); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; + if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = prevState; return; } - + JsonWriter.WriteObject(writer, value); - } - } + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/JsonSerializer.cs b/src/ServiceStack.Text/JsonSerializer.cs index 9d6a65479..d3468fb74 100644 --- a/src/ServiceStack.Text/JsonSerializer.cs +++ b/src/ServiceStack.Text/JsonSerializer.cs @@ -1,4 +1,3 @@ - // // https://github.com/ServiceStack/ServiceStack.Text // ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. @@ -6,163 +5,293 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; -using System.Globalization; using System.IO; +using System.Net; using System.Text; +using System.Threading; +using System.Threading.Tasks; using ServiceStack.Text.Common; using ServiceStack.Text.Json; namespace ServiceStack.Text { - /// - /// Creates an instance of a Type from a string value - /// - public static class JsonSerializer - { - private static readonly UTF8Encoding UTF8EncodingWithoutBom = new UTF8Encoding(false); - - public static T DeserializeFromString(string value) - { - if (string.IsNullOrEmpty(value)) return default(T); - return (T)JsonReader.Parse(value); - } - - public static T DeserializeFromReader(TextReader reader) - { - return DeserializeFromString(reader.ReadToEnd()); - } - - public static object DeserializeFromString(string value, Type type) - { - return string.IsNullOrEmpty(value) - ? null - : JsonReader.GetParseFn(type)(value); - } + /// + /// Creates an instance of a Type from a string value + /// + public static class JsonSerializer + { + static JsonSerializer() + { + JsConfig.InitStatics(); + } + + public static int BufferSize = 1024; + + [Obsolete("Use JsConfig.UTF8Encoding")] + public static UTF8Encoding UTF8Encoding + { + get => JsConfig.UTF8Encoding; + set => JsConfig.UTF8Encoding = value; + } + + public static Action OnSerialize { get; set; } - public static object DeserializeFromReader(TextReader reader, Type type) - { - return DeserializeFromString(reader.ReadToEnd(), type); - } + public static T DeserializeFromString(string value) + { + return JsonReader.Parse(value) is T obj ? obj : default(T); + } + + public static T DeserializeFromSpan(ReadOnlySpan value) + { + return JsonReader.Parse(value) is T obj ? obj : default(T); + } + + public static T DeserializeFromReader(TextReader reader) + { + return DeserializeFromString(reader.ReadToEnd()); + } + + public static object DeserializeFromSpan(Type type, ReadOnlySpan value) + { + return value.IsEmpty + ? null + : JsonReader.GetParseSpanFn(type)(value); + } + + public static object DeserializeFromString(string value, Type type) + { + return string.IsNullOrEmpty(value) + ? null + : JsonReader.GetParseFn(type)(value); + } - public static string SerializeToString(T value) - { - if (value == null) return null; - if (typeof(T) == typeof(object) || typeof(T).IsAbstract || typeof(T).IsInterface) + public static object DeserializeFromReader(TextReader reader, Type type) + { + return DeserializeFromString(reader.ReadToEnd(), type); + } + + public static string SerializeToString(T value) + { + if (value == null || value is Delegate) return null; + if (typeof(T) == typeof(object)) { - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; + return SerializeToString(value, value.GetType()); + } + if (typeof(T).IsAbstract || typeof(T).IsInterface) + { + var prevState = JsState.IsWritingDynamic; + JsState.IsWritingDynamic = true; var result = SerializeToString(value, value.GetType()); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; + JsState.IsWritingDynamic = prevState; return result; } - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture)) - { - if (typeof(T) == typeof(string)) - { - JsonUtils.WriteString(writer, value as string); - } - else - { - JsonWriter.WriteObject(writer, value); - } - } - return sb.ToString(); - } - - public static string SerializeToString(object value, Type type) - { - if (value == null) return null; - - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture)) - { - if (type == typeof(string)) - { - JsonUtils.WriteString(writer, value as string); - } - else - { - JsonWriter.GetWriteFn(type)(writer, value); - } - } - return sb.ToString(); - } - - public static void SerializeToWriter(T value, TextWriter writer) - { - if (value == null) return; - if (typeof(T) == typeof(string)) - { - writer.Write(value); - return; - } - if (typeof(T) == typeof(object) || typeof(T).IsAbstract || typeof(T).IsInterface) - { - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; + var writer = StringWriterThreadStatic.Allocate(); + if (typeof(T) == typeof(string)) + { + JsonUtils.WriteString(writer, value as string); + } + else + { + WriteObjectToWriter(value, JsonWriter.GetRootObjectWriteFn(value), writer); + } + return StringWriterThreadStatic.ReturnAndFree(writer); + } + + public static string SerializeToString(object value, Type type) + { + if (value == null) return null; + + var writer = StringWriterThreadStatic.Allocate(); + if (type == typeof(string)) + { + JsonUtils.WriteString(writer, value as string); + } + else + { + OnSerialize?.Invoke(value); + WriteObjectToWriter(value, JsonWriter.GetWriteFn(type), writer); + } + return StringWriterThreadStatic.ReturnAndFree(writer); + } + + public static void SerializeToWriter(T value, TextWriter writer) + { + if (value == null) return; + if (typeof(T) == typeof(string)) + { + JsonUtils.WriteString(writer, value as string); + } + else if (typeof(T) == typeof(object)) + { + SerializeToWriter(value, value.GetType(), writer); + } + else if (typeof(T).IsAbstract || typeof(T).IsInterface) + { + var prevState = JsState.IsWritingDynamic; + JsState.IsWritingDynamic = false; SerializeToWriter(value, value.GetType(), writer); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; + JsState.IsWritingDynamic = prevState; + } + else + { + WriteObjectToWriter(value, JsonWriter.GetRootObjectWriteFn(value), writer); + } + } + + public static void SerializeToWriter(object value, Type type, TextWriter writer) + { + if (value == null) return; + if (type == typeof(string)) + { + JsonUtils.WriteString(writer, value as string); return; - } - - JsonWriter.WriteObject(writer, value); - } - - public static void SerializeToWriter(object value, Type type, TextWriter writer) - { - if (value == null) return; - if (type == typeof(string)) - { - writer.Write(value); - return; - } - - JsonWriter.GetWriteFn(type)(writer, value); - } - - public static void SerializeToStream(T value, Stream stream) - { - if (value == null) return; - if (typeof(T) == typeof(object) || typeof(T).IsAbstract || typeof(T).IsInterface) - { - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; + } + + OnSerialize?.Invoke(value); + WriteObjectToWriter(value, JsonWriter.GetWriteFn(type), writer); + } + + public static void SerializeToStream(T value, Stream stream) + { + if (value == null) return; + if (typeof(T) == typeof(object)) + { SerializeToStream(value, value.GetType(), stream); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; - return; - } - - var writer = new StreamWriter(stream, UTF8EncodingWithoutBom); - JsonWriter.WriteObject(writer, value); - writer.Flush(); - } - - public static void SerializeToStream(object value, Type type, Stream stream) - { - var writer = new StreamWriter(stream, UTF8EncodingWithoutBom); - JsonWriter.GetWriteFn(type)(writer, value); - writer.Flush(); - } - - public static T DeserializeFromStream(Stream stream) - { - using (var reader = new StreamReader(stream, UTF8EncodingWithoutBom)) - { - return DeserializeFromString(reader.ReadToEnd()); - } - } - - public static object DeserializeFromStream(Type type, Stream stream) - { - using (var reader = new StreamReader(stream, UTF8EncodingWithoutBom)) - { - return DeserializeFromString(reader.ReadToEnd(), type); - } - } - } + } + else if (typeof(T).IsAbstract || typeof(T).IsInterface) + { + var prevState = JsState.IsWritingDynamic; + JsState.IsWritingDynamic = false; + SerializeToStream(value, value.GetType(), stream); + JsState.IsWritingDynamic = prevState; + } + else + { + var writer = new StreamWriter(stream, JsConfig.UTF8Encoding, BufferSize, leaveOpen:true); + WriteObjectToWriter(value, JsonWriter.GetRootObjectWriteFn(value), writer); + writer.Flush(); + } + } + + public static void SerializeToStream(object value, Type type, Stream stream) + { + OnSerialize?.Invoke(value); + var writer = new StreamWriter(stream, JsConfig.UTF8Encoding, BufferSize, leaveOpen:true); + WriteObjectToWriter(value, JsonWriter.GetWriteFn(type), writer); + writer.Flush(); + } + + private static void WriteObjectToWriter(object value, WriteObjectDelegate serializeFn, TextWriter writer) + { + if (!JsConfig.Indent) + { + serializeFn(writer, value); + } + else + { + var sb = StringBuilderCache.Allocate(); + using var captureJson = new StringWriter(sb); + serializeFn(captureJson, value); + captureJson.Flush(); + var json = StringBuilderCache.ReturnAndFree(sb); + var indentJson = json.IndentJson(); + writer.Write(indentJson); + } + } + public static T DeserializeFromStream(Stream stream) + { + return (T)MemoryProvider.Instance.Deserialize(stream, typeof(T), DeserializeFromSpan); + } + + public static object DeserializeFromStream(Type type, Stream stream) + { + return MemoryProvider.Instance.Deserialize(stream, type, DeserializeFromSpan); + } + + public static Task DeserializeFromStreamAsync(Type type, Stream stream) + { + return MemoryProvider.Instance.DeserializeAsync(stream, type, DeserializeFromSpan); + } + + public static async Task DeserializeFromStreamAsync(Stream stream) + { + var obj = await MemoryProvider.Instance.DeserializeAsync(stream, typeof(T), DeserializeFromSpan).ConfigAwait(); + return (T)obj; + } + + public static T DeserializeResponse(WebRequest webRequest) + { + using (var webRes = PclExport.Instance.GetResponse(webRequest)) + using (var stream = webRes.GetResponseStream()) + { + return DeserializeFromStream(stream); + } + } + + public static object DeserializeResponse(Type type, WebRequest webRequest) + { + using (var webRes = PclExport.Instance.GetResponse(webRequest)) + using (var stream = webRes.GetResponseStream()) + { + return DeserializeFromStream(type, stream); + } + } + + public static T DeserializeRequest(WebRequest webRequest) + { + using (var webRes = PclExport.Instance.GetResponse(webRequest)) + { + return DeserializeResponse(webRes); + } + } + + public static object DeserializeRequest(Type type, WebRequest webRequest) + { + using (var webRes = PclExport.Instance.GetResponse(webRequest)) + { + return DeserializeResponse(type, webRes); + } + } + + public static T DeserializeResponse(WebResponse webResponse) + { + using (var stream = webResponse.GetResponseStream()) + { + return DeserializeFromStream(stream); + } + } + + public static object DeserializeResponse(Type type, WebResponse webResponse) + { + using (var stream = webResponse.GetResponseStream()) + { + return DeserializeFromStream(type, stream); + } + } + } + + public class JsonStringSerializer : IStringSerializer + { + public To DeserializeFromString(string serializedText) + { + return JsonSerializer.DeserializeFromString(serializedText); + } + + public object DeserializeFromString(string serializedText, Type type) + { + return JsonSerializer.DeserializeFromString(serializedText, type); + } + + public string SerializeToString(TFrom @from) + { + return JsonSerializer.SerializeToString(@from); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Jsv/JsvDeserializeType.cs b/src/ServiceStack.Text/Jsv/JsvDeserializeType.cs deleted file mode 100644 index c80ea3a92..000000000 --- a/src/ServiceStack.Text/Jsv/JsvDeserializeType.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Reflection; -using ServiceStack.Text.Common; - -namespace ServiceStack.Text.Jsv -{ - public static class JsvDeserializeType - { - public static SetPropertyDelegate GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) - { - return TypeAccessor.GetSetPropertyMethod(type, propertyInfo); - } - } -} \ No newline at end of file diff --git a/src/ServiceStack.Text/Jsv/JsvReader.Generic.cs b/src/ServiceStack.Text/Jsv/JsvReader.Generic.cs index 2c08cdea5..df21cd9ad 100644 --- a/src/ServiceStack.Text/Jsv/JsvReader.Generic.cs +++ b/src/ServiceStack.Text/Jsv/JsvReader.Generic.cs @@ -1,81 +1,108 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; using System.Collections.Generic; -using System.Reflection; using System.Threading; +using System.Runtime.CompilerServices; using ServiceStack.Text.Common; namespace ServiceStack.Text.Jsv { - public static class JsvReader - { - internal static readonly JsReader Instance = new JsReader(); + public static class JsvReader + { + internal static readonly JsReader Instance = new JsReader(); private static Dictionary ParseFnCache = new Dictionary(); - public static ParseStringDelegate GetParseFn(Type type) - { - ParseFactoryDelegate parseFactoryFn; - ParseFnCache.TryGetValue(type, out parseFactoryFn); + public static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSpanFn(type)(v.AsSpan()); + + public static ParseStringSpanDelegate GetParseSpanFn(Type type) => v => GetParseStringSpanFn(type)(v); + + public static ParseStringSpanDelegate GetParseStringSpanFn(Type type) + { + ParseFnCache.TryGetValue(type, out var parseFactoryFn); if (parseFactoryFn != null) return parseFactoryFn(); var genericType = typeof(JsvReader<>).MakeGenericType(type); - var mi = genericType.GetMethod("GetParseFn", BindingFlags.Public | BindingFlags.Static); - parseFactoryFn = (ParseFactoryDelegate)Delegate.CreateDelegate(typeof(ParseFactoryDelegate), mi); + var mi = genericType.GetStaticMethod(nameof(GetParseStringSpanFn)); + parseFactoryFn = (ParseFactoryDelegate)mi.MakeDelegate(typeof(ParseFactoryDelegate)); Dictionary snapshot, newCache; do { snapshot = ParseFnCache; - newCache = new Dictionary(ParseFnCache); - newCache[type] = parseFactoryFn; + newCache = new Dictionary(ParseFnCache) { + [type] = parseFactoryFn + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseFnCache, newCache, snapshot), snapshot)); - + return parseFactoryFn(); - } - } - - public static class JsvReader - { - private static readonly ParseStringDelegate ReadFn; - - static JsvReader() - { - ReadFn = JsvReader.Instance.GetParseFn(); - } - - public static ParseStringDelegate GetParseFn() - { - return ReadFn ?? Parse; - } - - public static object Parse(string value) - { - if (ReadFn == null) - { - if (typeof(T).IsInterface) - { - throw new NotSupportedException("Can not deserialize interface type: " - + typeof(T).Name); - } - } - return value == null - ? null - : ReadFn(value); - } - } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + Text.Jsv.JsvReader.Instance.GetParseFn(); + Text.Jsv.JsvReader.Parse(default(ReadOnlySpan)); + Text.Jsv.JsvReader.GetParseFn(); + Text.Jsv.JsvReader.GetParseStringSpanFn(); + } + } + + internal static class JsvReader + { + private static ParseStringSpanDelegate ReadFn; + + static JsvReader() + { + Refresh(); + } + + public static void Refresh() + { + JsConfig.InitStatics(); + + if (JsvReader.Instance == null) + return; + + ReadFn = JsvReader.Instance.GetParseStringSpanFn(); + JsConfig.AddUniqueType(typeof(T)); + } + + public static ParseStringDelegate GetParseFn() => ReadFn != null + ? (ParseStringDelegate)(v => ReadFn(v.AsSpan())) + : Parse; + + public static ParseStringSpanDelegate GetParseStringSpanFn() => ReadFn ?? Parse; + + public static object Parse(string value) => value != null + ? Parse(value.AsSpan()) + : null; + + public static object Parse(ReadOnlySpan value) + { + TypeConfig.Init(); + + value = value.WithoutBom(); + + if (ReadFn == null) + { + if (typeof(T).IsInterface) + { + throw new NotSupportedException("Can not deserialize interface type: " + + typeof(T).Name); + } + + Refresh(); + } + + return !value.IsEmpty + ? ReadFn(value) + : null; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Jsv/JsvSerializer.Generic.cs b/src/ServiceStack.Text/Jsv/JsvSerializer.Generic.cs index eb7a5b641..4bf59a4ea 100644 --- a/src/ServiceStack.Text/Jsv/JsvSerializer.Generic.cs +++ b/src/ServiceStack.Text/Jsv/JsvSerializer.Generic.cs @@ -1,14 +1,5 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; using System.Collections.Generic; @@ -19,18 +10,17 @@ namespace ServiceStack.Text.Jsv { - public class JsvSerializer - { - Dictionary DeserializerCache = new Dictionary(); + internal class JsvSerializer + { + Dictionary DeserializerCache = new Dictionary(); - public T DeserializeFromString(string value, Type type) - { - ParseStringDelegate parseFn; - if (DeserializerCache.TryGetValue(type, out parseFn)) return (T)parseFn(value); + public T DeserializeFromString(string value, Type type) + { + if (DeserializerCache.TryGetValue(type, out var parseFn)) return (T)parseFn(value); var genericType = typeof(T).MakeGenericType(type); - var mi = genericType.GetMethod("DeserializeFromString", new[] { typeof(string) }); - parseFn = (ParseStringDelegate)Delegate.CreateDelegate(typeof(ParseStringDelegate), mi); + var mi = genericType.GetMethodInfo("DeserializeFromString", new[] { typeof(string) }); + parseFn = (ParseStringDelegate)mi.MakeDelegate(typeof(ParseStringDelegate)); Dictionary snapshot, newCache; do @@ -41,33 +31,30 @@ public T DeserializeFromString(string value, Type type) } while (!ReferenceEquals( Interlocked.CompareExchange(ref DeserializerCache, newCache, snapshot), snapshot)); - - return (T)parseFn(value); - } - - public T DeserializeFromString(string value) - { - if (typeof(T) == typeof(string)) return (T)(object)value; - - return (T)JsvReader.Parse(value); - } - public void SerializeToWriter(T value, TextWriter writer) - { - JsvWriter.WriteObject(writer, value); - } - - public string SerializeToString(T value) - { - if (value == null) return null; - if (value is string) return value as string; - - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb)) - { - JsvWriter.WriteObject(writer, value); - } - return sb.ToString(); - } - } + return (T)parseFn(value); + } + + public T DeserializeFromString(string value) + { + if (typeof(T) == typeof(string)) return (T)(object)value; + + return (T)JsvReader.Parse(value); + } + + public void SerializeToWriter(T value, TextWriter writer) + { + JsvWriter.WriteObject(writer, value); + } + + public string SerializeToString(T value) + { + if (value == null) return null; + if (value is string) return value as string; + + var writer = StringWriterThreadStatic.Allocate(); + JsvWriter.WriteObject(writer, value); + return StringWriterThreadStatic.ReturnAndFree(writer); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs b/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs index ad8a5f057..4c7a48306 100644 --- a/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs +++ b/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs @@ -1,114 +1,121 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; using System.Globalization; using System.IO; +using System.Runtime.CompilerServices; using ServiceStack.Text.Common; using ServiceStack.Text.Json; namespace ServiceStack.Text.Jsv { - internal class JsvTypeSerializer - : ITypeSerializer - { - public static ITypeSerializer Instance = new JsvTypeSerializer(); - - public bool IncludeNullValues - { - get { return false; } //Doesn't support null values, treated as "null" string literal - } - - public string TypeAttrInObject - { - get { return JsConfig.JsvTypeAttrInObject; } - } - - internal static string GetTypeAttrInObject(string typeAttr) - { - return string.Format("{{{0}:", typeAttr); - } - - public WriteObjectDelegate GetWriteFn() - { - return JsvWriter.WriteFn(); - } - - public WriteObjectDelegate GetWriteFn(Type type) - { - return JsvWriter.GetWriteFn(type); - } - - static readonly TypeInfo DefaultTypeInfo = new TypeInfo { EncodeMapKey = false }; - - public TypeInfo GetTypeInfo(Type type) - { - return DefaultTypeInfo; - } - - public void WriteRawString(TextWriter writer, string value) - { - writer.Write(value.EncodeJsv()); - } - - public void WritePropertyName(TextWriter writer, string value) - { - writer.Write(value); - } - - public void WriteBuiltIn(TextWriter writer, object value) - { - writer.Write(value); - } - - public void WriteObjectString(TextWriter writer, object value) - { - if (value != null) - { - writer.Write(value.ToString().EncodeJsv()); - } - } - - public void WriteException(TextWriter writer, object value) - { - writer.Write(((Exception)value).Message.EncodeJsv()); - } - - public void WriteString(TextWriter writer, string value) - { - writer.Write(value.EncodeJsv()); - } - - public void WriteDateTime(TextWriter writer, object oDateTime) - { - writer.Write(DateTimeSerializer.ToShortestXsdDateTimeString((DateTime)oDateTime)); - } - - public void WriteNullableDateTime(TextWriter writer, object dateTime) - { - if (dateTime == null) return; - writer.Write(DateTimeSerializer.ToShortestXsdDateTimeString((DateTime)dateTime)); - } - - public void WriteDateTimeOffset(TextWriter writer, object oDateTimeOffset) - { - writer.Write(((DateTimeOffset) oDateTimeOffset).ToString("o")); - } - - public void WriteNullableDateTimeOffset(TextWriter writer, object dateTimeOffset) - { - if (dateTimeOffset == null) return; - this.WriteDateTimeOffset(writer, dateTimeOffset); - } + public struct JsvTypeSerializer + : ITypeSerializer + { + public static ITypeSerializer Instance = new JsvTypeSerializer(); + + public ObjectDeserializerDelegate ObjectDeserializer { get; set; } + + public bool IncludeNullValues => false; + + public bool IncludeNullValuesInDictionaries => false; + + public string TypeAttrInObject => JsConfig.JsvTypeAttrInObject; + + internal static string GetTypeAttrInObject(string typeAttr) => $"{{{typeAttr}:"; + + public WriteObjectDelegate GetWriteFn() => JsvWriter.WriteFn(); + + public WriteObjectDelegate GetWriteFn(Type type) => JsvWriter.GetWriteFn(type); + + static readonly TypeInfo DefaultTypeInfo = new TypeInfo { EncodeMapKey = false }; + + public TypeInfo GetTypeInfo(Type type) => DefaultTypeInfo; + + public void WriteRawString(TextWriter writer, string value) + { + writer.Write(value.EncodeJsv()); + } + + public void WritePropertyName(TextWriter writer, string value) + { + writer.Write(value); + } + + public void WriteBuiltIn(TextWriter writer, object value) + { + writer.Write(value); + } + + public void WriteObjectString(TextWriter writer, object value) + { + if (value != null) + { + if (value is string strValue) + { + WriteString(writer, strValue); + } + else + { + writer.Write(value.ToString().EncodeJsv()); + } + } + } + + public void WriteException(TextWriter writer, object value) + { + writer.Write(((Exception)value).Message.EncodeJsv()); + } + + public void WriteString(TextWriter writer, string value) + { + if (JsState.QueryStringMode && !string.IsNullOrEmpty(value) && value.StartsWith(JsWriter.QuoteString) && value.EndsWith(JsWriter.QuoteString)) + value = String.Concat(JsWriter.QuoteChar, value, JsWriter.QuoteChar); + else if (JsState.QueryStringMode && !string.IsNullOrEmpty(value) && value.Contains(JsWriter.ItemSeperatorString)) + value = String.Concat(JsWriter.QuoteChar, value, JsWriter.QuoteChar); + + writer.Write(value == "" ? "\"\"" : value.EncodeJsv()); + } + + public void WriteFormattableObjectString(TextWriter writer, object value) + { + var f = (IFormattable)value; + writer.Write(f.ToString(null, CultureInfo.InvariantCulture).EncodeJsv()); + } + + public void WriteDateTime(TextWriter writer, object oDateTime) + { + var dateTime = (DateTime)oDateTime; + switch (JsConfig.DateHandler) + { + case DateHandler.UnixTime: + writer.Write(dateTime.ToUnixTime()); + return; + case DateHandler.UnixTimeMs: + writer.Write(dateTime.ToUnixTimeMs()); + return; + } + + writer.Write(DateTimeSerializer.ToShortestXsdDateTimeString((DateTime)oDateTime)); + } + + public void WriteNullableDateTime(TextWriter writer, object dateTime) + { + if (dateTime == null) return; + WriteDateTime(writer, dateTime); + } + + public void WriteDateTimeOffset(TextWriter writer, object oDateTimeOffset) + { + writer.Write(((DateTimeOffset)oDateTimeOffset).ToString("o")); + } + + public void WriteNullableDateTimeOffset(TextWriter writer, object dateTimeOffset) + { + if (dateTimeOffset == null) return; + this.WriteDateTimeOffset(writer, dateTimeOffset); + } public void WriteTimeSpan(TextWriter writer, object oTimeSpan) { @@ -121,329 +128,400 @@ public void WriteNullableTimeSpan(TextWriter writer, object oTimeSpan) writer.Write(DateTimeSerializer.ToXsdTimeSpanString((TimeSpan?)oTimeSpan)); } - public void WriteGuid(TextWriter writer, object oValue) - { - writer.Write(((Guid)oValue).ToString("N")); - } - - public void WriteNullableGuid(TextWriter writer, object oValue) - { - if (oValue == null) return; - writer.Write(((Guid)oValue).ToString("N")); - } - - public void WriteBytes(TextWriter writer, object oByteValue) - { - if (oByteValue == null) return; - writer.Write(Convert.ToBase64String((byte[])oByteValue)); - } - - public void WriteChar(TextWriter writer, object charValue) - { - if (charValue == null) return; - writer.Write((char)charValue); - } - - public void WriteByte(TextWriter writer, object byteValue) - { - if (byteValue == null) return; - writer.Write((byte)byteValue); - } - - public void WriteInt16(TextWriter writer, object intValue) - { - if (intValue == null) return; - writer.Write((short)intValue); - } - - public void WriteUInt16(TextWriter writer, object intValue) - { - if (intValue == null) return; - writer.Write((ushort)intValue); - } - - public void WriteInt32(TextWriter writer, object intValue) - { - if (intValue == null) return; - writer.Write((int)intValue); - } - - public void WriteUInt32(TextWriter writer, object uintValue) - { - if (uintValue == null) return; - writer.Write((uint)uintValue); - } - - public void WriteUInt64(TextWriter writer, object ulongValue) - { - if (ulongValue == null) return; - writer.Write((ulong)ulongValue); - } - - public void WriteInt64(TextWriter writer, object longValue) - { - if (longValue == null) return; - writer.Write((long)longValue); - } - - public void WriteBool(TextWriter writer, object boolValue) - { - if (boolValue == null) return; - writer.Write((bool)boolValue); - } - - public void WriteFloat(TextWriter writer, object floatValue) - { - if (floatValue == null) return; - var floatVal = (float)floatValue; - if (Equals(floatVal, float.MaxValue) || Equals(floatVal, float.MinValue)) - writer.Write(floatVal.ToString("r", CultureInfo.InvariantCulture)); - else - writer.Write(floatVal.ToString(CultureInfo.InvariantCulture)); - } - - public void WriteDouble(TextWriter writer, object doubleValue) - { - if (doubleValue == null) return; - var doubleVal = (double)doubleValue; - if (Equals(doubleVal, double.MaxValue) || Equals(doubleVal, double.MinValue)) - writer.Write(doubleVal.ToString("r", CultureInfo.InvariantCulture)); - else - writer.Write(doubleVal.ToString(CultureInfo.InvariantCulture)); - } - - public void WriteDecimal(TextWriter writer, object decimalValue) - { - if (decimalValue == null) return; - writer.Write(((decimal)decimalValue).ToString(CultureInfo.InvariantCulture)); - } - - public void WriteEnum(TextWriter writer, object enumValue) - { - if (enumValue == null) return; - if (JsConfig.TreatEnumAsInteger) - JsWriter.WriteEnumFlags(writer, enumValue); - else - writer.Write(enumValue.ToString()); - } - - public void WriteEnumFlags(TextWriter writer, object enumFlagValue) - { - JsWriter.WriteEnumFlags(writer, enumFlagValue); - } - - public void WriteLinqBinary(TextWriter writer, object linqBinaryValue) - { -#if !MONOTOUCH && !SILVERLIGHT && !XBOX && !ANDROID - WriteRawString(writer, Convert.ToBase64String(((System.Data.Linq.Binary)linqBinaryValue).ToArray())); -#endif - } - - public object EncodeMapKey(object value) - { - return value; - } - - public ParseStringDelegate GetParseFn() - { - return JsvReader.Instance.GetParseFn(); - } - - public ParseStringDelegate GetParseFn(Type type) - { - return JsvReader.GetParseFn(type); - } - - public string UnescapeSafeString(string value) - { - return value.FromCsvField(); - } - - public string ParseRawString(string value) - { - return value; - } - - public string ParseString(string value) - { - return value.FromCsvField(); - } - - public string UnescapeString(string value) - { - return value.FromCsvField(); - } - - public string EatTypeValue(string value, ref int i) - { - return EatValue(value, ref i); - } - - public bool EatMapStartChar(string value, ref int i) - { - var success = value[i] == JsWriter.MapStartChar; - if (success) i++; - return success; - } - - public string EatMapKey(string value, ref int i) - { - var tokenStartPos = i; - - var valueLength = value.Length; - - var valueChar = value[tokenStartPos]; - - switch (valueChar) - { - case JsWriter.QuoteChar: - while (++i < valueLength) - { - valueChar = value[i]; + public void WriteGuid(TextWriter writer, object oValue) + { + writer.Write(((Guid)oValue).ToString("N")); + } - if (valueChar != JsWriter.QuoteChar) continue; + public void WriteNullableGuid(TextWriter writer, object oValue) + { + if (oValue == null) return; + writer.Write(((Guid)oValue).ToString("N")); + } - var isLiteralQuote = i + 1 < valueLength && value[i + 1] == JsWriter.QuoteChar; + public void WriteBytes(TextWriter writer, object oByteValue) + { + if (oByteValue == null) return; + writer.Write(Convert.ToBase64String((byte[])oByteValue)); + } + + public void WriteChar(TextWriter writer, object charValue) + { + if (charValue == null) return; + writer.Write((char)charValue); + } + + public void WriteByte(TextWriter writer, object byteValue) + { + if (byteValue == null) return; + writer.Write((byte)byteValue); + } + + public void WriteSByte(TextWriter writer, object sbyteValue) + { + if (sbyteValue == null) return; + writer.Write((sbyte)sbyteValue); + } - i++; //skip quote - if (!isLiteralQuote) - break; - } - return value.Substring(tokenStartPos, i - tokenStartPos); + public void WriteInt16(TextWriter writer, object intValue) + { + if (intValue == null) return; + writer.Write((short)intValue); + } - //Is Type/Map, i.e. {...} - case JsWriter.MapStartChar: - var endsToEat = 1; - var withinQuotes = false; - while (++i < valueLength && endsToEat > 0) - { - valueChar = value[i]; + public void WriteUInt16(TextWriter writer, object intValue) + { + if (intValue == null) return; + writer.Write((ushort)intValue); + } - if (valueChar == JsWriter.QuoteChar) - withinQuotes = !withinQuotes; + public void WriteInt32(TextWriter writer, object intValue) + { + if (intValue == null) return; + writer.Write((int)intValue); + } - if (withinQuotes) - continue; + public void WriteUInt32(TextWriter writer, object uintValue) + { + if (uintValue == null) return; + writer.Write((uint)uintValue); + } - if (valueChar == JsWriter.MapStartChar) - endsToEat++; + public void WriteUInt64(TextWriter writer, object ulongValue) + { + if (ulongValue == null) return; + writer.Write((ulong)ulongValue); + } - if (valueChar == JsWriter.MapEndChar) - endsToEat--; - } - return value.Substring(tokenStartPos, i - tokenStartPos); - } + public void WriteInt64(TextWriter writer, object longValue) + { + if (longValue == null) return; + writer.Write((long)longValue); + } - while (value[++i] != JsWriter.MapKeySeperator) { } - return value.Substring(tokenStartPos, i - tokenStartPos); - } + public void WriteBool(TextWriter writer, object boolValue) + { + if (boolValue == null) return; + writer.Write((bool)boolValue); + } - public bool EatMapKeySeperator(string value, ref int i) - { - return value[i++] == JsWriter.MapKeySeperator; - } + public void WriteFloat(TextWriter writer, object floatValue) + { + if (floatValue == null) return; + var floatVal = (float)floatValue; + var cultureInfo = JsState.IsCsv ? CsvConfig.RealNumberCultureInfo : null; + + if (Equals(floatVal, float.MaxValue) || Equals(floatVal, float.MinValue)) + writer.Write(floatVal.ToString("r", cultureInfo ?? CultureInfo.InvariantCulture)); + else + writer.Write(floatVal.ToString("r", cultureInfo ?? CultureInfo.InvariantCulture)); + } - public bool EatItemSeperatorOrMapEndChar(string value, ref int i) - { - if (i == value.Length) return false; - - var success = value[i] == JsWriter.ItemSeperator - || value[i] == JsWriter.MapEndChar; - i++; - return success; - } + public void WriteDouble(TextWriter writer, object doubleValue) + { + if (doubleValue == null) return; + var doubleVal = (double)doubleValue; + var cultureInfo = JsState.IsCsv ? CsvConfig.RealNumberCultureInfo : null; + + if (Equals(doubleVal, double.MaxValue) || Equals(doubleVal, double.MinValue)) + writer.Write(doubleVal.ToString("r", cultureInfo ?? CultureInfo.InvariantCulture)); + else + writer.Write(doubleVal.ToString(cultureInfo ?? CultureInfo.InvariantCulture)); + } - public void EatWhitespace(string value, ref int i) - { + public void WriteDecimal(TextWriter writer, object decimalValue) + { + if (decimalValue == null) return; + var cultureInfo = JsState.IsCsv ? CsvConfig.RealNumberCultureInfo : null; + + writer.Write(((decimal)decimalValue).ToString(cultureInfo ?? CultureInfo.InvariantCulture)); + } + + public void WriteEnum(TextWriter writer, object enumValue) + { + if (enumValue == null) + return; + var serializedValue = CachedTypeInfo.Get(enumValue.GetType()).EnumInfo.GetSerializedValue(enumValue); + if (serializedValue is string strEnum) + writer.Write(strEnum); + else + JsWriter.WriteEnumFlags(writer, enumValue); + } + +#if NET6_0 + public void WriteDateOnly(TextWriter writer, object oDateOnly) + { + var dateOnly = (DateOnly)oDateOnly; + switch (JsConfig.DateHandler) + { + case DateHandler.UnixTime: + writer.Write(dateOnly.ToUnixTime()); + break; + case DateHandler.UnixTimeMs: + writer.Write(dateOnly.ToUnixTimeMs()); + break; + default: + writer.Write(dateOnly.ToString("O")); + break; + } + } + + public void WriteNullableDateOnly(TextWriter writer, object oDateOnly) + { + if (oDateOnly == null) return; + WriteDateOnly(writer, oDateOnly); + } + + public void WriteTimeOnly(TextWriter writer, object oTimeOnly) + { + var stringValue = JsConfig.TimeSpanHandler == TimeSpanHandler.StandardFormat + ? oTimeOnly.ToString() + : DateTimeSerializer.ToXsdTimeSpanString(((TimeOnly)oTimeOnly).ToTimeSpan()); + WriteRawString(writer, stringValue); + } + + public void WriteNullableTimeOnly(TextWriter writer, object oTimeOnly) + { + if (oTimeOnly == null) return; + WriteTimeSpan(writer, ((TimeOnly?)oTimeOnly).Value.ToTimeSpan()); + } +#endif + + public ParseStringDelegate GetParseFn() => JsvReader.Instance.GetParseFn(); + + public ParseStringDelegate GetParseFn(Type type) => JsvReader.GetParseFn(type); + + public ParseStringSpanDelegate GetParseStringSpanFn() => JsvReader.Instance.GetParseStringSpanFn(); + + public ParseStringSpanDelegate GetParseStringSpanFn(Type type) => JsvReader.GetParseStringSpanFn(type); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object UnescapeStringAsObject(ReadOnlySpan value) + { + return UnescapeSafeString(value).Value(); + } + + public string UnescapeSafeString(string value) => JsState.IsCsv + ? value + : value.FromCsvField(); + + public ReadOnlySpan UnescapeSafeString(ReadOnlySpan value) => JsState.IsCsv + ? value // already unescaped in CsvReader.ParseFields() + : value.FromCsvField(); + + public string ParseRawString(string value) => value; + + public string ParseString(string value) => value.FromCsvField(); + + public string ParseString(ReadOnlySpan value) => value.ToString().FromCsvField(); + + public string UnescapeString(string value) => value.FromCsvField(); + + public ReadOnlySpan UnescapeString(ReadOnlySpan value) => value.FromCsvField(); + + public string EatTypeValue(string value, ref int i) => EatValue(value, ref i); + + public ReadOnlySpan EatTypeValue(ReadOnlySpan value, ref int i) => EatValue(value, ref i); + + public bool EatMapStartChar(string value, ref int i) => EatMapStartChar(value.AsSpan(), ref i); + + public bool EatMapStartChar(ReadOnlySpan value, ref int i) + { + var success = value[i] == JsWriter.MapStartChar; + if (success) i++; + return success; + } + + public string EatMapKey(string value, ref int i) => EatMapKey(value.AsSpan(), ref i).ToString(); + + public ReadOnlySpan EatMapKey(ReadOnlySpan value, ref int i) + { + var tokenStartPos = i; + + var valueLength = value.Length; + + var valueChar = value[tokenStartPos]; + + switch (valueChar) + { + case JsWriter.QuoteChar: + while (++i < valueLength) + { + valueChar = value[i]; + + if (valueChar != JsWriter.QuoteChar) continue; + + var isLiteralQuote = i + 1 < valueLength && value[i + 1] == JsWriter.QuoteChar; + + i++; //skip quote + if (!isLiteralQuote) + break; + } + return value.Slice(tokenStartPos, i - tokenStartPos); + + //Is Type/Map, i.e. {...} + case JsWriter.MapStartChar: + var endsToEat = 1; + var withinQuotes = false; + while (++i < valueLength && endsToEat > 0) + { + valueChar = value[i]; + + if (valueChar == JsWriter.QuoteChar) + withinQuotes = !withinQuotes; + + if (withinQuotes) + continue; + + if (valueChar == JsWriter.MapStartChar) + endsToEat++; + + if (valueChar == JsWriter.MapEndChar) + endsToEat--; + } + return value.Slice(tokenStartPos, i - tokenStartPos); + } + + while (value[++i] != JsWriter.MapKeySeperator) { } + return value.Slice(tokenStartPos, i - tokenStartPos); + } + + public bool EatMapKeySeperator(string value, ref int i) + { + return value[i++] == JsWriter.MapKeySeperator; + } + + public bool EatMapKeySeperator(ReadOnlySpan value, ref int i) + { + return value[i++] == JsWriter.MapKeySeperator; + } + + public bool EatItemSeperatorOrMapEndChar(string value, ref int i) + { + if (i == value.Length) return false; + + var success = value[i] == JsWriter.ItemSeperator + || value[i] == JsWriter.MapEndChar; + + if (success) + i++; + else if (Env.StrictMode) throw new Exception( + $"Expected '{JsWriter.ItemSeperator}' or '{JsWriter.MapEndChar}'"); + + return success; + } + + public bool EatItemSeperatorOrMapEndChar(ReadOnlySpan value, ref int i) + { + if (i == value.Length) return false; + + var success = value[i] == JsWriter.ItemSeperator + || value[i] == JsWriter.MapEndChar; + + if (success) + i++; + else if (Env.StrictMode) throw new Exception( + $"Expected '{JsWriter.ItemSeperator}' or '{JsWriter.MapEndChar}'"); + + return success; + } + + public void EatWhitespace(string value, ref int i) {} + + public void EatWhitespace(ReadOnlySpan value, ref int i) { } + + public string EatValue(string value, ref int i) + { + return EatValue(value.AsSpan(), ref i).ToString(); + } + + public ReadOnlySpan EatValue(ReadOnlySpan value, ref int i) + { + var tokenStartPos = i; + var valueLength = value.Length; + if (i == valueLength) return default; + + var valueChar = value[i]; + var withinQuotes = false; + var endsToEat = 1; + + switch (valueChar) + { + //If we are at the end, return. + case JsWriter.ItemSeperator: + case JsWriter.MapEndChar: + return default; + + //Is Within Quotes, i.e. "..." + case JsWriter.QuoteChar: + while (++i < valueLength) + { + valueChar = value[i]; + + if (valueChar != JsWriter.QuoteChar) continue; + + var isLiteralQuote = i + 1 < valueLength && value[i + 1] == JsWriter.QuoteChar; + + i++; //skip quote + if (!isLiteralQuote) + break; + } + return value.Slice(tokenStartPos, i - tokenStartPos); + + //Is Type/Map, i.e. {...} + case JsWriter.MapStartChar: + while (++i < valueLength && endsToEat > 0) + { + valueChar = value[i]; + + if (valueChar == JsWriter.QuoteChar) + withinQuotes = !withinQuotes; + + if (withinQuotes) + continue; + + if (valueChar == JsWriter.MapStartChar) + endsToEat++; + + if (valueChar == JsWriter.MapEndChar) + endsToEat--; + } + return value.Slice(tokenStartPos, i - tokenStartPos); + + //Is List, i.e. [...] + case JsWriter.ListStartChar: + while (++i < valueLength && endsToEat > 0) + { + valueChar = value[i]; + + if (valueChar == JsWriter.QuoteChar) + withinQuotes = !withinQuotes; + + if (withinQuotes) + continue; + + if (valueChar == JsWriter.ListStartChar) + endsToEat++; + + if (valueChar == JsWriter.ListEndChar) + endsToEat--; + } + return value.Slice(tokenStartPos, i - tokenStartPos); + } + + //Is Value + while (++i < valueLength) + { + valueChar = value[i]; + + if (valueChar == JsWriter.ItemSeperator + || valueChar == JsWriter.MapEndChar) + { + break; + } + } + + return value.Slice(tokenStartPos, i - tokenStartPos); } - - public string EatValue(string value, ref int i) - { - var tokenStartPos = i; - var valueLength = value.Length; - if (i == valueLength) return null; - - var valueChar = value[i]; - var withinQuotes = false; - var endsToEat = 1; - - switch (valueChar) - { - //If we are at the end, return. - case JsWriter.ItemSeperator: - case JsWriter.MapEndChar: - return null; - - //Is Within Quotes, i.e. "..." - case JsWriter.QuoteChar: - while (++i < valueLength) - { - valueChar = value[i]; - - if (valueChar != JsWriter.QuoteChar) continue; - - var isLiteralQuote = i + 1 < valueLength && value[i + 1] == JsWriter.QuoteChar; - - i++; //skip quote - if (!isLiteralQuote) - break; - } - return value.Substring(tokenStartPos, i - tokenStartPos); - - //Is Type/Map, i.e. {...} - case JsWriter.MapStartChar: - while (++i < valueLength && endsToEat > 0) - { - valueChar = value[i]; - - if (valueChar == JsWriter.QuoteChar) - withinQuotes = !withinQuotes; - - if (withinQuotes) - continue; - - if (valueChar == JsWriter.MapStartChar) - endsToEat++; - - if (valueChar == JsWriter.MapEndChar) - endsToEat--; - } - return value.Substring(tokenStartPos, i - tokenStartPos); - - //Is List, i.e. [...] - case JsWriter.ListStartChar: - while (++i < valueLength && endsToEat > 0) - { - valueChar = value[i]; - - if (valueChar == JsWriter.QuoteChar) - withinQuotes = !withinQuotes; - - if (withinQuotes) - continue; - - if (valueChar == JsWriter.ListStartChar) - endsToEat++; - - if (valueChar == JsWriter.ListEndChar) - endsToEat--; - } - return value.Substring(tokenStartPos, i - tokenStartPos); - } - - //Is Value - while (++i < valueLength) - { - valueChar = value[i]; - - if (valueChar == JsWriter.ItemSeperator - || valueChar == JsWriter.MapEndChar) - { - break; - } - } - - return value.Substring(tokenStartPos, i - tokenStartPos); - } - } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Jsv/JsvWriter.Generic.cs b/src/ServiceStack.Text/Jsv/JsvWriter.Generic.cs index b44004047..7cb2cce8a 100644 --- a/src/ServiceStack.Text/Jsv/JsvWriter.Generic.cs +++ b/src/ServiceStack.Text/Jsv/JsvWriter.Generic.cs @@ -1,40 +1,45 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; using System.Collections.Generic; using System.IO; -using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; using ServiceStack.Text.Common; namespace ServiceStack.Text.Jsv { - internal static class JsvWriter - { - public static readonly JsWriter Instance = new JsWriter(); + public static class JsvWriter + { + public static readonly JsWriter Instance = new JsWriter(); private static Dictionary WriteFnCache = new Dictionary(); - public static WriteObjectDelegate GetWriteFn(Type type) - { - try - { - WriteObjectDelegate writeFn; - if (WriteFnCache.TryGetValue(type, out writeFn)) return writeFn; + internal static void RemoveCacheFn(Type forType) + { + Dictionary snapshot, newCache; + do + { + snapshot = WriteFnCache; + newCache = new Dictionary(WriteFnCache); + newCache.Remove(forType); + + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref WriteFnCache, newCache, snapshot), snapshot)); + } + + public static WriteObjectDelegate GetWriteFn(Type type) + { + try + { + if (WriteFnCache.TryGetValue(type, out var writeFn)) + return writeFn; var genericType = typeof(JsvWriter<>).MakeGenericType(type); - var mi = genericType.GetMethod("WriteFn", BindingFlags.Public | BindingFlags.Static); - var writeFactoryFn = (Func)Delegate.CreateDelegate(typeof(Func), mi); + var mi = genericType.GetStaticMethod("WriteFn"); + var writeFactoryFn = (Func)mi.MakeDelegate(typeof(Func)); + writeFn = writeFactoryFn(); Dictionary snapshot, newCache; @@ -48,61 +53,121 @@ public static WriteObjectDelegate GetWriteFn(Type type) Interlocked.CompareExchange(ref WriteFnCache, newCache, snapshot), snapshot)); return writeFn; - } - catch (Exception ex) - { - Tracer.Instance.WriteError(ex); - throw; - } - } - - public static void WriteLateBoundObject(TextWriter writer, object value) - { - if (value == null) return; - var type = value.GetType(); - var writeFn = type == typeof(object) - ? WriteType.WriteObjectType - : GetWriteFn(type); - - var prevState = JsState.IsWritingDynamic; - JsState.IsWritingDynamic = true; - writeFn(writer, value); - JsState.IsWritingDynamic = prevState; - } - - public static WriteObjectDelegate GetValueTypeToStringMethod(Type type) - { - return Instance.GetValueTypeToStringMethod(type); - } - } - - /// - /// Implement the serializer using a more static approach - /// - /// - internal static class JsvWriter - { - private static readonly WriteObjectDelegate CacheFn; - - public static WriteObjectDelegate WriteFn() - { - return CacheFn ?? WriteObject; - } - - static JsvWriter() - { - CacheFn = typeof(T) == typeof(object) - ? JsvWriter.WriteLateBoundObject + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + throw; + } + } + + public static void WriteLateBoundObject(TextWriter writer, object value) + { + if (value == null) + return; + + try + { + if (!JsState.Traverse(value)) + return; + + var type = value.GetType(); + var writeFn = type == typeof(object) + ? WriteType.WriteObjectType + : GetWriteFn(type); + + var prevState = JsState.IsWritingDynamic; + JsState.IsWritingDynamic = true; + writeFn(writer, value); + JsState.IsWritingDynamic = prevState; + } + finally + { + JsState.UnTraverse(); + } + } + + public static WriteObjectDelegate GetValueTypeToStringMethod(Type type) + { + return Instance.GetValueTypeToStringMethod(type); + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + Text.Jsv.JsvWriter.WriteFn(); + Text.Jsv.JsvWriter.Instance.GetWriteFn(); + Text.Jsv.JsvWriter.Instance.GetValueTypeToStringMethod(typeof(T)); + JsWriter.GetTypeSerializer().GetWriteFn(); + } + } + + /// + /// Implement the serializer using a more static approach + /// + /// + public static class JsvWriter + { + private static WriteObjectDelegate CacheFn; + + public static void Reset() + { + JsvWriter.RemoveCacheFn(typeof(T)); + Refresh(); + } + + public static void Refresh() + { + if (JsvWriter.Instance == null) + return; + + CacheFn = typeof(T) == typeof(object) + ? JsvWriter.WriteLateBoundObject + : JsvWriter.Instance.GetWriteFn(); + JsConfig.AddUniqueType(typeof(T)); + } + + public static WriteObjectDelegate WriteFn() + { + return CacheFn ?? WriteObject; + } + + static JsvWriter() + { + CacheFn = typeof(T) == typeof(object) + ? JsvWriter.WriteLateBoundObject : JsvWriter.Instance.GetWriteFn(); - } + } + + public static void WriteObject(TextWriter writer, object value) + { + if (writer == null) return; //AOT + + TypeConfig.Init(); + + try + { + if (!JsState.Traverse(value)) + return; + + CacheFn(writer, value); + } + finally + { + JsState.UnTraverse(); + } + } + + public static void WriteRootObject(TextWriter writer, object value) + { + if (writer == null) return; //AOT + + TypeConfig.Init(); + TypeSerializer.OnSerialize?.Invoke(value); - public static void WriteObject(TextWriter writer, object value) - { -#if MONOTOUCH - if (writer == null) return; -#endif - CacheFn(writer, value); - } + JsState.Depth = 0; + CacheFn(writer, value); + } - } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/JsvFormatter.cs b/src/ServiceStack.Text/JsvFormatter.cs index f0debacca..7cad0db93 100644 --- a/src/ServiceStack.Text/JsvFormatter.cs +++ b/src/ServiceStack.Text/JsvFormatter.cs @@ -6,9 +6,9 @@ // Peter Townsend (townsend.pete@gmail.com) // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; @@ -18,78 +18,86 @@ namespace ServiceStack.Text { - public static class JsvFormatter - { - public static string Format(string serializedText) - { - if (string.IsNullOrEmpty(serializedText)) return null; + public static class JsvFormatter + { + public static string Format(string serializedText) + { + if (string.IsNullOrEmpty(serializedText)) return null; - var tabCount = 0; - var sb = new StringBuilder(); - var firstKeySeparator = true; + var tabCount = 0; + var sb = StringBuilderThreadStatic.Allocate(); + var firstKeySeparator = true; + var inString = false; - for (var i = 0; i < serializedText.Length; i++) - { - var current = serializedText[i]; - var previous = i - 1 >= 0 ? serializedText[i - 1] : 0; - var next = i < serializedText.Length - 1 ? serializedText[i + 1] : 0; + for (var i = 0; i < serializedText.Length; i++) + { + var current = serializedText[i]; + var previous = i - 1 >= 0 ? serializedText[i - 1] : 0; + var next = i < serializedText.Length - 1 ? serializedText[i + 1] : 0; - if (current == JsWriter.MapStartChar || current == JsWriter.ListStartChar) - { - if (previous == JsWriter.MapKeySeperator) - { - if (next == JsWriter.MapEndChar || next == JsWriter.ListEndChar) - { - sb.Append(current); - sb.Append(serializedText[++i]); //eat next - continue; - } + if (current == JsWriter.MapStartChar || current == JsWriter.ListStartChar) + { + if (previous == JsWriter.MapKeySeperator) + { + if (next == JsWriter.MapEndChar || next == JsWriter.ListEndChar) + { + sb.Append(current); + sb.Append(serializedText[++i]); //eat next + continue; + } - AppendTabLine(sb, tabCount); - } + AppendTabLine(sb, tabCount); + } - sb.Append(current); - AppendTabLine(sb, ++tabCount); - firstKeySeparator = true; - continue; - } + sb.Append(current); + AppendTabLine(sb, ++tabCount); + firstKeySeparator = true; + continue; + } - if (current == JsWriter.MapEndChar || current == JsWriter.ListEndChar) - { - AppendTabLine(sb, --tabCount); - sb.Append(current); - firstKeySeparator = true; - continue; - } + if (current == JsWriter.MapEndChar || current == JsWriter.ListEndChar) + { + AppendTabLine(sb, --tabCount); + sb.Append(current); + firstKeySeparator = true; + continue; + } - if (current == JsWriter.ItemSeperator) - { - sb.Append(current); - AppendTabLine(sb, tabCount); - firstKeySeparator = true; - continue; - } + if (current == JsWriter.QuoteChar) + { + sb.Append(current); + inString = !inString; + continue; + } - sb.Append(current); + if (current == JsWriter.ItemSeperator && !inString) + { + sb.Append(current); + AppendTabLine(sb, tabCount); + firstKeySeparator = true; + continue; + } - if (current == JsWriter.MapKeySeperator && firstKeySeparator) - { - sb.Append(" "); - firstKeySeparator = false; - } - } + sb.Append(current); - return sb.ToString(); - } + if (current == JsWriter.MapKeySeperator && firstKeySeparator) + { + sb.Append(" "); + firstKeySeparator = false; + } + } - private static void AppendTabLine(StringBuilder sb, int tabCount) - { - sb.AppendLine(); + return StringBuilderThreadStatic.ReturnAndFree(sb); + } - if (tabCount > 0) - { - sb.Append(new string('\t', tabCount)); - } - } - } + private static void AppendTabLine(StringBuilder sb, int tabCount) + { + sb.AppendLine(); + + if (tabCount > 0) + { + sb.Append(new string('\t', tabCount)); + } + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/LicenseUtils.cs b/src/ServiceStack.Text/LicenseUtils.cs new file mode 100644 index 000000000..2fb47aec7 --- /dev/null +++ b/src/ServiceStack.Text/LicenseUtils.cs @@ -0,0 +1,828 @@ +// Copyright (c) ServiceStack, Inc. All Rights Reserved. +// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using ServiceStack.Text; +using ServiceStack.Text.Common; + +namespace ServiceStack +{ + public class LicenseException : Exception + { + public LicenseException(string message) : base(message) { } + public LicenseException(string message, Exception innerException) : base(message, innerException) {} + } + + public enum LicenseType + { + Free, + FreeIndividual, + FreeOpenSource, + Indie, + Business, + Enterprise, + TextIndie, + TextBusiness, + OrmLiteIndie, + OrmLiteBusiness, + RedisIndie, + RedisBusiness, + AwsIndie, + AwsBusiness, + Trial, + Site, + TextSite, + RedisSite, + OrmLiteSite, + } + + [Flags] + public enum LicenseFeature : long + { + None = 0, + All = Premium | Text | Client | Common | Redis | OrmLite | ServiceStack | Server | Razor | Admin | Aws, + RedisSku = Redis | Text, + OrmLiteSku = OrmLite | Text, + AwsSku = Aws | Text, + Free = None, + Premium = 1 << 0, + Text = 1 << 1, + Client = 1 << 2, + Common = 1 << 3, + Redis = 1 << 4, + OrmLite = 1 << 5, + ServiceStack = 1 << 6, + Server = 1 << 7, + Razor = 1 << 8, + Admin = 1 << 9, + Aws = 1 << 10, + } + + [Flags] + public enum LicenseMeta : long + { + None = 0, + Subscription = 1 << 0, + Cores = 1 << 1, + } + + public enum QuotaType + { + Operations, //ServiceStack + Types, //Text, Redis + Fields, //ServiceStack, Text, Redis, OrmLite + RequestsPerHour, //Redis + Tables, //OrmLite, Aws + PremiumFeature, //AdminUI, Advanced Redis APIs, etc + } + + /// + /// Public Code API to register commercial license for ServiceStack. + /// + public static class Licensing + { + public static void RegisterLicense(string licenseKeyText) + { + LicenseUtils.RegisterLicense(licenseKeyText); + } + + public static void RegisterLicenseFromFile(string filePath) + { + if (!filePath.FileExists()) + throw new LicenseException("License file does not exist: " + filePath).Trace(); + + var licenseKeyText = filePath.ReadAllText(); + LicenseUtils.RegisterLicense(licenseKeyText); + } + + public static void RegisterLicenseFromFileIfExists(string filePath) + { + if (!filePath.FileExists()) + return; + + var licenseKeyText = filePath.ReadAllText(); + LicenseUtils.RegisterLicense(licenseKeyText); + } + } + + public class LicenseKey + { + public string Ref { get; set; } + public string Name { get; set; } + public LicenseType Type { get; set; } + public long Meta { get; set; } + public string Hash { get; set; } + public DateTime Expiry { get; set; } + } + + /// + /// Internal Utilities to verify licensing + /// + public static class LicenseUtils + { + public const string RuntimePublicKey = "nkqwkUAcuIlVzzOPENcQ+g5ALCe4LyzzWv59E4a7LuOM1Nb+hlNlnx2oBinIkvh09EyaxIX2PmaY0KtyDRIh+PoItkKeJe/TydIbK/bLa0+0Axuwa0MFShE6HdJo/dynpODm64+Sg1XfhICyfsBBSxuJMiVKjlMDIxu9kDg7vEs=AQAB"; + public const string LicensePublicKey = "w2fTTfr2SrGCclwLUkrbH0XsIUpZDJ1Kei2YUwYGmIn5AUyCPLTUv3obDBUBFJKLQ61Khs7dDkXlzuJr5tkGQ0zS0PYsmBPAtszuTum+FAYRH4Wdhmlfqu1Z03gkCIo1i11TmamN5432uswwFCVH60JU3CpaN97Ehru39LA1X9E=AQAB"; + + private const string ContactDetails = " Please see servicestack.net or contact team@servicestack.net for more details."; + + static LicenseUtils() + { + PclExport.Instance.RegisterLicenseFromConfig(); + } + + public static bool HasInit { get; private set; } + public static void Init() + { + HasInit = true; //Dummy method to init static constructor + } + + public static class ErrorMessages + { + private const string UpgradeInstructions = " Please see https://servicestack.net to upgrade to a commercial license or visit https://github.com/ServiceStackV3/ServiceStackV3 to revert back to the free ServiceStack v3."; + internal const string ExceededRedisTypes = "The free-quota limit on '{0} Redis Types' has been reached." + UpgradeInstructions; + internal const string ExceededRedisRequests = "The free-quota limit on '{0} Redis requests per hour' has been reached." + UpgradeInstructions; + internal const string ExceededOrmLiteTables = "The free-quota limit on '{0} OrmLite Tables' has been reached." + UpgradeInstructions; + internal const string ExceededAwsTables = "The free-quota limit on '{0} AWS Tables' has been reached." + UpgradeInstructions; + internal const string ExceededServiceStackOperations = "The free-quota limit on '{0} ServiceStack Operations' has been reached." + UpgradeInstructions; + internal const string ExceededAdminUi = "The Admin UI is a commercial-only premium feature." + UpgradeInstructions; + internal const string ExceededPremiumFeature = "Unauthorized use of a commercial-only premium feature." + UpgradeInstructions; + public const string UnauthorizedAccessRequest = "Unauthorized access request of a licensed feature."; + } + + public static class FreeQuotas + { + public const int ServiceStackOperations = 10; + public const int TypeFields = 20; + public const int RedisTypes = 20; + public const int RedisRequestPerHour = 6000; + public const int OrmLiteTables = 10; + public const int AwsTables = 10; + public const int PremiumFeature = 0; + } + + public static void AssertEvaluationLicense() + { + if (DateTime.UtcNow > new DateTime(2013, 12, 31)) + throw new LicenseException("The evaluation license for this software has expired. " + + "See https://servicestack.net to upgrade to a valid license.").Trace(); + } + + private static readonly int[] revokedSubs = { 4018, 4019, 4041, 4331, 4581 }; + + private class __ActivatedLicense + { + internal readonly LicenseKey LicenseKey; + internal __ActivatedLicense(LicenseKey licenseKey) => LicenseKey = licenseKey; + } + + public static string LicenseWarningMessage { get; private set; } + + private static string GetLicenseWarningMessage() + { + var key = __activatedLicense?.LicenseKey; + if (key == null) + return null; + + if (DateTime.UtcNow > key.Expiry) + { + var licenseMeta = key.Meta; + if ((licenseMeta & (long)LicenseMeta.Subscription) != 0) + return $"This Annual Subscription expired on '{key.Expiry:d}', please update your License Key with this years subscription."; + } + + return null; + } + + private static __ActivatedLicense __activatedLicense; + public static void RegisterLicense(string licenseKeyText) + { + JsConfig.InitStatics(); + + if (__activatedLicense != null) //Skip multiple license registrations. Use RemoveLicense() to reset. + return; + + string subId = null; + var hold = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + try + { + if (IsFreeLicenseKey(licenseKeyText)) + { + ValidateFreeLicenseKey(licenseKeyText); + return; + } + + var parts = licenseKeyText.SplitOnFirst('-'); + subId = parts[0]; + + if (int.TryParse(subId, out var subIdInt) && revokedSubs.Contains(subIdInt)) + throw new LicenseException("This subscription has been revoked. " + ContactDetails); + + var key = VerifyLicenseKeyText(licenseKeyText); + ValidateLicenseKey(key); + } + catch (PlatformNotSupportedException) + { + // Allow usage in environments like dotnet script + __activatedLicense = new __ActivatedLicense(new LicenseKey { Type = LicenseType.Indie }); + } + catch (Exception ex) + { + //bubble unrelated project Exceptions + if (ex is FileNotFoundException || ex is FileLoadException || ex is BadImageFormatException || ex is NotSupportedException) + throw; + + if (ex is LicenseException) + throw; + + var msg = "This license is invalid." + ContactDetails; + if (!string.IsNullOrEmpty(subId)) + msg += $" The id for this license is '{subId}'"; + + lock (typeof(LicenseUtils)) + { + try + { + var key = PclExport.Instance.VerifyLicenseKeyTextFallback(licenseKeyText); + ValidateLicenseKey(key); + } + catch (Exception exFallback) + { + if (exFallback is FileNotFoundException || exFallback is FileLoadException || exFallback is BadImageFormatException) + throw; + + throw new LicenseException(msg, exFallback).Trace(); + } + } + + throw new LicenseException(msg, ex).Trace(); + } + finally + { + Thread.CurrentThread.CurrentCulture = hold; + } + } + + private static void ValidateLicenseKey(LicenseKey key) + { + var releaseDate = Env.GetReleaseDate(); + if (releaseDate > key.Expiry) + throw new LicenseException($"This license has expired on {key.Expiry:d} and is not valid for use with this release." + + ContactDetails).Trace(); + + if (key.Type == LicenseType.Trial && DateTime.UtcNow > key.Expiry) + throw new LicenseException($"This trial license has expired on {key.Expiry:d}." + ContactDetails).Trace(); + + __activatedLicense = new __ActivatedLicense(key); + + LicenseWarningMessage = GetLicenseWarningMessage(); + if (LicenseWarningMessage != null) + Console.WriteLine(LicenseWarningMessage); + } + + private const string IndividualPrefix = "Individual (c) "; + private const string OpenSourcePrefix = "OSS "; + + private static bool IsFreeLicenseKey(string licenseText) => + licenseText.StartsWith(IndividualPrefix) || licenseText.StartsWith(OpenSourcePrefix); + + private static void ValidateFreeLicenseKey(string licenseText) + { + if (!IsFreeLicenseKey(licenseText)) + throw new NotSupportedException("Not a free License Key"); + + var envKey = Environment.GetEnvironmentVariable("SERVICESTACK_LICENSE"); + if (envKey == licenseText) + throw new LicenseException("Cannot use SERVICESTACK_LICENSE Environment variable with free License Keys, " + + "please use Licensing.RegisterLicense() in source code."); + + LicenseKey key = null; + if (licenseText.StartsWith(IndividualPrefix)) + { + key = VerifyIndividualLicense(licenseText); + if (key == null) + throw new LicenseException("Individual License Key is invalid."); + } + else if (licenseText.StartsWith(OpenSourcePrefix)) + { + key = VerifyOpenSourceLicense(licenseText); + if (key == null) + throw new LicenseException("Open Source License Key is invalid."); + } + else throw new NotSupportedException("Not a free License Key"); + + var releaseDate = Env.GetReleaseDate(); + if (releaseDate > key.Expiry) + throw new LicenseException($"This license has expired on {key.Expiry:d} and is not valid for use with this release.\n" + + "Check https://servicestack.net/free for eligible renewals.").Trace(); + + __activatedLicense = new __ActivatedLicense(key); + } + + internal static string Info => __activatedLicense?.LicenseKey == null + ? "NO" + : __activatedLicense.LicenseKey.Type switch { + LicenseType.Free => "FR", + LicenseType.FreeIndividual => "FI", + LicenseType.FreeOpenSource => "FO", + LicenseType.Indie => "IN", + LicenseType.Business => "BU", + LicenseType.Enterprise => "EN", + LicenseType.TextIndie => "TI", + LicenseType.TextBusiness => "TB", + LicenseType.OrmLiteIndie => "OI", + LicenseType.OrmLiteBusiness => "OB", + LicenseType.RedisIndie => "RI", + LicenseType.RedisBusiness => "RB", + LicenseType.AwsIndie => "AI", + LicenseType.AwsBusiness => "AB", + LicenseType.Trial => "TR", + LicenseType.Site => "SI", + LicenseType.TextSite => "TS", + LicenseType.RedisSite => "RS", + LicenseType.OrmLiteSite => "OS", + _ => "UN", + }; + + private static LicenseKey VerifyIndividualLicense(string licenseKey) + { + if (licenseKey == null) + return null; + if (licenseKey.Length < 100) + return null; + if (!licenseKey.StartsWith(IndividualPrefix)) + return null; + var keyText = licenseKey.LastLeftPart(' '); + var keySign = licenseKey.LastRightPart(' '); + if (keySign.Length < 48) + return null; + + try + { + var rsa = System.Security.Cryptography.RSA.Create(); + rsa.FromXml(LicensePublicKey); + +#if !NETCORE + var verified = ((System.Security.Cryptography.RSACryptoServiceProvider)rsa) + .VerifyData(keyText.ToUtf8Bytes(), "SHA256", Convert.FromBase64String(keySign)); +#else + var verified = rsa.VerifyData(keyText.ToUtf8Bytes(), + Convert.FromBase64String(keySign), + System.Security.Cryptography.HashAlgorithmName.SHA256, + System.Security.Cryptography.RSASignaturePadding.Pkcs1); +#endif + if (verified) + { + var yearStr = keyText.Substring(IndividualPrefix.Length).LeftPart(' '); + if (yearStr.Length == 4 && int.TryParse(yearStr, out var year)) + { + return new LicenseKey { + Expiry = new DateTime(year + 1, 1, 1, 0, 0, 0, DateTimeKind.Utc), + Hash = keySign, + Name = keyText, + Type = LicenseType.FreeIndividual, + }; + } + } + } + catch { } + + return null; + } + + private static LicenseKey VerifyOpenSourceLicense(string licenseKey) + { + if (licenseKey == null) + return null; + if (licenseKey.Length < 100) + return null; + if (!licenseKey.StartsWith(OpenSourcePrefix)) + return null; + var keyText = licenseKey.LastLeftPart(' '); + var keySign = licenseKey.LastRightPart(' '); + if (keySign.Length < 48) + return null; + + try + { + var rsa = System.Security.Cryptography.RSA.Create(); + rsa.FromXml(LicensePublicKey); + +#if !NETCORE + var verified = ((System.Security.Cryptography.RSACryptoServiceProvider)rsa) + .VerifyData(keyText.ToUtf8Bytes(), "SHA256", Convert.FromBase64String(keySign)); +#else + var verified = rsa.VerifyData(keyText.ToUtf8Bytes(), + Convert.FromBase64String(keySign), + System.Security.Cryptography.HashAlgorithmName.SHA256, + System.Security.Cryptography.RSASignaturePadding.Pkcs1); +#endif + if (verified) + { + var yearStr = keyText.Substring(OpenSourcePrefix.Length).RightPart(' ').LeftPart(' '); + if (yearStr.Length == 4 && int.TryParse(yearStr, out var year)) + { + return new LicenseKey { + Expiry = new DateTime(year + 1, 1, 1, 0, 0, 0, DateTimeKind.Utc), + Hash = keySign, + Name = keyText, + Type = LicenseType.FreeOpenSource, + }; + } + } + } + catch { } + + return null; + } + + public static void RemoveLicense() + { + __activatedLicense = null; + } + + public static LicenseFeature ActivatedLicenseFeatures() + { + return __activatedLicense?.LicenseKey.GetLicensedFeatures() ?? LicenseFeature.None; + } + + public static void ApprovedUsage(LicenseFeature licenseFeature, LicenseFeature requestedFeature, + int allowedUsage, int actualUsage, string message) + { + var hasFeature = (requestedFeature & licenseFeature) == requestedFeature; + if (hasFeature) + return; + + if (actualUsage > allowedUsage) + throw new LicenseException(message.Fmt(allowedUsage)).Trace(); + } + + public static bool HasLicensedFeature(LicenseFeature feature) + { + var licensedFeatures = ActivatedLicenseFeatures(); + return (feature & licensedFeatures) == feature; + } + + public static void AssertValidUsage(LicenseFeature feature, QuotaType quotaType, int count) + { + var licensedFeatures = ActivatedLicenseFeatures(); + if ((LicenseFeature.All & licensedFeatures) == LicenseFeature.All) //Standard Usage + return; + + //Free Quotas + switch (feature) + { + case LicenseFeature.Redis: + switch (quotaType) + { + case QuotaType.Types: + ApprovedUsage(licensedFeatures, feature, FreeQuotas.RedisTypes, count, ErrorMessages.ExceededRedisTypes); + return; + case QuotaType.RequestsPerHour: + ApprovedUsage(licensedFeatures, feature, FreeQuotas.RedisRequestPerHour, count, ErrorMessages.ExceededRedisRequests); + return; + } + break; + + case LicenseFeature.OrmLite: + switch (quotaType) + { + case QuotaType.Tables: + ApprovedUsage(licensedFeatures, feature, FreeQuotas.OrmLiteTables, count, ErrorMessages.ExceededOrmLiteTables); + return; + } + break; + + case LicenseFeature.Aws: + switch (quotaType) + { + case QuotaType.Tables: + ApprovedUsage(licensedFeatures, feature, FreeQuotas.AwsTables, count, ErrorMessages.ExceededAwsTables); + return; + } + break; + + case LicenseFeature.ServiceStack: + switch (quotaType) + { + case QuotaType.Operations: + ApprovedUsage(licensedFeatures, feature, FreeQuotas.ServiceStackOperations, count, ErrorMessages.ExceededServiceStackOperations); + return; + } + break; + + case LicenseFeature.Admin: + switch (quotaType) + { + case QuotaType.PremiumFeature: + ApprovedUsage(licensedFeatures, feature, FreeQuotas.PremiumFeature, count, ErrorMessages.ExceededAdminUi); + return; + } + break; + + case LicenseFeature.Premium: + switch (quotaType) + { + case QuotaType.PremiumFeature: + ApprovedUsage(licensedFeatures, feature, FreeQuotas.PremiumFeature, count, ErrorMessages.ExceededPremiumFeature); + return; + } + break; + } + + throw new LicenseException("Unknown Quota Usage: {0}, {1}".Fmt(feature, quotaType)).Trace(); + } + + public static LicenseFeature GetLicensedFeatures(this LicenseKey key) + { + switch (key.Type) + { + case LicenseType.Free: + return LicenseFeature.Free; + + case LicenseType.FreeIndividual: + case LicenseType.FreeOpenSource: + case LicenseType.Indie: + case LicenseType.Business: + case LicenseType.Enterprise: + case LicenseType.Trial: + case LicenseType.Site: + return LicenseFeature.All; + + case LicenseType.TextIndie: + case LicenseType.TextBusiness: + case LicenseType.TextSite: + return LicenseFeature.Text; + + case LicenseType.OrmLiteIndie: + case LicenseType.OrmLiteBusiness: + case LicenseType.OrmLiteSite: + return LicenseFeature.OrmLiteSku; + + case LicenseType.AwsIndie: + case LicenseType.AwsBusiness: + return LicenseFeature.AwsSku; + + case LicenseType.RedisIndie: + case LicenseType.RedisBusiness: + case LicenseType.RedisSite: + return LicenseFeature.RedisSku; + } + throw new ArgumentException("Unknown License Type: " + key.Type).Trace(); + } + + public static LicenseKey ToLicenseKey(this string licenseKeyText) + { + licenseKeyText = Regex.Replace(licenseKeyText, @"\s+", ""); + var parts = licenseKeyText.SplitOnFirst('-'); + var refId = parts[0]; + var base64 = parts[1]; + var jsv = Convert.FromBase64String(base64).FromUtf8Bytes(); + + var hold = JsConfig.DeSerializeFn; + var holdRaw = JsConfig.RawDeserializeFn; + + try + { + JsConfig.DeSerializeFn = null; + JsConfig.RawDeserializeFn = null; + + var key = jsv.FromJsv(); + + if (key.Ref != refId) + throw new LicenseException("The license '{0}' is not assigned to CustomerId '{1}'.".Fmt(base64, refId)).Trace(); + + return key; + } + finally + { + JsConfig.DeSerializeFn = hold; + JsConfig.RawDeserializeFn = holdRaw; + } + } + + public static LicenseKey ToLicenseKeyFallback(this string licenseKeyText) + { + licenseKeyText = Regex.Replace(licenseKeyText, @"\s+", ""); + var parts = licenseKeyText.SplitOnFirst('-'); + var refId = parts[0]; + var base64 = parts[1]; + var jsv = Convert.FromBase64String(base64).FromUtf8Bytes(); + + var map = jsv.FromJsv>(); + var key = new LicenseKey + { + Ref = map.Get("Ref"), + Name = map.Get("Name"), + Type = (LicenseType)Enum.Parse(typeof(LicenseType), map.Get("Type"), ignoreCase: true), + Hash = map.Get("Hash"), + Expiry = DateTimeSerializer.ParseManual(map.Get("Expiry"), DateTimeKind.Utc).GetValueOrDefault(), + }; + + if (key.Ref != refId) + throw new LicenseException($"The license '{base64}' is not assigned to CustomerId '{refId}'.").Trace(); + + return key; + } + + public static string GetHashKeyToSign(this LicenseKey key) + { + return $"{key.Ref}:{key.Name}:{key.Expiry:yyyy-MM-dd}:{key.Type}"; + } + + public static Exception GetInnerMostException(this Exception ex) + { + //Extract true exception from static initializers (e.g. LicenseException) + while (ex.InnerException != null) + { + ex = ex.InnerException; + } + return ex; + } + + //License Utils + public static bool VerifySignedHash(byte[] DataToVerify, byte[] SignedData, System.Security.Cryptography.RSAParameters Key) + { + try + { + var RSAalg = new System.Security.Cryptography.RSACryptoServiceProvider(); + RSAalg.ImportParameters(Key); + return RSAalg.VerifySha1Data(DataToVerify, SignedData); + + } + catch (System.Security.Cryptography.CryptographicException ex) + { + Tracer.Instance.WriteError(ex); + return false; + } + } + + public static LicenseKey VerifyLicenseKeyText(string licenseKeyText) + { +#if NETFX || NETCORE + LicenseKey key; + try + { + if (!licenseKeyText.VerifyLicenseKeyText(out key)) + throw new ArgumentException("licenseKeyText"); + } + catch (Exception) + { + if (!VerifyLicenseKeyTextFallback(licenseKeyText, out key)) + throw; + } + return key; +#else + return licenseKeyText.ToLicenseKey(); +#endif + } + + private static void FromXml(this System.Security.Cryptography.RSA rsa, string xml) + { +#if NETFX + rsa.FromXmlString(xml); +#else + //throws PlatformNotSupportedException + var csp = ExtractFromXml(xml); + rsa.ImportParameters(csp); +#endif + } + +#if !NET45 + private static System.Security.Cryptography.RSAParameters ExtractFromXml(string xml) + { + var csp = new System.Security.Cryptography.RSAParameters(); + using (var reader = System.Xml.XmlReader.Create(new StringReader(xml))) + { + while (reader.Read()) + { + if (reader.NodeType != System.Xml.XmlNodeType.Element) + continue; + + var elName = reader.Name; + if (elName == "RSAKeyValue") + continue; + + do { + reader.Read(); + } while (reader.NodeType != System.Xml.XmlNodeType.Text && reader.NodeType != System.Xml.XmlNodeType.EndElement); + + if (reader.NodeType == System.Xml.XmlNodeType.EndElement) + continue; + + var value = reader.Value; + switch (elName) + { + case "Modulus": + csp.Modulus = Convert.FromBase64String(value); + break; + case "Exponent": + csp.Exponent = Convert.FromBase64String(value); + break; + case "P": + csp.P = Convert.FromBase64String(value); + break; + case "Q": + csp.Q = Convert.FromBase64String(value); + break; + case "DP": + csp.DP = Convert.FromBase64String(value); + break; + case "DQ": + csp.DQ = Convert.FromBase64String(value); + break; + case "InverseQ": + csp.InverseQ = Convert.FromBase64String(value); + break; + case "D": + csp.D = Convert.FromBase64String(value); + break; + } + } + + return csp; + } + } +#endif + + public static bool VerifyLicenseKeyText(this string licenseKeyText, out LicenseKey key) + { + var publicRsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(); + publicRsaProvider.FromXml(LicenseUtils.LicensePublicKey); + var publicKeyParams = publicRsaProvider.ExportParameters(false); + + key = licenseKeyText.ToLicenseKey(); + var originalData = key.GetHashKeyToSign().ToUtf8Bytes(); + var signedData = Convert.FromBase64String(key.Hash); + + return VerifySignedHash(originalData, signedData, publicKeyParams); + } + + public static bool VerifyLicenseKeyTextFallback(this string licenseKeyText, out LicenseKey key) + { + System.Security.Cryptography.RSAParameters publicKeyParams; + try + { + var publicRsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(); + publicRsaProvider.FromXml(LicenseUtils.LicensePublicKey); + publicKeyParams = publicRsaProvider.ExportParameters(false); + } + catch (Exception ex) + { + throw new Exception("Could not import LicensePublicKey", ex); + } + + try + { + key = licenseKeyText.ToLicenseKeyFallback(); + } + catch (Exception ex) + { + throw new Exception("Could not deserialize LicenseKeyText Manually", ex); + } + + byte[] originalData; + byte[] signedData; + + try + { + originalData = key.GetHashKeyToSign().ToUtf8Bytes(); + } + catch (Exception ex) + { + throw new Exception("Could not convert HashKey to UTF-8", ex); + } + + try + { + signedData = Convert.FromBase64String(key.Hash); + } + catch (Exception ex) + { + throw new Exception("Could not convert key.Hash from Base64", ex); + } + + try + { + return VerifySignedHash(originalData, signedData, publicKeyParams); + } + catch (Exception ex) + { + throw new Exception($"Could not Verify License Key ({originalData.Length}, {signedData.Length})", ex); + } + } + + public static bool VerifySha1Data(this System.Security.Cryptography.RSACryptoServiceProvider RSAalg, byte[] unsignedData, byte[] encryptedData) + { + using var sha = System.Security.Cryptography.SHA1.Create(); + return RSAalg.VerifyData(unsignedData, sha, encryptedData); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/ListExtensions.cs b/src/ServiceStack.Text/ListExtensions.cs index 26920f07a..5b690debf 100644 --- a/src/ServiceStack.Text/ListExtensions.cs +++ b/src/ServiceStack.Text/ListExtensions.cs @@ -1,62 +1,83 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; using System.Collections.Generic; using System.Linq; using System.Text; +using ServiceStack.Text; using ServiceStack.Text.Common; -namespace ServiceStack.Text +namespace ServiceStack { - public static class ListExtensions - { - public static string Join(this IEnumerable values) - { - return Join(values, JsWriter.ItemSeperatorString); - } - - public static string Join(this IEnumerable values, string seperator) - { - var sb = new StringBuilder(); - foreach (var value in values) - { - if (sb.Length > 0) - sb.Append(seperator); - sb.Append(value); - } - return sb.ToString(); - } - - public static bool IsNullOrEmpty(this List list) - { - return list == null || list.Count == 0; - } - - //TODO: make it work - public static IEnumerable SafeWhere(this List list, Func predicate) - { - return list.Where(predicate); - } - - public static int NullableCount(this List list) - { - return list == null ? 0 : list.Count; - } - - public static void AddIfNotExists(this List list, T item) - { - if (!list.Contains(item)) - list.Add(item); - } - } + public static class ListExtensions + { + public static string Join(this IEnumerable values) + { + return Join(values, JsWriter.ItemSeperatorString); + } + + public static string Join(this IEnumerable values, string seperator) + { + var sb = StringBuilderThreadStatic.Allocate(); + foreach (var value in values) + { + if (sb.Length > 0) + sb.Append(seperator); + sb.Append(value); + } + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + + public static bool IsNullOrEmpty(this List list) + { + return list == null || list.Count == 0; + } + + //TODO: make it work + public static IEnumerable SafeWhere(this List list, Func predicate) + { + return list.Where(predicate); + } + + public static int NullableCount(this List list) + { + return list == null ? 0 : list.Count; + } + + public static void AddIfNotExists(this List list, T item) + { + if (!list.Contains(item)) + list.Add(item); + } + + public static T[] NewArray(this T[] array, T with = null, T without = null) where T : class + { + var to = new List(array); + + if (with != null) + to.Add(with); + + if (without != null) + to.Remove(without); + + return to.ToArray(); + } + + public static List InList(this T value) + { + return new List { value }; + } + + public static T[] InArray(this T value) + { + return new[] { value }; + } + + public static List Add(this List types) + { + types.Add(typeof(T)); + return types; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/MapExtensions.cs b/src/ServiceStack.Text/MapExtensions.cs index 5ca0e2dc1..f39d7e8dc 100644 --- a/src/ServiceStack.Text/MapExtensions.cs +++ b/src/ServiceStack.Text/MapExtensions.cs @@ -5,35 +5,36 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System.Collections.Generic; using System.Text; +using ServiceStack.Text; using ServiceStack.Text.Common; -namespace ServiceStack.Text +namespace ServiceStack { - public static class MapExtensions - { - public static string Join(this Dictionary values) - { - return Join(values, JsWriter.ItemSeperatorString, JsWriter.MapKeySeperatorString); - } + public static class MapExtensions + { + public static string Join(this Dictionary values) + { + return Join(values, JsWriter.ItemSeperatorString, JsWriter.MapKeySeperatorString); + } - public static string Join(this Dictionary values, string itemSeperator, string keySeperator) - { - var sb = new StringBuilder(); - foreach (var entry in values) - { - if (sb.Length > 0) - sb.Append(itemSeperator); + public static string Join(this Dictionary values, string itemSeperator, string keySeperator) + { + var sb = StringBuilderThreadStatic.Allocate(); + foreach (var entry in values) + { + if (sb.Length > 0) + sb.Append(itemSeperator); - sb.Append(entry.Key).Append(keySeperator).Append(entry.Value); - } - return sb.ToString(); - } - } + sb.Append(entry.Key).Append(keySeperator).Append(entry.Value); + } + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Marc/ObjectAccessor.cs b/src/ServiceStack.Text/Marc/ObjectAccessor.cs deleted file mode 100644 index 2ed05053d..000000000 --- a/src/ServiceStack.Text/Marc/ObjectAccessor.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -//using System.Dynamic; - -//Not using it here, but @marcgravell's stuff is too good not to include -#if !SILVERLIGHT && !MONOTOUCH && !XBOX -namespace ServiceStack.Text.FastMember -{ - /// - /// Represents an individual object, allowing access to members by-name - /// - public abstract class ObjectAccessor - { - /// - /// Get or Set the value of a named member for the underlying object - /// - public abstract object this[string name] { get; set; } - /// - /// The object represented by this instance - /// - public abstract object Target { get; } - /// - /// Use the target types definition of equality - /// - public override bool Equals(object obj) - { - return Target.Equals(obj); - } - /// - /// Obtain the hash of the target object - /// - public override int GetHashCode() - { - return Target.GetHashCode(); - } - /// - /// Use the target's definition of a string representation - /// - public override string ToString() - { - return Target.ToString(); - } - - /// - /// Wraps an individual object, allowing by-name access to that instance - /// - public static ObjectAccessor Create(object target) - { - if (target == null) throw new ArgumentNullException("target"); - //IDynamicMetaObjectProvider dlr = target as IDynamicMetaObjectProvider; - //if (dlr != null) return new DynamicWrapper(dlr); // use the DLR - return new TypeAccessorWrapper(target, TypeAccessor.Create(target.GetType())); - } - - sealed class TypeAccessorWrapper : ObjectAccessor - { - private readonly object target; - private readonly TypeAccessor accessor; - public TypeAccessorWrapper(object target, TypeAccessor accessor) - { - this.target = target; - this.accessor = accessor; - } - public override object this[string name] - { - get { return accessor[target, name.ToUpperInvariant()]; } - set { accessor[target, name.ToUpperInvariant()] = value; } - } - public override object Target - { - get { return target; } - } - } - - //sealed class DynamicWrapper : ObjectAccessor - //{ - // private readonly IDynamicMetaObjectProvider target; - // public override object Target - // { - // get { return target; } - // } - // public DynamicWrapper(IDynamicMetaObjectProvider target) - // { - // this.target = target; - // } - // public override object this[string name] - // { - // get { return CallSiteCache.GetValue(name, target); } - // set { CallSiteCache.SetValue(name, target, value); } - // } - //} - } - -} - -#endif \ No newline at end of file diff --git a/src/ServiceStack.Text/Marc/TypeAccessor.cs b/src/ServiceStack.Text/Marc/TypeAccessor.cs deleted file mode 100644 index 403ee3920..000000000 --- a/src/ServiceStack.Text/Marc/TypeAccessor.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using System.Collections; -using System.Reflection; -using System.Reflection.Emit; -using System.Threading; -//using System.Dynamic; - -//Not using it here, but @marcgravell's stuff is too good not to include -// http://code.google.com/p/fast-member/ Apache License 2.0 -#if !SILVERLIGHT && !MONOTOUCH && !XBOX -namespace ServiceStack.Text.FastMember -{ - /// - /// Provides by-name member-access to objects of a given type - /// - public abstract class TypeAccessor - { - // hash-table has better read-without-locking semantics than dictionary - private static readonly Hashtable typeLookyp = new Hashtable(); - - /// - /// Does this type support new instances via a parameterless constructor? - /// - public virtual bool CreateNewSupported { get { return false; } } - /// - /// Create a new instance of this type - /// - public virtual object CreateNew() { throw new NotSupportedException();} - - /// - /// Provides a type-specific accessor, allowing by-name access for all objects of that type - /// - /// The accessor is cached internally; a pre-existing accessor may be returned - public static TypeAccessor Create(Type type) - { - if(type == null) throw new ArgumentNullException("type"); - TypeAccessor obj = (TypeAccessor)typeLookyp[type]; - if (obj != null) return obj; - - lock(typeLookyp) - { - // double-check - obj = (TypeAccessor)typeLookyp[type]; - if (obj != null) return obj; - - obj = CreateNew(type); - - typeLookyp[type] = obj; - return obj; - } - } - - //sealed class DynamicAccessor : TypeAccessor - //{ - // public static readonly DynamicAccessor Singleton = new DynamicAccessor(); - // private DynamicAccessor(){} - // public override object this[object target, string name] - // { - // get { return CallSiteCache.GetValue(name, target); } - // set { CallSiteCache.SetValue(name, target, value); } - // } - //} - - private static AssemblyBuilder assembly; - private static ModuleBuilder module; - private static int counter; - - private static void WriteGetter(ILGenerator il, Type type, PropertyInfo[] props, FieldInfo[] fields, bool isStatic) - { - LocalBuilder loc = type.IsValueType ? il.DeclareLocal(type) : null; - OpCode propName = isStatic ? OpCodes.Ldarg_1 : OpCodes.Ldarg_2, target = isStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1; - foreach (PropertyInfo prop in props) - { - if (prop.GetIndexParameters().Length != 0 || !prop.CanRead) continue; - - Label next = il.DefineLabel(); - il.Emit(propName); - il.Emit(OpCodes.Ldstr, prop.Name); - il.EmitCall(OpCodes.Call, strinqEquals, null); - il.Emit(OpCodes.Brfalse_S, next); - // match: - il.Emit(target); - Cast(il, type, loc); - il.EmitCall(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, prop.GetGetMethod(), null); - if (prop.PropertyType.IsValueType) - { - il.Emit(OpCodes.Box, prop.PropertyType); - } - il.Emit(OpCodes.Ret); - // not match: - il.MarkLabel(next); - } - foreach (FieldInfo field in fields) - { - Label next = il.DefineLabel(); - il.Emit(propName); - il.Emit(OpCodes.Ldstr, field.Name); - il.EmitCall(OpCodes.Call, strinqEquals, null); - il.Emit(OpCodes.Brfalse_S, next); - // match: - il.Emit(target); - Cast(il, type, loc); - il.Emit(OpCodes.Ldfld, field); - if (field.FieldType.IsValueType) - { - il.Emit(OpCodes.Box, field.FieldType); - } - il.Emit(OpCodes.Ret); - // not match: - il.MarkLabel(next); - } - il.Emit(OpCodes.Ldstr, "name"); - il.Emit(OpCodes.Newobj, typeof(ArgumentOutOfRangeException).GetConstructor(new Type[] { typeof(string) })); - il.Emit(OpCodes.Throw); - } - private static void WriteSetter(ILGenerator il, Type type, PropertyInfo[] props, FieldInfo[] fields, bool isStatic) - { - if (type.IsValueType) - { - il.Emit(OpCodes.Ldstr, "Write is not supported for structs"); - il.Emit(OpCodes.Newobj, typeof(NotSupportedException).GetConstructor(new Type[] { typeof(string) })); - il.Emit(OpCodes.Throw); - } - else - { - OpCode propName = isStatic ? OpCodes.Ldarg_1 : OpCodes.Ldarg_2, - target = isStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1, - value = isStatic ? OpCodes.Ldarg_2 : OpCodes.Ldarg_3; - LocalBuilder loc = type.IsValueType ? il.DeclareLocal(type) : null; - foreach (PropertyInfo prop in props) - { - if (prop.GetIndexParameters().Length != 0 || !prop.CanWrite) continue; - - Label next = il.DefineLabel(); - il.Emit(propName); - il.Emit(OpCodes.Ldstr, prop.Name); - il.EmitCall(OpCodes.Call, strinqEquals, null); - il.Emit(OpCodes.Brfalse_S, next); - // match: - il.Emit(target); - Cast(il, type, loc); - il.Emit(value); - Cast(il, prop.PropertyType, null); - il.EmitCall(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, prop.GetSetMethod(), null); - il.Emit(OpCodes.Ret); - // not match: - il.MarkLabel(next); - } - foreach (FieldInfo field in fields) - { - Label next = il.DefineLabel(); - il.Emit(propName); - il.Emit(OpCodes.Ldstr, field.Name); - il.EmitCall(OpCodes.Call, strinqEquals, null); - il.Emit(OpCodes.Brfalse_S, next); - // match: - il.Emit(target); - Cast(il, type, loc); - il.Emit(value); - Cast(il, field.FieldType, null); - il.Emit(OpCodes.Stfld, field); - il.Emit(OpCodes.Ret); - // not match: - il.MarkLabel(next); - } - il.Emit(OpCodes.Ldstr, "name"); - il.Emit(OpCodes.Newobj, typeof(ArgumentOutOfRangeException).GetConstructor(new Type[] { typeof(string) })); - il.Emit(OpCodes.Throw); - } - } - private static readonly MethodInfo strinqEquals = typeof(string).GetMethod("op_Equality", new Type[] { typeof(string), typeof(string) }); - - sealed class DelegateAccessor : TypeAccessor - { - private readonly Func getter; - private readonly Action setter; - private readonly Func ctor; - public DelegateAccessor(Func getter, Action setter, Func ctor) - { - this.getter = getter; - this.setter = setter; - this.ctor = ctor; - } - public override bool CreateNewSupported { get { return ctor != null; } } - public override object CreateNew() - { - return ctor != null ? ctor() : base.CreateNew(); - } - public override object this[object target, string name] - { - get { return getter(target, name); } - set { setter(target, name, value); } - } - } - private static bool IsFullyPublic(Type type) - { - while (type.IsNestedPublic) type = type.DeclaringType; - return type.IsPublic; - } - static TypeAccessor CreateNew(Type type) - { - //if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type)) - //{ - // return DynamicAccessor.Singleton; - //} - - PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); - FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); - ConstructorInfo ctor = null; - if(type.IsClass && !type.IsAbstract) - { - ctor = type.GetConstructor(Type.EmptyTypes); - } - ILGenerator il; - if(!IsFullyPublic(type)) - { - DynamicMethod dynGetter = new DynamicMethod(type.FullName + "_get", typeof(object), new Type[] { typeof(object), typeof(string) }, type, true), - dynSetter = new DynamicMethod(type.FullName + "_set", null, new Type[] { typeof(object), typeof(string), typeof(object) }, type, true); - WriteGetter(dynGetter.GetILGenerator(), type, props, fields, true); - WriteSetter(dynSetter.GetILGenerator(), type, props, fields, true); - DynamicMethod dynCtor = null; - if(ctor != null) - { - dynCtor = new DynamicMethod(type.FullName + "_ctor", typeof(object), Type.EmptyTypes, type, true); - il = dynCtor.GetILGenerator(); - il.Emit(OpCodes.Newobj, ctor); - il.Emit(OpCodes.Ret); - } - return new DelegateAccessor( - (Func)dynGetter.CreateDelegate(typeof(Func)), - (Action)dynSetter.CreateDelegate(typeof(Action)), - dynCtor == null ? null : (Func)dynCtor.CreateDelegate(typeof(Func))); - } - - // note this region is synchronized; only one is being created at a time so we don't need to stress about the builders - if(assembly == null) - { - AssemblyName name = new AssemblyName("FastMember_dynamic"); - assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); - module = assembly.DefineDynamicModule(name.Name); - } - TypeBuilder tb = module.DefineType("FastMember_dynamic." + type.Name + "_" + Interlocked.Increment(ref counter), - (typeof(TypeAccessor).Attributes | TypeAttributes.Sealed) & ~TypeAttributes.Abstract, typeof(TypeAccessor) ); - - tb.DefineDefaultConstructor(MethodAttributes.Public); - PropertyInfo indexer = typeof (TypeAccessor).GetProperty("Item"); - MethodInfo baseGetter = indexer.GetGetMethod(), baseSetter = indexer.GetSetMethod(); - MethodBuilder body = tb.DefineMethod(baseGetter.Name, baseGetter.Attributes & ~MethodAttributes.Abstract, typeof(object), new Type[] {typeof(object), typeof(string)}); - il = body.GetILGenerator(); - WriteGetter(il, type, props, fields, false); - tb.DefineMethodOverride(body, baseGetter); - - body = tb.DefineMethod(baseSetter.Name, baseSetter.Attributes & ~MethodAttributes.Abstract, null, new Type[] { typeof(object), typeof(string), typeof(object) }); - il = body.GetILGenerator(); - WriteSetter(il, type, props, fields, false); - tb.DefineMethodOverride(body, baseSetter); - - if(ctor != null) - { - MethodInfo baseMethod = typeof (TypeAccessor).GetProperty("CreateNewSupported").GetGetMethod(); - body = tb.DefineMethod(baseMethod.Name, baseMethod.Attributes, typeof (bool), Type.EmptyTypes); - il = body.GetILGenerator(); - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Ret); - tb.DefineMethodOverride(body, baseMethod); - - baseMethod = typeof (TypeAccessor).GetMethod("CreateNew"); - body = tb.DefineMethod(baseMethod.Name, baseMethod.Attributes, typeof (object), Type.EmptyTypes); - il = body.GetILGenerator(); - il.Emit(OpCodes.Newobj, ctor); - il.Emit(OpCodes.Ret); - tb.DefineMethodOverride(body, baseMethod); - } - - return (TypeAccessor)Activator.CreateInstance(tb.CreateType()); - } - - private static void Cast(ILGenerator il, Type type, LocalBuilder addr) - { - if(type == typeof(object)) {} - else if(type.IsValueType) - { - il.Emit(OpCodes.Unbox_Any, type); - if (addr != null) - { - il.Emit(OpCodes.Stloc, addr); - il.Emit(OpCodes.Ldloca_S, addr); - } - } - else - { - il.Emit(OpCodes.Castclass, type); - } - } - - /// - /// Get or set the value of a named member on the target instance - /// - public abstract object this[object target, string name] - { - get; set; - } - } -} - -#endif - - diff --git a/src/ServiceStack.Text/MemoryProvider.cs b/src/ServiceStack.Text/MemoryProvider.cs new file mode 100644 index 000000000..4876ebc20 --- /dev/null +++ b/src/ServiceStack.Text/MemoryProvider.cs @@ -0,0 +1,81 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text.Common; +using ServiceStack.Text.Json; + +namespace ServiceStack.Text +{ + public abstract class MemoryProvider + { + public static MemoryProvider Instance = +#if NETCORE && !NETSTANDARD2_0 + NetCoreMemory.Provider; +#else + DefaultMemory.Provider; +#endif + + internal const string BadFormat = "Input string was not in a correct format."; + internal const string OverflowMessage = "Value was either too large or too small for an {0}."; + + public abstract bool TryParseBoolean(ReadOnlySpan value, out bool result); + public abstract bool ParseBoolean(ReadOnlySpan value); + + public abstract bool TryParseDecimal(ReadOnlySpan value, out decimal result); + public abstract decimal ParseDecimal(ReadOnlySpan value); + public abstract decimal ParseDecimal(ReadOnlySpan value, bool allowThousands); + + public abstract bool TryParseFloat(ReadOnlySpan value, out float result); + public abstract float ParseFloat(ReadOnlySpan value); + + public abstract bool TryParseDouble(ReadOnlySpan value, out double result); + public abstract double ParseDouble(ReadOnlySpan value); + + public abstract sbyte ParseSByte(ReadOnlySpan value); + public abstract byte ParseByte(ReadOnlySpan value); + public abstract short ParseInt16(ReadOnlySpan value); + public abstract ushort ParseUInt16(ReadOnlySpan value); + public abstract int ParseInt32(ReadOnlySpan value); + public abstract uint ParseUInt32(ReadOnlySpan value); + public abstract uint ParseUInt32(ReadOnlySpan value, NumberStyles style); + public abstract long ParseInt64(ReadOnlySpan value); + public abstract ulong ParseUInt64(ReadOnlySpan value); + + public abstract Guid ParseGuid(ReadOnlySpan value); + + public abstract byte[] ParseBase64(ReadOnlySpan value); + + public abstract string ToBase64(ReadOnlyMemory value); + + public abstract void Write(Stream stream, ReadOnlyMemory value); + public abstract void Write(Stream stream, ReadOnlyMemory value); + + public abstract Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default); + public abstract Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default); + + public abstract Task WriteAsync(Stream stream, ReadOnlySpan value, CancellationToken token = default); + + public abstract object Deserialize(Stream stream, Type type, DeserializeStringSpanDelegate deserializer); + + public abstract Task DeserializeAsync(Stream stream, Type type, + DeserializeStringSpanDelegate deserializer); + + public abstract StringBuilder Append(StringBuilder sb, ReadOnlySpan value); + + public abstract int GetUtf8CharCount(ReadOnlySpan bytes); + public abstract int GetUtf8ByteCount(ReadOnlySpan chars); + + public abstract ReadOnlyMemory ToUtf8(ReadOnlySpan source); + public abstract ReadOnlyMemory FromUtf8(ReadOnlySpan source); + + public abstract int ToUtf8(ReadOnlySpan source, Span destination); + public abstract int FromUtf8(ReadOnlySpan source, Span destination); + + public abstract byte[] ToUtf8Bytes(ReadOnlySpan source); + public abstract string FromUtf8Bytes(ReadOnlySpan source); + public abstract MemoryStream ToMemoryStream(ReadOnlySpan source); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/MimeTypes.cs b/src/ServiceStack.Text/MimeTypes.cs new file mode 100644 index 000000000..4dfe3b122 --- /dev/null +++ b/src/ServiceStack.Text/MimeTypes.cs @@ -0,0 +1,453 @@ +using System; +using System.Collections.Generic; + +namespace ServiceStack; + +public static class MimeTypes +{ + public static Dictionary ExtensionMimeTypes = new(); + public const string Utf8Suffix = "; charset=utf-8"; + + public const string Html = "text/html"; + public const string HtmlUtf8 = Html + Utf8Suffix; + public const string Css = "text/css"; + public const string Xml = "application/xml"; + public const string XmlText = "text/xml"; + public const string Json = "application/json"; + public const string ProblemJson = "application/problem+json"; + public const string JsonText = "text/json"; + public const string Jsv = "application/jsv"; + public const string JsvText = "text/jsv"; + public const string Csv = "text/csv"; + public const string ProtoBuf = "application/x-protobuf"; + public const string JavaScript = "text/javascript"; + public const string WebAssembly = "application/wasm"; + public const string Jar = "application/java-archive"; + public const string Dmg = "application/x-apple-diskimage"; + public const string Pkg = "application/x-newton-compatible-pkg"; + + public const string FormUrlEncoded = "application/x-www-form-urlencoded"; + public const string MultiPartFormData = "multipart/form-data"; + public const string JsonReport = "text/jsonreport"; + public const string Soap11 = "text/xml; charset=utf-8"; + public const string Soap12 = "application/soap+xml"; + public const string Yaml = "application/yaml"; + public const string YamlText = "text/yaml"; + public const string PlainText = "text/plain"; + public const string MarkdownText = "text/markdown"; + public const string MsgPack = "application/x-msgpack"; + public const string Wire = "application/x-wire"; + public const string Compressed = "application/x-compressed"; + public const string NetSerializer = "application/x-netserializer"; + public const string Excel = "application/excel"; + public const string MsWord = "application/msword"; + public const string Cert = "application/x-x509-ca-cert"; + + public const string ImagePng = "image/png"; + public const string ImageGif = "image/gif"; + public const string ImageJpg = "image/jpeg"; + public const string ImageSvg = "image/svg+xml"; + + public const string Bson = "application/bson"; + public const string Binary = "application/octet-stream"; + public const string ServerSentEvents = "text/event-stream"; + + public static string GetExtension(string mimeType) + { + switch (mimeType) + { + case ProtoBuf: + return ".pbuf"; + } + + var parts = mimeType.Split('/'); + if (parts.Length == 1) return "." + parts[0].LeftPart('+').LeftPart(';'); + if (parts.Length == 2) return "." + parts[1].LeftPart('+').LeftPart(';'); + + throw new NotSupportedException("Unknown mimeType: " + mimeType); + } + + //Lower cases and trims left part of content-type prior ';' + public static string GetRealContentType(string contentType) + { + if (contentType == null) + return null; + + int start = -1, end = -1; + + for(int i=0; i < contentType.Length; i++) + { + if (!char.IsWhiteSpace(contentType[i])) + { + if (contentType[i] == ';') + break; + if (start == -1) + { + start = i; + } + end = i; + } + } + + return start != -1 + ? contentType.Substring(start, end - start + 1).ToLowerInvariant() + : null; + } + + /// + /// Case-insensitive, trimmed compare of two content types from start to ';', i.e. without charset suffix + /// + public static bool MatchesContentType(string contentType, string matchesContentType) + { + if (contentType == null || matchesContentType == null) + return false; + + int start = -1, matchStart = -1, matchEnd = -1; + + for (var i=0; i < contentType.Length; i++) + { + if (char.IsWhiteSpace(contentType[i])) + continue; + start = i; + break; + } + + for (var i=0; i < matchesContentType.Length; i++) + { + if (char.IsWhiteSpace(matchesContentType[i])) + continue; + if (matchesContentType[i] == ';') + break; + if (matchStart == -1) + matchStart = i; + matchEnd = i; + } + + return start != -1 && matchStart != -1 && matchEnd != -1 + && string.Compare(contentType, start, + matchesContentType, matchStart, matchEnd - matchStart + 1, + StringComparison.OrdinalIgnoreCase) == 0; + } + + public static Func IsBinaryFilter { get; set; } + + public static bool IsBinary(string contentType) + { + var userFilter = IsBinaryFilter?.Invoke(contentType); + if (userFilter != null) + return userFilter.Value; + + var realContentType = GetRealContentType(contentType); + switch (realContentType) + { + case ProtoBuf: + case MsgPack: + case Binary: + case Bson: + case Wire: + case Cert: + case Excel: + case MsWord: + case Compressed: + case WebAssembly: + case Jar: + case Dmg: + case Pkg: + return true; + } + + // Text format exceptions to below heuristics + switch (realContentType) + { + case ImageSvg: + return false; + } + + var primaryType = realContentType.LeftPart('/'); + var secondaryType = realContentType.RightPart('/'); + switch (primaryType) + { + case "image": + case "audio": + case "video": + return true; + } + + if (secondaryType.StartsWith("pkc") + || secondaryType.StartsWith("x-pkc") + || secondaryType.StartsWith("font") + || secondaryType.StartsWith("vnd.ms-")) + return true; + + return false; + } + + public static string GetMimeType(string fileNameOrExt) + { + if (string.IsNullOrEmpty(fileNameOrExt)) + throw new ArgumentNullException(nameof(fileNameOrExt)); + + var fileExt = fileNameOrExt.LastRightPart('.').ToLower(); + if (ExtensionMimeTypes.TryGetValue(fileExt, out var mimeType)) + { + return mimeType; + } + + switch (fileExt) + { + case "jpeg": + return "image/jpeg"; + case "gif": + return "image/gif"; + case "png": + return "image/png"; + case "tiff": + return "image/tiff"; + case "bmp": + return "image/bmp"; + case "webp": + return "image/webp"; + + case "jpg": + return "image/jpeg"; + + case "tif": + return "image/tiff"; + + case "svg": + return ImageSvg; + + case "ico": + return "image/x-icon"; + + case "htm": + case "html": + case "shtml": + return "text/html"; + + case "js": + return "text/javascript"; + case "ts": + return "text/typescript"; + case "jsx": + return "text/jsx"; + + case "csv": + return Csv; + case "css": + return Css; + + case "cs": + return "text/x-csharp"; + case "fs": + return "text/x-fsharp"; + case "vb": + return "text/x-vb"; + case "dart": + return "application/dart"; + case "go": + return "text/x-go"; + case "kt": + case "kts": + return "text/x-kotlin"; + case "java": + return "text/x-java"; + case "py": + return "text/x-python"; + case "groovy": + case "gradle": + return "text/x-groovy"; + + case "yml": + case "yaml": + return YamlText; + + case "sh": + return "text/x-sh"; + case "bat": + case "cmd": + return "application/bat"; + + case "xml": + case "csproj": + case "fsproj": + case "vbproj": + return "text/xml"; + + case "txt": + case "ps1": + return "text/plain"; + + case "sgml": + return "text/sgml"; + + case "mp3": + return "audio/mpeg3"; + + case "au": + case "snd": + return "audio/basic"; + + case "aac": + case "ac3": + case "aiff": + case "m4a": + case "m4b": + case "m4p": + case "mid": + case "midi": + case "wav": + return "audio/" + fileExt; + + case "qt": + case "mov": + return "video/quicktime"; + + case "mpg": + return "video/mpeg"; + + case "ogv": + return "video/ogg"; + + case "3gpp": + case "avi": + case "dv": + case "divx": + case "ogg": + case "mp4": + case "webm": + return "video/" + fileExt; + + case "rtf": + return "application/" + fileExt; + + case "xls": + case "xlt": + case "xla": + return Excel; + + case "xlsx": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + case "xltx": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.template"; + + case "doc": + case "dot": + return MsWord; + + case "docx": + return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + case "dotx": + return "application/vnd.openxmlformats-officedocument.wordprocessingml.template"; + + case "ppt": + case "oit": + case "pps": + case "ppa": + return "application/vnd.ms-powerpoint"; + + case "pptx": + return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + case "potx": + return "application/vnd.openxmlformats-officedocument.presentationml.template"; + case "ppsx": + return "application/vnd.openxmlformats-officedocument.presentationml.slideshow"; + + case "mdb": + return "application/vnd.ms-access"; + + case "cer": + case "crt": + case "der": + return Cert; + + case "p10": + return "application/pkcs10"; + case "p12": + return "application/x-pkcs12"; + case "p7b": + case "spc": + return "application/x-pkcs7-certificates"; + case "p7c": + case "p7m": + return "application/pkcs7-mime"; + case "p7r": + return "application/x-pkcs7-certreqresp"; + case "p7s": + return "application/pkcs7-signature"; + case "sst": + return "application/vnd.ms-pki.certstore"; + + case "gz": + case "tgz": + case "zip": + case "rar": + case "lzh": + case "z": + return Compressed; + + case "eot": + return "application/vnd.ms-fontobject"; + + case "ttf": + return "application/octet-stream"; + + case "woff": + return "application/font-woff"; + case "woff2": + return "application/font-woff2"; + + case "jar": + return Jar; + + case "aaf": + case "aca": + case "asd": + case "bin": + case "cab": + case "chm": + case "class": + case "cur": + case "db": + case "dat": + case "deploy": + case "dll": + case "dsp": + case "exe": + case "fla": + case "ics": + case "inf": + case "mix": + case "msi": + case "mso": + case "obj": + case "ocx": + case "prm": + case "prx": + case "psd": + case "psp": + case "qxd": + case "sea": + case "snp": + case "so": + case "sqlite": + case "toc": + case "u32": + case "xmp": + case "xsn": + case "xtp": + return Binary; + + case "wasm": + return WebAssembly; + + case "dmg": + return Dmg; + case "pkg": + return Pkg; + + default: + return "application/" + fileExt; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/MurmurHash2.cs b/src/ServiceStack.Text/MurmurHash2.cs new file mode 100644 index 000000000..807f51b3f --- /dev/null +++ b/src/ServiceStack.Text/MurmurHash2.cs @@ -0,0 +1,65 @@ +using System; + +namespace ServiceStack.Text +{ + // https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed#answer-145633 + // https://github.com/jitbit/MurmurHash.net + public class MurmurHash2 + { + public static uint Hash(string data) + { + return Hash(System.Text.Encoding.UTF8.GetBytes(data)); + } + + public static uint Hash(byte[] data) + { + return Hash(data, 0xc58f1a7a); + } + const uint m = 0x5bd1e995; + const int r = 24; + + public static uint Hash(byte[] data, uint seed) + { + int length = data.Length; + if (length == 0) + return 0; + uint h = seed ^ (uint)length; + int currentIndex = 0; + while (length >= 4) + { + uint k = (uint)(data[currentIndex++] | data[currentIndex++] << 8 | data[currentIndex++] << 16 | data[currentIndex++] << 24); + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + length -= 4; + } + switch (length) + { + case 3: + h ^= (UInt16)(data[currentIndex++] | data[currentIndex++] << 8); + h ^= (uint)(data[currentIndex] << 16); + h *= m; + break; + case 2: + h ^= (UInt16)(data[currentIndex++] | data[currentIndex] << 8); + h *= m; + break; + case 1: + h ^= data[currentIndex]; + h *= m; + break; + default: + break; + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/NetCoreMemory.cs b/src/ServiceStack.Text/NetCoreMemory.cs new file mode 100644 index 000000000..7a2aaed51 --- /dev/null +++ b/src/ServiceStack.Text/NetCoreMemory.cs @@ -0,0 +1,231 @@ +#if NETCORE && !NETSTANDARD2_0 + +using System; +using System.Buffers.Text; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text.Common; +using ServiceStack.Text.Pools; + +namespace ServiceStack.Text +{ + public sealed class NetCoreMemory : MemoryProvider + { + private static NetCoreMemory provider; + public static NetCoreMemory Provider => provider ??= new NetCoreMemory(); + private NetCoreMemory() { } + + public static void Configure() => Instance = Provider; + + public override bool ParseBoolean(ReadOnlySpan value) => bool.Parse(value); + + public override bool TryParseBoolean(ReadOnlySpan value, out bool result) => + bool.TryParse(value, out result); + + public override bool TryParseDecimal(ReadOnlySpan value, out decimal result) => + decimal.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result); + + public override decimal ParseDecimal(ReadOnlySpan value, bool allowThousands) => + decimal.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override bool TryParseFloat(ReadOnlySpan value, out float result) => + float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result); + + public override bool TryParseDouble(ReadOnlySpan value, out double result) => + double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result); + + public override decimal ParseDecimal(ReadOnlySpan value) => + decimal.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override float ParseFloat(ReadOnlySpan value) => + float.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override double ParseDouble(ReadOnlySpan value) => + double.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override sbyte ParseSByte(ReadOnlySpan value) => sbyte.Parse(value); + + public override byte ParseByte(ReadOnlySpan value) => byte.Parse(value); + + public override short ParseInt16(ReadOnlySpan value) => short.Parse(value); + + public override ushort ParseUInt16(ReadOnlySpan value) => ushort.Parse(value); + + public override int ParseInt32(ReadOnlySpan value) => int.Parse(value); + + public override uint ParseUInt32(ReadOnlySpan value) => uint.Parse(value); + + public override uint ParseUInt32(ReadOnlySpan value, NumberStyles style) => uint.Parse(value.ToString(), NumberStyles.HexNumber); + + public override long ParseInt64(ReadOnlySpan value) => long.Parse(value); + + public override ulong ParseUInt64(ReadOnlySpan value) => ulong.Parse(value); + + public override Guid ParseGuid(ReadOnlySpan value) => Guid.Parse(value); + + public override byte[] ParseBase64(ReadOnlySpan value) + { + byte[] bytes = BufferPool.GetBuffer(Base64.GetMaxDecodedFromUtf8Length(value.Length)); + try + { + if (Convert.TryFromBase64Chars(value, bytes, out var bytesWritten)) + { + var ret = new byte[bytesWritten]; + Buffer.BlockCopy(bytes, 0, ret, 0, bytesWritten); + return ret; + } + else + { + var chars = value.ToArray(); + return Convert.FromBase64CharArray(chars, 0, chars.Length); + } + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + + public override string ToBase64(ReadOnlyMemory value) + { + return Convert.ToBase64String(value.Span); + } + + public override void Write(Stream stream, ReadOnlyMemory value) + { + var utf8 = ToUtf8(value.Span); + if (stream is MemoryStream ms) + ms.Write(utf8.Span); + else + stream.Write(utf8.Span); + } + + public override void Write(Stream stream, ReadOnlyMemory value) + { + if (stream is MemoryStream ms) + ms.Write(value.Span); + else + stream.Write(value.Span); + } + + public override Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default) => + WriteAsync(stream, value.Span, token); + + public override Task WriteAsync(Stream stream, ReadOnlySpan value, CancellationToken token=default) + { + var utf8 = ToUtf8(value); + if (stream is MemoryStream ms) + ms.Write(utf8.Span); + else + return Task.FromResult(stream.WriteAsync(utf8, token)); + + return TypeConstants.EmptyTask; + } + + public override async Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default) + { + if (stream is MemoryStream ms) + ms.Write(value.Span); + else + await stream.WriteAsync(value, token).ConfigAwait(); + } + + public override object Deserialize(Stream stream, Type type, DeserializeStringSpanDelegate deserializer) + { + var fromPool = false; + + if (!(stream is MemoryStream ms)) + { + fromPool = true; + + if (stream.CanSeek) + stream.Position = 0; + + ms = stream.CopyToNewMemoryStream(); + } + + return Deserialize(ms, fromPool, type, deserializer); + } + + public override async Task DeserializeAsync(Stream stream, Type type, DeserializeStringSpanDelegate deserializer) + { + var fromPool = false; + + if (!(stream is MemoryStream ms)) + { + fromPool = true; + + if (stream.CanSeek) + stream.Position = 0; + + ms = await stream.CopyToNewMemoryStreamAsync().ConfigAwait(); + } + + return Deserialize(ms, fromPool, type, deserializer); + } + + private static object Deserialize(MemoryStream memoryStream, bool fromPool, Type type, DeserializeStringSpanDelegate deserializer) + { + var bytes = memoryStream.GetBufferAsSpan().WithoutBom(); + var chars = CharPool.GetBuffer(Encoding.UTF8.GetCharCount(bytes)); + try + { + var charsWritten = Encoding.UTF8.GetChars(bytes, chars); + ReadOnlySpan charsSpan = chars; + var ret = deserializer(type, charsSpan.Slice(0, charsWritten)); + return ret; + } + finally + { + CharPool.ReleaseBufferToPool(ref chars); + + if (fromPool) + memoryStream.Dispose(); + } + } + + public override StringBuilder Append(StringBuilder sb, ReadOnlySpan value) + { + return sb.Append(value); + } + + public override int GetUtf8CharCount(ReadOnlySpan bytes) => Encoding.UTF8.GetCharCount(bytes); + + public override int GetUtf8ByteCount(ReadOnlySpan chars) => Encoding.UTF8.GetByteCount(chars); + + public override ReadOnlyMemory ToUtf8(ReadOnlySpan source) + { + Memory bytes = new byte[Encoding.UTF8.GetByteCount(source)]; + var bytesWritten = Encoding.UTF8.GetBytes(source, bytes.Span); + return bytes.Slice(0, bytesWritten); + } + + public override ReadOnlyMemory FromUtf8(ReadOnlySpan source) + { + source = source.WithoutBom(); + Memory chars = new char[Encoding.UTF8.GetCharCount(source)]; + var charsWritten = Encoding.UTF8.GetChars(source, chars.Span); + return chars.Slice(0, charsWritten); + } + + public override int ToUtf8(ReadOnlySpan source, Span destination) => Encoding.UTF8.GetBytes(source, destination); + + public override int FromUtf8(ReadOnlySpan source, Span destination) => Encoding.UTF8.GetChars(source.WithoutBom(), destination); + + public override byte[] ToUtf8Bytes(ReadOnlySpan source) => ToUtf8(source).ToArray(); + + public override string FromUtf8Bytes(ReadOnlySpan source) => FromUtf8(source.WithoutBom()).ToString(); + + public override MemoryStream ToMemoryStream(ReadOnlySpan source) + { + var ms = MemoryStreamFactory.GetStream(source.Length); + ms.Write(source); + return ms; + } + } +} + +#endif diff --git a/src/ServiceStack.Text/ObjectDictionary.cs b/src/ServiceStack.Text/ObjectDictionary.cs new file mode 100644 index 000000000..94b08b4fa --- /dev/null +++ b/src/ServiceStack.Text/ObjectDictionary.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace ServiceStack +{ + /// + /// UX friendly alternative alias of Dictionary<string, object> + /// + public class ObjectDictionary : Dictionary + { + public ObjectDictionary() { } + public ObjectDictionary(int capacity) : base(capacity) { } + public ObjectDictionary(IEqualityComparer comparer) : base(comparer) { } + public ObjectDictionary(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } + public ObjectDictionary(IDictionary dictionary) : base(dictionary) { } + public ObjectDictionary(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary, comparer) { } + protected ObjectDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// UX friendly alternative alias of Dictionary<string, string> + /// + public class StringDictionary : Dictionary + { + public StringDictionary() { } + public StringDictionary(int capacity) : base(capacity) { } + public StringDictionary(IEqualityComparer comparer) : base(comparer) { } + public StringDictionary(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } + public StringDictionary(IDictionary dictionary) : base(dictionary) { } + public StringDictionary(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary, comparer) { } + protected StringDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// UX friendly alternative alias of List<KeyValuePair<string, object>gt; + /// + public class KeyValuePairs : List> + { + public KeyValuePairs() { } + public KeyValuePairs(int capacity) : base(capacity) { } + public KeyValuePairs(IEnumerable> collection) : base(collection) { } + + public static KeyValuePair Create(string key, object value) => + new KeyValuePair(key, value); + } + + /// + /// UX friendly alternative alias of List<KeyValuePair<string, string>gt; + /// + public class KeyValueStrings : List> + { + public KeyValueStrings() { } + public KeyValueStrings(int capacity) : base(capacity) { } + public KeyValueStrings(IEnumerable> collection) : base(collection) { } + + public static KeyValuePair Create(string key, string value) => + new KeyValuePair(key, value); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/ParseAsType.cs b/src/ServiceStack.Text/ParseAsType.cs new file mode 100644 index 000000000..7f9e639ae --- /dev/null +++ b/src/ServiceStack.Text/ParseAsType.cs @@ -0,0 +1,22 @@ +using System; + +namespace ServiceStack.Text +{ + [Flags] + public enum ParseAsType + { + None = 0, + Bool = 2, + Byte = 4, + SByte = 8, + Int16 = 16, + Int32 = 32, + Int64 = 64, + UInt16 = 128, + UInt32 = 256, + UInt64 = 512, + Decimal = 1024, + Double = 2048, + Single = 4096 + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/PathUtils.cs b/src/ServiceStack.Text/PathUtils.cs new file mode 100644 index 000000000..73a7fe027 --- /dev/null +++ b/src/ServiceStack.Text/PathUtils.cs @@ -0,0 +1,217 @@ +// Copyright (c) ServiceStack, Inc. All Rights Reserved. +// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using ServiceStack.Text; + +namespace ServiceStack +{ + public static class PathUtils + { + public static string MapAbsolutePath(this string relativePath, string appendPartialPathModifier) + { + return PclExport.Instance.MapAbsolutePath(relativePath, appendPartialPathModifier); + } + + /// + /// Maps the path of a file in the context of a VS project in a Console App + /// + /// the relative path + /// the absolute path + /// Assumes static content is two directories above the /bin/ directory, + /// eg. in a unit test scenario the assembly would be in /bin/Debug/. + public static string MapProjectPath(this string relativePath) + { + var sep = PclExport.Instance.DirSep; + return Env.HasMultiplePlatformTargets + ? PclExport.Instance.MapAbsolutePath(relativePath, $"{sep}..{sep}..{sep}..") + : PclExport.Instance.MapAbsolutePath(relativePath, $"{sep}..{sep}.."); + } + /// + /// Maps the path of a file in the context of a VS 2017+ multi-platform project in a Console App + /// + /// the relative path + /// the absolute path + /// Assumes static content is two directories above the /bin/ directory, + /// eg. in a unit test scenario the assembly would be in /bin/Debug/net45 + public static string MapProjectPlatformPath(this string relativePath) + { + var sep = PclExport.Instance.DirSep; + return PclExport.Instance.MapAbsolutePath(relativePath, $"{sep}..{sep}..{sep}.."); + } + + /// + /// Maps the path of a file in the bin\ folder of a self-hosted scenario + /// + /// the relative path + /// the absolute path + /// Assumes static content is copied to /bin/ folder with the assemblies + public static string MapAbsolutePath(this string relativePath) + { + return PclExport.Instance.MapAbsolutePath(relativePath, null); + } + + /// + /// Maps the path of a file in an ASP.NET hosted scenario + /// + /// the relative path + /// the absolute path + /// Assumes static content is in the parent folder of the /bin/ directory + public static string MapHostAbsolutePath(this string relativePath) + { + var sep = PclExport.Instance.DirSep; +#if !NETCORE + return PclExport.Instance.MapAbsolutePath(relativePath, $"{sep}.."); +#else + return PclExport.Instance.MapAbsolutePath(relativePath, $"{sep}..{sep}..{sep}.."); +#endif + } + + internal static string CombinePaths(StringBuilder sb, params string[] paths) + { + AppendPaths(sb, paths); + return sb.ToString(); + } + + public static void AppendPaths(StringBuilder sb, string[] paths) + { + foreach (var path in paths) + { + if (string.IsNullOrEmpty(path)) + continue; + + if (sb.Length > 0 && sb[sb.Length - 1] != '/') + sb.Append("/"); + + sb.Append(path.Replace('\\', '/').TrimStart('/')); + } + } + + public static string CombinePaths(params string[] paths) + { + var sb = StringBuilderThreadStatic.Allocate(); + AppendPaths(sb, paths); + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + + public static string AssertDir(this string dirPath) + { + if (!dirPath.DirectoryExists()) + dirPath.CreateDirectory(); + return dirPath; + } + + private static readonly char[] Slashes = { '/', '\\' }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] //only trim/allocate if need to + private static string TrimEndIf(this string path, char[] chars) + { + if (string.IsNullOrEmpty(path) || chars == null || chars.Length == 0) + return path; + + var lastChar = path[path.Length - 1]; + foreach (var c in chars) + { + if (c == lastChar) + return path.TrimEnd(chars); + } + return path; + } + + public static string CombineWith(this string path, string withPath) + { + if (path == null) + path = ""; + if (string.IsNullOrEmpty(withPath)) + return path; + var startPath = path.TrimEndIf(Slashes); + return startPath + (withPath[0] == '/' ? withPath : "/" + withPath); + } + + public static string CombineWith(this string path, params string[] thesePaths) + { + if (path == null) + path = ""; + + if (thesePaths.Length == 1 && thesePaths[0] == null) return path; + var startPath = path.TrimEndIf(Slashes); + + var sb = StringBuilderThreadStatic.Allocate(); + sb.Append(startPath); + AppendPaths(sb, thesePaths); + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + + public static string CombineWith(this string path, params object[] thesePaths) + { + if (thesePaths.Length == 1 && thesePaths[0] == null) return path; + + var sb = StringBuilderThreadStatic.Allocate(); + sb.Append(path.TrimEndIf(Slashes)); + AppendPaths(sb, ToStrings(thesePaths)); + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + + public static string ResolvePaths(this string path) + { + if (path == null || path.IndexOfAny("./", "/.") == -1) + return path; + + var schemePos = path.IndexOf("://", StringComparison.Ordinal); + var prefix = schemePos >= 0 + ? path.Substring(0, schemePos + 3) + : ""; + + var parts = path.Substring(prefix.Length).Split('/').ToList(); + var combinedPaths = new List(); + foreach (var part in parts) + { + if (string.IsNullOrEmpty(part) || part == ".") + continue; + + if (part == ".." && combinedPaths.Count > 0) + combinedPaths.RemoveAt(combinedPaths.Count - 1); + else + combinedPaths.Add(part); + } + + var resolvedPath = string.Join("/", combinedPaths); + if (path[0] == '/' && prefix.Length == 0) + resolvedPath = "/" + resolvedPath; + + return path[path.Length - 1] == '/' && resolvedPath.Length > 0 + ? prefix + resolvedPath + "/" + : prefix + resolvedPath; + } + + public static string[] ToStrings(object[] thesePaths) + { + var to = new string[thesePaths.Length]; + for (var i = 0; i < thesePaths.Length; i++) + { + to[i] = thesePaths[i].ToString(); + } + return to; + } + + internal static List Map(System.Collections.IEnumerable items, Func converter) + { + if (items == null) + return new List(); + + var list = new List(); + foreach (var item in items) + { + list.Add(converter(item)); + } + return list; + } + } + +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Pcl.Dynamic.cs b/src/ServiceStack.Text/Pcl.Dynamic.cs new file mode 100644 index 000000000..0a462ab61 --- /dev/null +++ b/src/ServiceStack.Text/Pcl.Dynamic.cs @@ -0,0 +1,183 @@ +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System; +using System.Collections.Generic; +using System.Dynamic; +using ServiceStack.Text; +using ServiceStack.Text.Common; +using ServiceStack.Text.Json; +using System.Linq; + +using System.Reflection; +using System.Reflection.Emit; + +namespace ServiceStack +{ + public static class DeserializeDynamic + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + private static readonly ParseStringSpanDelegate CachedParseFn; + static DeserializeDynamic() + { + CachedParseFn = ParseDynamic; + } + + public static ParseStringDelegate Parse => v => CachedParseFn(v.AsSpan()); + + public static ParseStringSpanDelegate ParseStringSpan => CachedParseFn; + + public static IDynamicMetaObjectProvider ParseDynamic(string value) => ParseDynamic(value.AsSpan()); + + public static IDynamicMetaObjectProvider ParseDynamic(ReadOnlySpan value) + { + var index = VerifyAndGetStartIndex(value, typeof(ExpandoObject)); + + var result = new ExpandoObject(); + + if (JsonTypeSerializer.IsEmptyMap(value)) return result; + + var container = (IDictionary)result; + + var tryToParsePrimitiveTypes = JsConfig.TryToParsePrimitiveTypeValues; + + var valueLength = value.Length; + while (index < valueLength) + { + var keyValue = Serializer.EatMapKey(value, ref index); + Serializer.EatMapKeySeperator(value, ref index); + var elementValue = Serializer.EatValue(value, ref index); + + var mapKey = Serializer.UnescapeString(keyValue).ToString(); + + if (JsonUtils.IsJsObject(elementValue)) + { + container[mapKey] = ParseDynamic(elementValue); + } + else if (JsonUtils.IsJsArray(elementValue)) + { + container[mapKey] = DeserializeList, TSerializer>.ParseStringSpan(elementValue); + } + else if (tryToParsePrimitiveTypes) + { + container[mapKey] = DeserializeType.ParsePrimitive(elementValue) ?? Serializer.UnescapeString(elementValue).Value(); + } + else + { + container[mapKey] = Serializer.UnescapeString(elementValue).Value(); + } + + Serializer.EatItemSeperatorOrMapEndChar(value, ref index); + } + + return result; + } + + private static int VerifyAndGetStartIndex(ReadOnlySpan value, Type createMapType) + { + var index = 0; + if (!Serializer.EatMapStartChar(value, ref index)) + { + //Don't throw ex because some KeyValueDataContractDeserializer don't have '{}' + Tracer.Instance.WriteDebug("WARN: Map definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", + JsWriter.MapStartChar, createMapType != null ? createMapType.Name : "Dictionary<,>", value.Substring(0, value.Length < 50 ? value.Length : 50)); + } + return index; + } + } + + public class DynamicJson : DynamicObject + { + private readonly IDictionary _hash = new Dictionary(); + + public static string Serialize(dynamic instance) + { + var json = JsonSerializer.SerializeToString(instance); + return json; + } + + public static dynamic Deserialize(string json) + { + // Support arbitrary nesting by using JsonObject + var deserialized = JsonSerializer.DeserializeFromString(json); + var hash = deserialized.ToUnescapedDictionary().ToObjectDictionary(); + return new DynamicJson(hash); + } + + public DynamicJson(IEnumerable> hash) + { + _hash.Clear(); + foreach (var entry in hash) + { + _hash.Add(Underscored(entry.Key), entry.Value); + } + } + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + var name = Underscored(binder.Name); + _hash[name] = value; + return _hash[name] == value; + } + + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + var name = Underscored(binder.Name); + return YieldMember(name, out result); + } + + public override string ToString() + { + return JsonSerializer.SerializeToString(_hash); + } + + private bool YieldMember(string name, out object result) + { + if (_hash.ContainsKey(name)) + { + var json = _hash[name].ToString(); + if (json.TrimStart(' ').StartsWith("{", StringComparison.Ordinal)) + { + result = Deserialize(json); + return true; + } + else if (json.TrimStart(' ').StartsWith("[", StringComparison.Ordinal)) + { + result = JsonArrayObjects.Parse(json).Select(a => + { + var hash = a.ToDictionary, string, object>(entry => entry.Key, entry => entry.Value); + return new DynamicJson(hash); + }).ToArray(); + return true; + } + result = json; + return _hash[name] == result; + } + result = null; + return false; + } + + internal static string Underscored(string pascalCase) + { + return Underscored(pascalCase.ToCharArray()); + } + + internal static string Underscored(IEnumerable pascalCase) + { + var sb = StringBuilderCache.Allocate(); + var i = 0; + foreach (var c in pascalCase) + { + if (char.IsUpper(c) && i > 0) + { + sb.Append("_"); + } + sb.Append(c); + i++; + } + return StringBuilderCache.ReturnAndFree(sb).ToLowerInvariant(); + } + } +} diff --git a/src/ServiceStack.Text/PclExport.NetCore.cs b/src/ServiceStack.Text/PclExport.NetCore.cs new file mode 100644 index 000000000..7cf1fc8f3 --- /dev/null +++ b/src/ServiceStack.Text/PclExport.NetCore.cs @@ -0,0 +1,41 @@ +#if (NETCORE || NET6_0_OR_GREATER) && !NETSTANDARD2_0 + +using System; +using ServiceStack.Text; +using ServiceStack.Text.Common; + +namespace ServiceStack +{ + public class Net6PclExport : NetStandardPclExport + { + public Net6PclExport() + { + this.PlatformName = Platforms.Net6; + ReflectionOptimizer.Instance = EmitReflectionOptimizer.Provider; + } + + public override ParseStringDelegate GetJsReaderParseMethod(Type type) + { + if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || + type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) + { + return DeserializeDynamic.Parse; + } + + return null; + } + + public override ParseStringSpanDelegate GetJsReaderParseStringSpanMethod(Type type) + { + if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || + type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) + { + return DeserializeDynamic.ParseStringSpan; + } + + return null; + } + } +} + +#endif \ No newline at end of file diff --git a/src/ServiceStack.Text/PclExport.NetFx.cs b/src/ServiceStack.Text/PclExport.NetFx.cs new file mode 100644 index 000000000..9df18991b --- /dev/null +++ b/src/ServiceStack.Text/PclExport.NetFx.cs @@ -0,0 +1,1025 @@ +#if NETFX +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text; +using ServiceStack.Text.Common; +using ServiceStack.Text.Json; +using System.Reflection.Emit; + +namespace ServiceStack +{ + public class NetFxPclExport : PclExport + { + public static NetFxPclExport Provider = new NetFxPclExport(); + + public NetFxPclExport() + { + this.DirSep = Path.DirectorySeparatorChar; + this.AltDirSep = Path.DirectorySeparatorChar == '/' ? '\\' : '/'; + this.RegexOptions = RegexOptions.Compiled; + this.InvariantComparison = StringComparison.InvariantCulture; + this.InvariantComparisonIgnoreCase = StringComparison.InvariantCultureIgnoreCase; + this.InvariantComparer = StringComparer.InvariantCulture; + this.InvariantComparerIgnoreCase = StringComparer.InvariantCultureIgnoreCase; + + this.PlatformName = Platforms.NetFX; + ReflectionOptimizer.Instance = EmitReflectionOptimizer.Provider; + } + + public static PclExport Configure() + { + Configure(Provider); + return Provider; + } + + public override string ReadAllText(string filePath) + { + return File.ReadAllText(filePath); + } + + public override string ToInvariantUpper(char value) + { + return value.ToString(CultureInfo.InvariantCulture).ToUpper(); + } + + public override bool IsAnonymousType(Type type) + { + return type.HasAttribute() + && type.IsGenericType && type.Name.Contains("AnonymousType") + && (type.Name.StartsWith("<>", StringComparison.Ordinal) || type.Name.StartsWith("VB$", StringComparison.Ordinal)) + && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; + } + + public override bool FileExists(string filePath) + { + return File.Exists(filePath); + } + + public override bool DirectoryExists(string dirPath) + { + return Directory.Exists(dirPath); + } + + public override void CreateDirectory(string dirPath) + { + Directory.CreateDirectory(dirPath); + } + + public override string[] GetFileNames(string dirPath, string searchPattern = null) + { + if (!Directory.Exists(dirPath)) + return TypeConstants.EmptyStringArray; + + return searchPattern != null + ? Directory.GetFiles(dirPath, searchPattern) + : Directory.GetFiles(dirPath); + } + + public override string[] GetDirectoryNames(string dirPath, string searchPattern = null) + { + if (!Directory.Exists(dirPath)) + return TypeConstants.EmptyStringArray; + + return searchPattern != null + ? Directory.GetDirectories(dirPath, searchPattern) + : Directory.GetDirectories(dirPath); + } + + public const string AppSettingsKey = "servicestack:license"; + public const string EnvironmentKey = "SERVICESTACK_LICENSE"; + + public override void RegisterLicenseFromConfig() + { + string licenceKeyText; + try + { + //Automatically register license key stored in + licenceKeyText = System.Configuration.ConfigurationManager.AppSettings[AppSettingsKey]; + if (!string.IsNullOrEmpty(licenceKeyText)) + { + LicenseUtils.RegisterLicense(licenceKeyText); + return; + } + } + catch (NotSupportedException) { return; } // Ignore Unity/IL2CPP Exception + catch (Exception ex) + { + licenceKeyText = Environment.GetEnvironmentVariable(EnvironmentKey)?.Trim(); + if (string.IsNullOrEmpty(licenceKeyText)) + throw; + try + { + LicenseUtils.RegisterLicense(licenceKeyText); + } + catch + { + throw ex; + } + } + + //or SERVICESTACK_LICENSE Environment variable + licenceKeyText = Environment.GetEnvironmentVariable(EnvironmentKey)?.Trim(); + if (!string.IsNullOrEmpty(licenceKeyText)) + { + LicenseUtils.RegisterLicense(licenceKeyText); + } + } + + public override string GetEnvironmentVariable(string name) + { + return Environment.GetEnvironmentVariable(name); + } + + public override void WriteLine(string line) + { + Console.WriteLine(line); + } + + public override void WriteLine(string format, params object[] args) + { + Console.WriteLine(format, args); + } + + public override async Task WriteAndFlushAsync(Stream stream, byte[] bytes) + { + await stream.WriteAsync(bytes, 0, bytes.Length).ConfigAwait(); + await stream.FlushAsync().ConfigAwait(); + } + + public override void AddCompression(WebRequest webReq) + { + var httpReq = (HttpWebRequest)webReq; + httpReq.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate"); + httpReq.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + } + + public override Stream GetRequestStream(WebRequest webRequest) + { + return webRequest.GetRequestStream(); + } + + public override WebResponse GetResponse(WebRequest webRequest) + { + return webRequest.GetResponse(); + } + +#if !LITE + public override bool IsDebugBuild(Assembly assembly) + { + return assembly.AllAttributes() + .OfType() + .Select(attr => attr.IsJITTrackingEnabled) + .FirstOrDefault(); + } +#endif + + public override string MapAbsolutePath(string relativePath, string appendPartialPathModifier) + { + if (relativePath.StartsWith("~")) + { + var assemblyDirectoryPath = Path.GetDirectoryName(new Uri(typeof(PathUtils).Assembly.EscapedCodeBase).LocalPath); + + // Escape the assembly bin directory to the hostname directory + var hostDirectoryPath = appendPartialPathModifier != null + ? assemblyDirectoryPath + appendPartialPathModifier + : assemblyDirectoryPath; + + return Path.GetFullPath(relativePath.Replace("~", hostDirectoryPath)); + } + return relativePath; + } + + public override Assembly LoadAssembly(string assemblyPath) + { + return Assembly.LoadFrom(assemblyPath); + } + + public override void AddHeader(WebRequest webReq, string name, string value) + { + webReq.Headers.Add(name, value); + } + + public override Assembly[] GetAllAssemblies() + { + return AppDomain.CurrentDomain.GetAssemblies(); + } + + public override Type FindType(string typeName, string assemblyName) + { + var binPath = AssemblyUtils.GetAssemblyBinPath(Assembly.GetExecutingAssembly()); + Assembly assembly = null; + var assemblyDllPath = binPath + $"{assemblyName}.dll"; + if (File.Exists(assemblyDllPath)) + { + assembly = AssemblyUtils.LoadAssembly(assemblyDllPath); + } + var assemblyExePath = binPath + $"{assemblyName}.exe"; + if (File.Exists(assemblyExePath)) + { + assembly = AssemblyUtils.LoadAssembly(assemblyExePath); + } + return assembly != null ? assembly.GetType(typeName) : null; + } + + public override string GetAssemblyCodeBase(Assembly assembly) + { + return assembly.CodeBase; + } + + public override string GetAssemblyPath(Type source) + { + var assemblyUri = new Uri(source.Assembly.EscapedCodeBase); + return assemblyUri.LocalPath; + } + + public override string GetAsciiString(byte[] bytes, int index, int count) + { + return Encoding.ASCII.GetString(bytes, index, count); + } + + public override byte[] GetAsciiBytes(string str) + { + return Encoding.ASCII.GetBytes(str); + } + + public override bool InSameAssembly(Type t1, Type t2) + { + return t1.Assembly == t2.Assembly; + } + + public override Type GetGenericCollectionType(Type type) + { + return type.FindInterfaces((t, critera) => + t.IsGenericType + && t.GetGenericTypeDefinition() == typeof(ICollection<>), null).FirstOrDefault(); + } + + public override string ToXsdDateTimeString(DateTime dateTime) + { +#if !LITE + return System.Xml.XmlConvert.ToString(dateTime.ToStableUniversalTime(), System.Xml.XmlDateTimeSerializationMode.Utc); +#else + return dateTime.ToStableUniversalTime().ToString(DateTimeSerializer.XsdDateTimeFormat); +#endif + } + + public override string ToLocalXsdDateTimeString(DateTime dateTime) + { +#if !LITE + return System.Xml.XmlConvert.ToString(dateTime, System.Xml.XmlDateTimeSerializationMode.Local); +#else + return dateTime.ToString(DateTimeSerializer.XsdDateTimeFormat); +#endif + } + + public override DateTime ParseXsdDateTime(string dateTimeStr) + { +#if !LITE + return System.Xml.XmlConvert.ToDateTime(dateTimeStr, System.Xml.XmlDateTimeSerializationMode.Utc); +#else + return DateTime.ParseExact(dateTimeStr, DateTimeSerializer.XsdDateTimeFormat, CultureInfo.InvariantCulture); +#endif + } + +#if !LITE + public override DateTime ParseXsdDateTimeAsUtc(string dateTimeStr) + { + return System.Xml.XmlConvert.ToDateTime(dateTimeStr, System.Xml.XmlDateTimeSerializationMode.Utc).Prepare(parsedAsUtc: true); + } +#endif + + public override DateTime ToStableUniversalTime(DateTime dateTime) + { + // .Net 2.0 - 3.5 has an issue with DateTime.ToUniversalTime, but works ok with TimeZoneInfo.ConvertTimeToUtc. + // .Net 4.0+ does this under the hood anyway. + return TimeZoneInfo.ConvertTimeToUtc(dateTime); + } + + public override ParseStringDelegate GetDictionaryParseMethod(Type type) + { + if (type == typeof(Hashtable)) + { + return SerializerUtils.ParseHashtable; + } + return null; + } + + public override ParseStringSpanDelegate GetDictionaryParseStringSpanMethod(Type type) + { + if (type == typeof(Hashtable)) + { + return SerializerUtils.ParseHashtable; + } + return null; + } + + public override ParseStringDelegate GetSpecializedCollectionParseMethod(Type type) + { + if (type == typeof(StringCollection)) + { + return SerializerUtils.ParseStringCollection; + } + return null; + } + + public override ParseStringSpanDelegate GetSpecializedCollectionParseStringSpanMethod(Type type) + { + if (type == typeof(StringCollection)) + { + return SerializerUtils.ParseStringCollection; + } + return null; + } + + + public override ParseStringDelegate GetJsReaderParseMethod(Type type) + { +#if !LITE + if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || + type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) + { + return DeserializeDynamic.Parse; + } +#endif + return null; + } + + public override ParseStringSpanDelegate GetJsReaderParseStringSpanMethod(Type type) + { +#if !LITE + if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || + type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) + { + return DeserializeDynamic.ParseStringSpan; + } +#endif + return null; + } + + public override void InitHttpWebRequest(HttpWebRequest httpReq, + long? contentLength = null, bool allowAutoRedirect = true, bool keepAlive = true) + { + httpReq.UserAgent = Env.ServerUserAgent; + httpReq.AllowAutoRedirect = allowAutoRedirect; + httpReq.KeepAlive = keepAlive; + + if (contentLength != null) + { + httpReq.ContentLength = contentLength.Value; + } + } + + public override void CloseStream(Stream stream) + { + stream.Close(); + } + + public override LicenseKey VerifyLicenseKeyText(string licenseKeyText) + { + if (!licenseKeyText.VerifyLicenseKeyText(out LicenseKey key)) + throw new ArgumentException("licenseKeyText"); + + return key; + } + + public override void BeginThreadAffinity() + { + Thread.BeginThreadAffinity(); + } + + public override void EndThreadAffinity() + { + Thread.EndThreadAffinity(); + } + + public override void Config(HttpWebRequest req, + bool? allowAutoRedirect = null, + TimeSpan? timeout = null, + TimeSpan? readWriteTimeout = null, + string userAgent = null, + bool? preAuthenticate = null) + { + req.MaximumResponseHeadersLength = int.MaxValue; //throws "The message length limit was exceeded" exception + if (allowAutoRedirect.HasValue) req.AllowAutoRedirect = allowAutoRedirect.Value; + if (readWriteTimeout.HasValue) req.ReadWriteTimeout = (int)readWriteTimeout.Value.TotalMilliseconds; + if (timeout.HasValue) req.Timeout = (int)timeout.Value.TotalMilliseconds; + if (userAgent != null) req.UserAgent = userAgent; + if (preAuthenticate.HasValue) req.PreAuthenticate = preAuthenticate.Value; + } + + public override void SetUserAgent(HttpWebRequest httpReq, string value) + { + httpReq.UserAgent = value; + } + + public override void SetContentLength(HttpWebRequest httpReq, long value) + { + httpReq.ContentLength = value; + } + + public override void SetAllowAutoRedirect(HttpWebRequest httpReq, bool value) + { + httpReq.AllowAutoRedirect = value; + } + + public override void SetKeepAlive(HttpWebRequest httpReq, bool value) + { + httpReq.KeepAlive = value; + } + + public override string GetStackTrace() + { + return Environment.StackTrace; + } + + public override DataContractAttribute GetWeakDataContract(Type type) + { + return type.GetWeakDataContract(); + } + + public override DataMemberAttribute GetWeakDataMember(PropertyInfo pi) + { + return pi.GetWeakDataMember(); + } + + public override DataMemberAttribute GetWeakDataMember(FieldInfo pi) + { + return pi.GetWeakDataMember(); + } + } + + internal class SerializerUtils + where TSerializer : ITypeSerializer + { + private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); + + private static int VerifyAndGetStartIndex(ReadOnlySpan value, Type createMapType) + { + var index = 0; + if (!Serializer.EatMapStartChar(value, ref index)) + { + //Don't throw ex because some KeyValueDataContractDeserializer don't have '{}' + Tracer.Instance.WriteDebug("WARN: Map definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", + JsWriter.MapStartChar, createMapType != null ? createMapType.Name : "Dictionary<,>", value.Substring(0, value.Length < 50 ? value.Length : 50)); + } + return index; + } + + public static Hashtable ParseHashtable(string value) => ParseHashtable(value.AsSpan()); + + public static Hashtable ParseHashtable(ReadOnlySpan value) + { + if (value.IsEmpty) + return null; + + var index = VerifyAndGetStartIndex(value, typeof(Hashtable)); + + var result = new Hashtable(); + + if (JsonTypeSerializer.IsEmptyMap(value, index)) return result; + + var valueLength = value.Length; + while (index < valueLength) + { + var keyValue = Serializer.EatMapKey(value, ref index); + Serializer.EatMapKeySeperator(value, ref index); + var elementValue = Serializer.EatValue(value, ref index); + if (keyValue.IsEmpty) continue; + + var mapKey = keyValue.ToString(); + var mapValue = elementValue.Value(); + + result[mapKey] = mapValue; + + Serializer.EatItemSeperatorOrMapEndChar(value, ref index); + } + + return result; + } + + public static StringCollection ParseStringCollection(string value) where TS : ITypeSerializer => ParseStringCollection(value.AsSpan()); + + + public static StringCollection ParseStringCollection(ReadOnlySpan value) where TS : ITypeSerializer + { + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new StringCollection(); + + return ToStringCollection(DeserializeListWithElements.ParseStringList(value)); + } + + public static StringCollection ToStringCollection(List items) + { + var to = new StringCollection(); + foreach (var item in items) + { + to.Add(item); + } + return to; + } + } + + public static class PclExportExt + { + //XmlSerializer + public static void CompressToStream(TXmlDto from, Stream stream) + { + using (var deflateStream = new System.IO.Compression.DeflateStream(stream, System.IO.Compression.CompressionMode.Compress)) + using (var xw = new System.Xml.XmlTextWriter(deflateStream, Encoding.UTF8)) + { + var serializer = new DataContractSerializer(from.GetType()); + serializer.WriteObject(xw, from); + xw.Flush(); + } + } + + public static byte[] Compress(TXmlDto from) + { + using (var ms = MemoryStreamFactory.GetStream()) + { + CompressToStream(from, ms); + + return ms.ToArray(); + } + } + + //ReflectionExtensions + const string DataContract = "DataContractAttribute"; + + public static DataContractAttribute GetWeakDataContract(this Type type) + { + var attr = type.AllAttributes().FirstOrDefault(x => x.GetType().Name == DataContract); + if (attr != null) + { + var attrType = attr.GetType(); + + var accessor = TypeProperties.Get(attr.GetType()); + + return new DataContractAttribute + { + Name = (string)accessor.GetPublicGetter("Name")(attr), + Namespace = (string)accessor.GetPublicGetter("Namespace")(attr), + }; + } + return null; + } + + public static DataMemberAttribute GetWeakDataMember(this PropertyInfo pi) + { + var attr = pi.AllAttributes().FirstOrDefault(x => x.GetType().Name == ReflectionExtensions.DataMember); + if (attr != null) + { + var attrType = attr.GetType(); + + var accessor = TypeProperties.Get(attr.GetType()); + + var newAttr = new DataMemberAttribute + { + Name = (string)accessor.GetPublicGetter("Name")(attr), + EmitDefaultValue = (bool)accessor.GetPublicGetter("EmitDefaultValue")(attr), + IsRequired = (bool)accessor.GetPublicGetter("IsRequired")(attr), + }; + + var order = (int)accessor.GetPublicGetter("Order")(attr); + if (order >= 0) + newAttr.Order = order; //Throws Exception if set to -1 + + return newAttr; + } + return null; + } + + public static DataMemberAttribute GetWeakDataMember(this FieldInfo pi) + { + var attr = pi.AllAttributes().FirstOrDefault(x => x.GetType().Name == ReflectionExtensions.DataMember); + if (attr != null) + { + var attrType = attr.GetType(); + + var accessor = TypeProperties.Get(attr.GetType()); + + var newAttr = new DataMemberAttribute + { + Name = (string)accessor.GetPublicGetter("Name")(attr), + EmitDefaultValue = (bool)accessor.GetPublicGetter("EmitDefaultValue")(attr), + IsRequired = (bool)accessor.GetPublicGetter("IsRequired")(attr), + }; + + var order = (int)accessor.GetPublicGetter("Order")(attr); + if (order >= 0) + newAttr.Order = order; //Throws Exception if set to -1 + + return newAttr; + } + return null; + } + } +} + +//Not using it here, but @marcgravell's stuff is too good not to include +// http://code.google.com/p/fast-member/ Apache License 2.0 +namespace ServiceStack.Text.FastMember +{ + /// + /// Represents an individual object, allowing access to members by-name + /// + public abstract class ObjectAccessor + { + /// + /// Get or Set the value of a named member for the underlying object + /// + public abstract object this[string name] { get; set; } + /// + /// The object represented by this instance + /// + public abstract object Target { get; } + /// + /// Use the target types definition of equality + /// + public override bool Equals(object obj) + { + return Target.Equals(obj); + } + /// + /// Obtain the hash of the target object + /// + public override int GetHashCode() + { + return Target.GetHashCode(); + } + /// + /// Use the target's definition of a string representation + /// + public override string ToString() + { + return Target.ToString(); + } + + /// + /// Wraps an individual object, allowing by-name access to that instance + /// + public static ObjectAccessor Create(object target) + { + if (target == null) throw new ArgumentNullException("target"); + //IDynamicMetaObjectProvider dlr = target as IDynamicMetaObjectProvider; + //if (dlr != null) return new DynamicWrapper(dlr); // use the DLR + return new TypeAccessorWrapper(target, TypeAccessor.Create(target.GetType())); + } + + sealed class TypeAccessorWrapper : ObjectAccessor + { + private readonly object target; + private readonly TypeAccessor accessor; + public TypeAccessorWrapper(object target, TypeAccessor accessor) + { + this.target = target; + this.accessor = accessor; + } + public override object this[string name] + { + get { return accessor[target, name.ToUpperInvariant()]; } + set { accessor[target, name.ToUpperInvariant()] = value; } + } + public override object Target + { + get { return target; } + } + } + + //sealed class DynamicWrapper : ObjectAccessor + //{ + // private readonly IDynamicMetaObjectProvider target; + // public override object Target + // { + // get { return target; } + // } + // public DynamicWrapper(IDynamicMetaObjectProvider target) + // { + // this.target = target; + // } + // public override object this[string name] + // { + // get { return CallSiteCache.GetValue(name, target); } + // set { CallSiteCache.SetValue(name, target, value); } + // } + //} + } + + /// + /// Provides by-name member-access to objects of a given type + /// + public abstract class TypeAccessor + { + // hash-table has better read-without-locking semantics than dictionary + private static readonly Hashtable typeLookyp = new Hashtable(); + + /// + /// Does this type support new instances via a parameterless constructor? + /// + public virtual bool CreateNewSupported => false; + + /// + /// Create a new instance of this type + /// + public virtual object CreateNew() { throw new NotSupportedException(); } + + /// + /// Provides a type-specific accessor, allowing by-name access for all objects of that type + /// + /// The accessor is cached internally; a pre-existing accessor may be returned + public static TypeAccessor Create(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + var obj = (TypeAccessor)typeLookyp[type]; + if (obj != null) return obj; + + lock (typeLookyp) + { + // double-check + obj = (TypeAccessor)typeLookyp[type]; + if (obj != null) return obj; + + obj = CreateNew(type); + + typeLookyp[type] = obj; + return obj; + } + } + + //sealed class DynamicAccessor : TypeAccessor + //{ + // public static readonly DynamicAccessor Singleton = new DynamicAccessor(); + // private DynamicAccessor(){} + // public override object this[object target, string name] + // { + // get { return CallSiteCache.GetValue(name, target); } + // set { CallSiteCache.SetValue(name, target, value); } + // } + //} + + private static AssemblyBuilder assembly; + private static ModuleBuilder module; + private static int counter; + + private static void WriteGetter(ILGenerator il, Type type, PropertyInfo[] props, FieldInfo[] fields, bool isStatic) + { + LocalBuilder loc = type.IsValueType ? il.DeclareLocal(type) : null; + OpCode propName = isStatic ? OpCodes.Ldarg_1 : OpCodes.Ldarg_2, target = isStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1; + foreach (PropertyInfo prop in props) + { + if (prop.GetIndexParameters().Length != 0 || !prop.CanRead) continue; + var getFn = prop.GetGetMethod(); + if (getFn == null) continue; //Mono + + Label next = il.DefineLabel(); + il.Emit(propName); + il.Emit(OpCodes.Ldstr, prop.Name); + il.EmitCall(OpCodes.Call, strinqEquals, null); + il.Emit(OpCodes.Brfalse_S, next); + // match: + il.Emit(target); + Cast(il, type, loc); + il.EmitCall(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, getFn, null); + if (prop.PropertyType.IsValueType) + { + il.Emit(OpCodes.Box, prop.PropertyType); + } + il.Emit(OpCodes.Ret); + // not match: + il.MarkLabel(next); + } + foreach (FieldInfo field in fields) + { + Label next = il.DefineLabel(); + il.Emit(propName); + il.Emit(OpCodes.Ldstr, field.Name); + il.EmitCall(OpCodes.Call, strinqEquals, null); + il.Emit(OpCodes.Brfalse_S, next); + // match: + il.Emit(target); + Cast(il, type, loc); + il.Emit(OpCodes.Ldfld, field); + if (field.FieldType.IsValueType) + { + il.Emit(OpCodes.Box, field.FieldType); + } + il.Emit(OpCodes.Ret); + // not match: + il.MarkLabel(next); + } + il.Emit(OpCodes.Ldstr, "name"); + il.Emit(OpCodes.Newobj, typeof(ArgumentOutOfRangeException).GetConstructor(new Type[] { typeof(string) })); + il.Emit(OpCodes.Throw); + } + private static void WriteSetter(ILGenerator il, Type type, PropertyInfo[] props, FieldInfo[] fields, bool isStatic) + { + if (type.IsValueType) + { + il.Emit(OpCodes.Ldstr, "Write is not supported for structs"); + il.Emit(OpCodes.Newobj, typeof(NotSupportedException).GetConstructor(new Type[] { typeof(string) })); + il.Emit(OpCodes.Throw); + } + else + { + OpCode propName = isStatic ? OpCodes.Ldarg_1 : OpCodes.Ldarg_2, + target = isStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1, + value = isStatic ? OpCodes.Ldarg_2 : OpCodes.Ldarg_3; + LocalBuilder loc = type.IsValueType ? il.DeclareLocal(type) : null; + foreach (PropertyInfo prop in props) + { + if (prop.GetIndexParameters().Length != 0 || !prop.CanWrite) continue; + var setFn = prop.GetSetMethod(); + if (setFn == null) continue; //Mono + + Label next = il.DefineLabel(); + il.Emit(propName); + il.Emit(OpCodes.Ldstr, prop.Name); + il.EmitCall(OpCodes.Call, strinqEquals, null); + il.Emit(OpCodes.Brfalse_S, next); + // match: + il.Emit(target); + Cast(il, type, loc); + il.Emit(value); + Cast(il, prop.PropertyType, null); + il.EmitCall(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, setFn, null); + il.Emit(OpCodes.Ret); + // not match: + il.MarkLabel(next); + } + foreach (FieldInfo field in fields) + { + Label next = il.DefineLabel(); + il.Emit(propName); + il.Emit(OpCodes.Ldstr, field.Name); + il.EmitCall(OpCodes.Call, strinqEquals, null); + il.Emit(OpCodes.Brfalse_S, next); + // match: + il.Emit(target); + Cast(il, type, loc); + il.Emit(value); + Cast(il, field.FieldType, null); + il.Emit(OpCodes.Stfld, field); + il.Emit(OpCodes.Ret); + // not match: + il.MarkLabel(next); + } + il.Emit(OpCodes.Ldstr, "name"); + il.Emit(OpCodes.Newobj, typeof(ArgumentOutOfRangeException).GetConstructor(new Type[] { typeof(string) })); + il.Emit(OpCodes.Throw); + } + } + private static readonly MethodInfo strinqEquals = typeof(string).GetMethod("op_Equality", new Type[] { typeof(string), typeof(string) }); + + sealed class DelegateAccessor : TypeAccessor + { + private readonly Func getter; + private readonly Action setter; + private readonly Func ctor; + public DelegateAccessor(Func getter, Action setter, Func ctor) + { + this.getter = getter; + this.setter = setter; + this.ctor = ctor; + } + public override bool CreateNewSupported => ctor != null; + + public override object CreateNew() + { + return ctor != null ? ctor() : base.CreateNew(); + } + public override object this[object target, string name] + { + get => getter(target, name); + set => setter(target, name, value); + } + } + + private static bool IsFullyPublic(Type type) + { + while (type.IsNestedPublic) type = type.DeclaringType; + return type.IsPublic; + } + + static TypeAccessor CreateNew(Type type) + { + //if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type)) + //{ + // return DynamicAccessor.Singleton; + //} + + var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + ConstructorInfo ctor = null; + if (type.IsClass && !type.IsAbstract) + { + ctor = type.GetConstructor(Type.EmptyTypes); + } + ILGenerator il; + if (!IsFullyPublic(type)) + { + DynamicMethod dynGetter = new DynamicMethod(type.FullName + "_get", typeof(object), new Type[] { typeof(object), typeof(string) }, type, true), + dynSetter = new DynamicMethod(type.FullName + "_set", null, new Type[] { typeof(object), typeof(string), typeof(object) }, type, true); + WriteGetter(dynGetter.GetILGenerator(), type, props, fields, true); + WriteSetter(dynSetter.GetILGenerator(), type, props, fields, true); + DynamicMethod dynCtor = null; + if (ctor != null) + { + dynCtor = new DynamicMethod(type.FullName + "_ctor", typeof(object), Type.EmptyTypes, type, true); + il = dynCtor.GetILGenerator(); + il.Emit(OpCodes.Newobj, ctor); + il.Emit(OpCodes.Ret); + } + return new DelegateAccessor( + (Func)dynGetter.CreateDelegate(typeof(Func)), + (Action)dynSetter.CreateDelegate(typeof(Action)), + dynCtor == null ? null : (Func)dynCtor.CreateDelegate(typeof(Func))); + } + + // note this region is synchronized; only one is being created at a time so we don't need to stress about the builders + if (assembly == null) + { + AssemblyName name = new AssemblyName("FastMember_dynamic"); + assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); + module = assembly.DefineDynamicModule(name.Name); + } + TypeBuilder tb = module.DefineType("FastMember_dynamic." + type.Name + "_" + Interlocked.Increment(ref counter), + (typeof(TypeAccessor).Attributes | TypeAttributes.Sealed) & ~TypeAttributes.Abstract, typeof(TypeAccessor)); + + tb.DefineDefaultConstructor(MethodAttributes.Public); + PropertyInfo indexer = typeof(TypeAccessor).GetProperty("Item"); + MethodInfo baseGetter = indexer.GetGetMethod(), baseSetter = indexer.GetSetMethod(); + MethodBuilder body = tb.DefineMethod(baseGetter.Name, baseGetter.Attributes & ~MethodAttributes.Abstract, typeof(object), new Type[] { typeof(object), typeof(string) }); + il = body.GetILGenerator(); + WriteGetter(il, type, props, fields, false); + tb.DefineMethodOverride(body, baseGetter); + + body = tb.DefineMethod(baseSetter.Name, baseSetter.Attributes & ~MethodAttributes.Abstract, null, new Type[] { typeof(object), typeof(string), typeof(object) }); + il = body.GetILGenerator(); + WriteSetter(il, type, props, fields, false); + tb.DefineMethodOverride(body, baseSetter); + + if (ctor != null) + { + MethodInfo baseMethod = typeof(TypeAccessor).GetProperty("CreateNewSupported").GetGetMethod(); + body = tb.DefineMethod(baseMethod.Name, baseMethod.Attributes, typeof(bool), Type.EmptyTypes); + il = body.GetILGenerator(); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Ret); + tb.DefineMethodOverride(body, baseMethod); + + baseMethod = typeof(TypeAccessor).GetMethod("CreateNew"); + body = tb.DefineMethod(baseMethod.Name, baseMethod.Attributes, typeof(object), Type.EmptyTypes); + il = body.GetILGenerator(); + il.Emit(OpCodes.Newobj, ctor); + il.Emit(OpCodes.Ret); + tb.DefineMethodOverride(body, baseMethod); + } + + return (TypeAccessor)Activator.CreateInstance(tb.CreateType()); + } + + private static void Cast(ILGenerator il, Type type, LocalBuilder addr) + { + if (type == typeof(object)) { } + else if (type.IsValueType) + { + il.Emit(OpCodes.Unbox_Any, type); + if (addr != null) + { + il.Emit(OpCodes.Stloc, addr); + il.Emit(OpCodes.Ldloca_S, addr); + } + } + else + { + il.Emit(OpCodes.Castclass, type); + } + } + + /// + /// Get or set the value of a named member on the target instance + /// + public abstract object this[object target, string name] + { + get; + set; + } + } +} + +#endif diff --git a/src/ServiceStack.Text/PclExport.NetStandard.cs b/src/ServiceStack.Text/PclExport.NetStandard.cs new file mode 100644 index 000000000..e536fec6f --- /dev/null +++ b/src/ServiceStack.Text/PclExport.NetStandard.cs @@ -0,0 +1,542 @@ +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +#if NETCORE +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ServiceStack.Text; +using ServiceStack.Text.Common; +using ServiceStack.Text.Json; +using System.Globalization; +using System.Reflection; +using System.Net; + +using System.Collections.Specialized; + +namespace ServiceStack +{ + public class NetStandardPclExport : PclExport + { + public static NetStandardPclExport Provider = new(); + + static string[] allDateTimeFormats = { + "yyyy-MM-ddTHH:mm:ss.FFFFFFFzzzzzz", + "yyyy-MM-ddTHH:mm:ss.FFFFFFF", + "yyyy-MM-ddTHH:mm:ss.FFFFFFFZ", + "HH:mm:ss.FFFFFFF", + "HH:mm:ss.FFFFFFFZ", + "HH:mm:ss.FFFFFFFzzzzzz", + "yyyy-MM-dd", + "yyyy-MM-ddZ", + "yyyy-MM-ddzzzzzz", + "yyyy-MM", + "yyyy-MMZ", + "yyyy-MMzzzzzz", + "yyyy", + "yyyyZ", + "yyyyzzzzzz", + "--MM-dd", + "--MM-ddZ", + "--MM-ddzzzzzz", + "---dd", + "---ddZ", + "---ddzzzzzz", + "--MM--", + "--MM--Z", + "--MM--zzzzzz", + }; + + public NetStandardPclExport() + { + this.PlatformName = Platforms.NetStandard; + this.DirSep = Path.DirectorySeparatorChar; + } + + public override string ReadAllText(string filePath) + { + using (var reader = File.OpenText(filePath)) + { + return reader.ReadToEnd(); + } + } + + public override bool FileExists(string filePath) + { + return File.Exists(filePath); + } + + public override bool DirectoryExists(string dirPath) + { + return Directory.Exists(dirPath); + } + + public override void CreateDirectory(string dirPath) + { + Directory.CreateDirectory(dirPath); + } + + public override string[] GetFileNames(string dirPath, string searchPattern = null) + { + if (!Directory.Exists(dirPath)) + return TypeConstants.EmptyStringArray; + + return searchPattern != null + ? Directory.GetFiles(dirPath, searchPattern) + : Directory.GetFiles(dirPath); + } + + public override string[] GetDirectoryNames(string dirPath, string searchPattern = null) + { + if (!Directory.Exists(dirPath)) + return TypeConstants.EmptyStringArray; + + return searchPattern != null + ? Directory.GetDirectories(dirPath, searchPattern) + : Directory.GetDirectories(dirPath); + } + + public const string AppSettingsKey = "servicestack:license"; + public const string EnvironmentKey = "SERVICESTACK_LICENSE"; + + public override void RegisterLicenseFromConfig() + { + //Automatically register license key stored in is done in .NET Core AppHost + + //or SERVICESTACK_LICENSE Environment variable + var licenceKeyText = GetEnvironmentVariable(EnvironmentKey)?.Trim(); + if (!string.IsNullOrEmpty(licenceKeyText)) + { + LicenseUtils.RegisterLicense(licenceKeyText); + } + } + + public override string MapAbsolutePath(string relativePath, string appendPartialPathModifier) + { + if (relativePath.StartsWith("~")) + { + var assemblyDirectoryPath = AppContext.BaseDirectory; + + // Escape the assembly bin directory to the hostname directory + var hostDirectoryPath = appendPartialPathModifier != null + ? assemblyDirectoryPath + appendPartialPathModifier + : assemblyDirectoryPath; + + return Path.GetFullPath(relativePath.Replace("~", hostDirectoryPath)); + } + return relativePath; + } + + public static PclExport Configure() + { + Configure(Provider); + return Provider; + } + + public override string GetEnvironmentVariable(string name) => Environment.GetEnvironmentVariable(name); + + public override void WriteLine(string line) => Console.WriteLine(line); + + public override void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); + + public override void AddCompression(WebRequest webReq) + { + try + { + var httpReq = (HttpWebRequest)webReq; + httpReq.Headers[HttpRequestHeader.AcceptEncoding] = "gzip,deflate"; + httpReq.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + } + } + + public override void AddHeader(WebRequest webReq, string name, string value) + { + webReq.Headers[name] = value; + } + + public override Assembly[] GetAllAssemblies() + { + return AppDomain.CurrentDomain.GetAssemblies(); + } + + public override string GetAssemblyCodeBase(Assembly assembly) + { + var dll = typeof(PclExport).Assembly; + var pi = dll.GetType().GetProperty("CodeBase"); + var codeBase = pi?.GetProperty(dll).ToString(); + return codeBase; + } + + public override string GetAssemblyPath(Type source) + { + var codeBase = GetAssemblyCodeBase(source.GetTypeInfo().Assembly); + if (codeBase == null) + return null; + + var assemblyUri = new Uri(codeBase); + return assemblyUri.LocalPath; + } + + public override string GetAsciiString(byte[] bytes, int index, int count) + { + return System.Text.Encoding.ASCII.GetString(bytes, index, count); + } + + public override byte[] GetAsciiBytes(string str) + { + return System.Text.Encoding.ASCII.GetBytes(str); + } + + public override bool InSameAssembly(Type t1, Type t2) + { + return t1.Assembly == t2.Assembly; + } + + public override Type GetGenericCollectionType(Type type) + { + return type.GetTypeInfo().ImplementedInterfaces.FirstOrDefault(t => + t.IsGenericType + && t.GetGenericTypeDefinition() == typeof(ICollection<>)); + } + + public override DateTime ParseXsdDateTimeAsUtc(string dateTimeStr) + { + return DateTime.ParseExact(dateTimeStr, allDateTimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowLeadingWhite|DateTimeStyles.AllowTrailingWhite|DateTimeStyles.AdjustToUniversal) + .Prepare(parsedAsUtc: true); + } + + //public override DateTime ToStableUniversalTime(DateTime dateTime) + //{ + // // .Net 2.0 - 3.5 has an issue with DateTime.ToUniversalTime, but works ok with TimeZoneInfo.ConvertTimeToUtc. + // // .Net 4.0+ does this under the hood anyway. + // return TimeZoneInfo.ConvertTimeToUtc(dateTime); + //} + + public override ParseStringDelegate GetSpecializedCollectionParseMethod(Type type) + { + if (type == typeof(StringCollection)) + { + return v => ParseStringCollection(v.AsSpan()); + } + return null; + } + + public override ParseStringSpanDelegate GetSpecializedCollectionParseStringSpanMethod(Type type) + { + if (type == typeof(StringCollection)) + { + return ParseStringCollection; + } + return null; + } + + private static StringCollection ParseStringCollection(ReadOnlySpan value) where TSerializer : ITypeSerializer + { + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new StringCollection(); + + var result = new StringCollection(); + + if (value.Length > 0) + { + foreach (var item in DeserializeListWithElements.ParseStringList(value)) + { + result.Add(item); + } + } + + return result; + } + public override void SetUserAgent(HttpWebRequest httpReq, string value) + { + try + { + httpReq.UserAgent = value; + } + catch (Exception e) // API may have been removed by Xamarin's Linker + { + Tracer.Instance.WriteError(e); + } + } + + public override void SetContentLength(HttpWebRequest httpReq, long value) + { + try + { + httpReq.ContentLength = value; + } + catch (Exception e) // API may have been removed by Xamarin's Linker + { + Tracer.Instance.WriteError(e); + } + } + + public override void SetAllowAutoRedirect(HttpWebRequest httpReq, bool value) + { + try + { + httpReq.AllowAutoRedirect = value; + } + catch (Exception e) // API may have been removed by Xamarin's Linker + { + Tracer.Instance.WriteError(e); + } + } + + public override void SetKeepAlive(HttpWebRequest httpReq, bool value) + { + try + { + httpReq.KeepAlive = value; + } + catch (Exception e) // API may have been removed by Xamarin's Linker + { + Tracer.Instance.WriteError(e); + } + } + + public override void InitHttpWebRequest(HttpWebRequest httpReq, + long? contentLength = null, bool allowAutoRedirect = true, bool keepAlive = true) + { + httpReq.UserAgent = Env.ServerUserAgent; + httpReq.AllowAutoRedirect = allowAutoRedirect; + httpReq.KeepAlive = keepAlive; + + if (contentLength != null) + { + SetContentLength(httpReq, contentLength.Value); + } + } + + public override void Config(HttpWebRequest req, + bool? allowAutoRedirect = null, + TimeSpan? timeout = null, + TimeSpan? readWriteTimeout = null, + string userAgent = null, + bool? preAuthenticate = null) + { + try + { + //req.MaximumResponseHeadersLength = int.MaxValue; //throws "The message length limit was exceeded" exception + if (allowAutoRedirect.HasValue) + req.AllowAutoRedirect = allowAutoRedirect.Value; + + if (userAgent != null) + req.UserAgent = userAgent; + + if (readWriteTimeout.HasValue) req.ReadWriteTimeout = (int) readWriteTimeout.Value.TotalMilliseconds; + if (timeout.HasValue) req.Timeout = (int) timeout.Value.TotalMilliseconds; + + if (preAuthenticate.HasValue) + req.PreAuthenticate = preAuthenticate.Value; + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + } + } + + public override string GetStackTrace() => Environment.StackTrace; + + public static void InitForAot() + { + } + + internal class Poco + { + public string Dummy { get; set; } + } + + public override void RegisterForAot() + { + RegisterTypeForAot(); + + RegisterElement(); + + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + + //RegisterElement(); + + RegisterTypeForAot(); // used by DateTime + + // register built in structs + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + } + + public static void RegisterTypeForAot() + { + AotConfig.RegisterSerializers(); + } + + public static void RegisterQueryStringWriter() + { + var i = 0; + if (QueryStringWriter.WriteFn() != null) i++; + } + + public static int RegisterElement() + { + var i = 0; + i += AotConfig.RegisterSerializers(); + AotConfig.RegisterElement(); + AotConfig.RegisterElement(); + return i; + } + + internal class AotConfig + { + internal static JsReader jsonReader; + internal static JsWriter jsonWriter; + internal static JsReader jsvReader; + internal static JsWriter jsvWriter; + internal static JsonTypeSerializer jsonSerializer; + internal static Text.Jsv.JsvTypeSerializer jsvSerializer; + + static AotConfig() + { + jsonSerializer = new JsonTypeSerializer(); + jsvSerializer = new Text.Jsv.JsvTypeSerializer(); + jsonReader = new JsReader(); + jsonWriter = new JsWriter(); + jsvReader = new JsReader(); + jsvWriter = new JsWriter(); + } + + internal static int RegisterSerializers() + { + var i = 0; + i += Register(); + if (jsonSerializer.GetParseFn() != null) i++; + if (jsonSerializer.GetWriteFn() != null) i++; + if (jsonReader.GetParseFn() != null) i++; + if (jsonWriter.GetWriteFn() != null) i++; + + i += Register(); + if (jsvSerializer.GetParseFn() != null) i++; + if (jsvSerializer.GetWriteFn() != null) i++; + if (jsvReader.GetParseFn() != null) i++; + if (jsvWriter.GetWriteFn() != null) i++; + + //RegisterCsvSerializer(); + RegisterQueryStringWriter(); + return i; + } + + internal static void RegisterCsvSerializer() + { + CsvSerializer.WriteFn(); + CsvSerializer.WriteObject(null, null); + CsvWriter.Write(null, default(IEnumerable)); + CsvWriter.WriteRow(null, default(T)); + } + + public static ParseStringDelegate GetParseFn(Type type) + { + var parseFn = JsonTypeSerializer.Instance.GetParseFn(type); + return parseFn; + } + + internal static int Register() where TSerializer : ITypeSerializer + { + var i = 0; + + if (JsonWriter.WriteFn() != null) i++; + if (JsonWriter.Instance.GetWriteFn() != null) i++; + if (JsonReader.Instance.GetParseFn() != null) i++; + if (JsonReader.Parse(default(ReadOnlySpan)) != null) i++; + if (JsonReader.GetParseFn() != null) i++; + //if (JsWriter.GetTypeSerializer().GetWriteFn() != null) i++; + if (new List() != null) i++; + if (new T[0] != null) i++; + + JsConfig.ExcludeTypeInfo = false; + + if (JsConfig.OnDeserializedFn != null) i++; + if (JsConfig.HasDeserializeFn) i++; + if (JsConfig.SerializeFn != null) i++; + if (JsConfig.DeSerializeFn != null) i++; + //JsConfig.SerializeFn = arg => ""; + //JsConfig.DeSerializeFn = arg => default(T); + if (TypeConfig.Properties != null) i++; + + WriteListsOfElements.WriteList(null, null); + WriteListsOfElements.WriteIList(null, null); + WriteListsOfElements.WriteEnumerable(null, null); + WriteListsOfElements.WriteListValueType(null, null); + WriteListsOfElements.WriteIListValueType(null, null); + WriteListsOfElements.WriteGenericArrayValueType(null, null); + WriteListsOfElements.WriteArray(null, null); + + TranslateListWithElements.LateBoundTranslateToGenericICollection(null, null); + TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, null); + + QueryStringWriter.WriteObject(null, null); + return i; + } + + internal static void RegisterElement() where TSerializer : ITypeSerializer + { + DeserializeDictionary.ParseDictionary(default(ReadOnlySpan), null, null, null); + DeserializeDictionary.ParseDictionary(default(ReadOnlySpan), null, null, null); + + ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); + ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); + + // Include List deserialisations from the Register<> method above. This solves issue where List properties on responses deserialise to null. + // No idea why this is happening because there is no visible exception raised. Suspect IOS is swallowing an AOT exception somewhere. + DeserializeArrayWithElements.ParseGenericArray(default(ReadOnlySpan), null); + DeserializeListWithElements.ParseGenericList(default(ReadOnlySpan), null, null); + + // Cannot use the line below for some unknown reason - when trying to compile to run on device, mtouch bombs during native code compile. + // Something about this line or its inner workings is offensive to mtouch. Luckily this was not needed for my List issue. + // DeserializeCollection.ParseCollection(null, null, null); + + TranslateListWithElements.LateBoundTranslateToGenericICollection(null, typeof(List)); + TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, typeof(List)); + } + } + + } +} + +#endif diff --git a/src/ServiceStack.Text/PclExport.cs b/src/ServiceStack.Text/PclExport.cs new file mode 100644 index 000000000..9ba3c9cd1 --- /dev/null +++ b/src/ServiceStack.Text/PclExport.cs @@ -0,0 +1,389 @@ +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using ServiceStack.Text; +using ServiceStack.Text.Common; + +namespace ServiceStack +{ + public abstract class PclExport + { + public static class Platforms + { + public const string NetStandard = "NETStd"; + public const string Net6 = "NET6"; + public const string NetFX = "NETFX"; + } + + public static PclExport Instance = +#if NETFX + new NetFxPclExport() +#elif NETSTANDARD2_0 + new NetStandardPclExport() +#elif NETCORE || NET6_0_OR_GREATER + new Net6PclExport() +#endif + ; + + public static ReflectionOptimizer Reflection => ReflectionOptimizer.Instance; + + static PclExport() {} + + public static bool ConfigureProvider(string typeName) + { + var type = Type.GetType(typeName); + if (type == null) + return false; + + var mi = type.GetMethod("Configure"); + if (mi != null) + { + mi.Invoke(null, new object[0]); + } + + return true; + } + + public static void Configure(PclExport instance) + { + Instance = instance ?? Instance; + + if (Instance != null && Instance.EmptyTask == null) + { + var tcs = new TaskCompletionSource(); + tcs.SetResult(null); + Instance.EmptyTask = tcs.Task; + } + } + + public Task EmptyTask; + + public char DirSep = '\\'; + + public char AltDirSep = '/'; + + public static readonly char[] DirSeps = { '\\', '/' }; + + public string PlatformName = "Unknown"; + + public RegexOptions RegexOptions = RegexOptions.None; + + public StringComparison InvariantComparison = StringComparison.Ordinal; + + public StringComparison InvariantComparisonIgnoreCase = StringComparison.OrdinalIgnoreCase; + + public StringComparer InvariantComparer = StringComparer.Ordinal; + + public StringComparer InvariantComparerIgnoreCase = StringComparer.OrdinalIgnoreCase; + + public abstract string ReadAllText(string filePath); + + // HACK: The only way to detect anonymous types right now. + public virtual bool IsAnonymousType(Type type) + { + return type.IsGenericType && type.Name.Contains("AnonymousType") + && (type.Name.StartsWith("<>", StringComparison.Ordinal) || type.Name.StartsWith("VB$", StringComparison.Ordinal)); + } + + public virtual string ToInvariantUpper(char value) + { + return value.ToString().ToUpperInvariant(); + } + + public virtual bool FileExists(string filePath) + { + return false; + } + + public virtual bool DirectoryExists(string dirPath) + { + return false; + } + + public virtual void CreateDirectory(string dirPath) + { + } + + public virtual void RegisterLicenseFromConfig() + { + } + + public virtual string GetEnvironmentVariable(string name) + { + return null; + } + + public virtual string[] GetFileNames(string dirPath, string searchPattern = null) + { + return TypeConstants.EmptyStringArray; + } + + public virtual string[] GetDirectoryNames(string dirPath, string searchPattern = null) + { + return TypeConstants.EmptyStringArray; + } + + public virtual void WriteLine(string line) + { + } + + public virtual void WriteLine(string line, params object[] args) + { + } + + public virtual void Config(HttpWebRequest req, + bool? allowAutoRedirect = null, + TimeSpan? timeout = null, + TimeSpan? readWriteTimeout = null, + string userAgent = null, + bool? preAuthenticate = null) + { + } + + public virtual void AddCompression(WebRequest webRequest) + { + } + + public virtual Stream GetRequestStream(WebRequest webRequest) + { + var async = webRequest.GetRequestStreamAsync(); + async.Wait(); + return async.Result; + } + + public virtual WebResponse GetResponse(WebRequest webRequest) + { + try + { + var async = webRequest.GetResponseAsync(); + async.Wait(); + return async.Result; + } + catch (Exception ex) + { + throw ex.UnwrapIfSingleException(); + } + } + + public virtual Task GetResponseAsync(WebRequest webRequest) + { + return webRequest.GetResponseAsync(); + } + + public virtual bool IsDebugBuild(Assembly assembly) + { + return assembly.AllAttributes() + .Any(x => x.GetType().Name == "DebuggableAttribute"); + } + + public virtual string MapAbsolutePath(string relativePath, string appendPartialPathModifier) + { + return relativePath; + } + + public virtual Assembly LoadAssembly(string assemblyPath) + { + return null; + } + + public virtual void AddHeader(WebRequest webReq, string name, string value) + { + webReq.Headers[name] = value; + } + + public virtual void SetUserAgent(HttpWebRequest httpReq, string value) + { + httpReq.Headers[HttpRequestHeader.UserAgent] = value; + } + + public virtual void SetContentLength(HttpWebRequest httpReq, long value) + { + httpReq.Headers[HttpRequestHeader.ContentLength] = value.ToString(); + } + + public virtual void SetAllowAutoRedirect(HttpWebRequest httpReq, bool value) + { + } + + public virtual void SetKeepAlive(HttpWebRequest httpReq, bool value) + { + } + + public virtual Assembly[] GetAllAssemblies() + { + return new Assembly[0]; + } + + public virtual Type FindType(string typeName, string assemblyName) + { + return null; + } + + public virtual string GetAssemblyCodeBase(Assembly assembly) + { + return assembly.FullName; + } + + public virtual string GetAssemblyPath(Type source) + { + return null; + } + + public virtual string GetAsciiString(byte[] bytes) + { + return GetAsciiString(bytes, 0, bytes.Length); + } + + public virtual string GetAsciiString(byte[] bytes, int index, int count) + { + return Encoding.UTF8.GetString(bytes, index, count); + } + + public virtual byte[] GetAsciiBytes(string str) + { + return Encoding.UTF8.GetBytes(str); + } + + public virtual Encoding GetUTF8Encoding(bool emitBom=false) + { + return new UTF8Encoding(emitBom); + } + + + [Obsolete("ReflectionOptimizer.CreateGetter")] + public GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) => ReflectionOptimizer.Instance.CreateGetter(propertyInfo); + + [Obsolete("ReflectionOptimizer.CreateGetter")] + public GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) => ReflectionOptimizer.Instance.CreateGetter(propertyInfo); + + [Obsolete("ReflectionOptimizer.CreateSetter")] + public SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) => ReflectionOptimizer.Instance.CreateSetter(propertyInfo); + + [Obsolete("ReflectionOptimizer.CreateSetter")] + public SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) => ReflectionOptimizer.Instance.CreateSetter(propertyInfo); + + + [Obsolete("ReflectionOptimizer.CreateGetter")] + public virtual GetMemberDelegate CreateGetter(FieldInfo fieldInfo) => ReflectionOptimizer.Instance.CreateGetter(fieldInfo); + + [Obsolete("ReflectionOptimizer.CreateGetter")] + public virtual GetMemberDelegate CreateGetter(FieldInfo fieldInfo) => ReflectionOptimizer.Instance.CreateGetter(fieldInfo); + + [Obsolete("ReflectionOptimizer.CreateSetter")] + public virtual SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => ReflectionOptimizer.Instance.CreateSetter(fieldInfo); + + [Obsolete("ReflectionOptimizer.CreateSetter")] + public virtual SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => ReflectionOptimizer.Instance.CreateSetter(fieldInfo); + + + public virtual bool InSameAssembly(Type t1, Type t2) + { + return t1.AssemblyQualifiedName != null && t1.AssemblyQualifiedName.Equals(t2.AssemblyQualifiedName); + } + + public virtual Type GetGenericCollectionType(Type type) + { + return type.GetInterfaces() + .FirstOrDefault(t => t.IsGenericType + && t.GetGenericTypeDefinition() == typeof(ICollection<>)); + } + + public virtual string ToXsdDateTimeString(DateTime dateTime) + { + return System.Xml.XmlConvert.ToString(dateTime.ToStableUniversalTime(), DateTimeSerializer.XsdDateTimeFormat); + } + + public virtual string ToLocalXsdDateTimeString(DateTime dateTime) + { + return System.Xml.XmlConvert.ToString(dateTime, DateTimeSerializer.XsdDateTimeFormat); + } + + public virtual DateTime ParseXsdDateTime(string dateTimeStr) + { + return System.Xml.XmlConvert.ToDateTimeOffset(dateTimeStr).DateTime; + } + + public virtual DateTime ParseXsdDateTimeAsUtc(string dateTimeStr) + { + return DateTimeSerializer.ParseManual(dateTimeStr, DateTimeKind.Utc) + ?? DateTime.ParseExact(dateTimeStr, DateTimeSerializer.XsdDateTimeFormat, CultureInfo.InvariantCulture); + } + + public virtual DateTime ToStableUniversalTime(DateTime dateTime) + { + // Silverlight 3, 4 and 5 all work ok with DateTime.ToUniversalTime, but have no TimeZoneInfo.ConverTimeToUtc implementation. + return dateTime.ToUniversalTime(); + } + + public virtual ParseStringDelegate GetDictionaryParseMethod(Type type) + where TSerializer : ITypeSerializer => null; + + public virtual ParseStringSpanDelegate GetDictionaryParseStringSpanMethod(Type type) + where TSerializer : ITypeSerializer => null; + + public virtual ParseStringDelegate GetSpecializedCollectionParseMethod(Type type) + where TSerializer : ITypeSerializer => null; + + public virtual ParseStringSpanDelegate GetSpecializedCollectionParseStringSpanMethod(Type type) + where TSerializer : ITypeSerializer => null; + + public virtual ParseStringDelegate GetJsReaderParseMethod(Type type) + where TSerializer : ITypeSerializer => null; + + public virtual ParseStringSpanDelegate GetJsReaderParseStringSpanMethod(Type type) + where TSerializer : ITypeSerializer => null; + + + public virtual void InitHttpWebRequest(HttpWebRequest httpReq, + long? contentLength = null, bool allowAutoRedirect = true, bool keepAlive = true) {} + + public virtual void CloseStream(Stream stream) + { + stream.Flush(); + } + + public virtual void ResetStream(Stream stream) + { + stream.Position = 0; + } + + public virtual LicenseKey VerifyLicenseKeyText(string licenseKeyText) + { + return licenseKeyText.ToLicenseKey(); + } + + public virtual LicenseKey VerifyLicenseKeyTextFallback(string licenseKeyText) + { + return licenseKeyText.ToLicenseKeyFallback(); + } + + public virtual void BeginThreadAffinity() {} + public virtual void EndThreadAffinity() {} + + public virtual DataContractAttribute GetWeakDataContract(Type type) => null; + public virtual DataMemberAttribute GetWeakDataMember(PropertyInfo pi) => null; + public virtual DataMemberAttribute GetWeakDataMember(FieldInfo pi) => null; + + public virtual void RegisterForAot() {} + public virtual string GetStackTrace() => null; + + public virtual Task WriteAndFlushAsync(Stream stream, byte[] bytes) + { + stream.Write(bytes, 0, bytes.Length); + stream.Flush(); + return EmptyTask; + } + } + +} \ No newline at end of file diff --git a/src/ServiceStack.Text/PlatformExtensions.cs b/src/ServiceStack.Text/PlatformExtensions.cs new file mode 100644 index 000000000..aabf7f366 --- /dev/null +++ b/src/ServiceStack.Text/PlatformExtensions.cs @@ -0,0 +1,1160 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Threading; +using System.Xml; +using ServiceStack.Text; + +namespace ServiceStack +{ + public static class PlatformExtensions + { + [Obsolete("Use type.IsInterface")] + public static bool IsInterface(this Type type) => type.IsInterface; + + [Obsolete("Use type.IsArray")] + public static bool IsArray(this Type type) => type.IsArray; + + [Obsolete("Use type.IsValueType")] + public static bool IsValueType(this Type type) => type.IsValueType; + + [Obsolete("Use type.IsGenericType")] + public static bool IsGeneric(this Type type) => type.IsGenericType; + + [Obsolete("Use type.BaseType")] + public static Type BaseType(this Type type) => type.BaseType; + + [Obsolete("Use pi.ReflectedType")] + public static Type ReflectedType(this PropertyInfo pi) => pi.ReflectedType; + + [Obsolete("Use fi.ReflectedType")] + public static Type ReflectedType(this FieldInfo fi) => fi.ReflectedType; + + [Obsolete("Use type.GetGenericTypeDefinition()")] + public static Type GenericTypeDefinition(this Type type) => type.GetGenericTypeDefinition(); + + [Obsolete("Use type.GetInterfaces()")] + public static Type[] GetTypeInterfaces(this Type type) => type.GetInterfaces(); + + [Obsolete("Use type.GetGenericArguments()")] + public static Type[] GetTypeGenericArguments(this Type type) => type.GetGenericArguments(); + + [Obsolete("Use type.GetConstructor(Type.EmptyTypes)")] + public static ConstructorInfo GetEmptyConstructor(this Type type) => type.GetConstructor(Type.EmptyTypes); + + [Obsolete("Use type.GetConstructors()")] + public static IEnumerable GetAllConstructors(this Type type) => type.GetConstructors(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static PropertyInfo[] GetTypesPublicProperties(this Type subType) + { + return subType.GetProperties( + BindingFlags.FlattenHierarchy | + BindingFlags.Public | + BindingFlags.Instance); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static PropertyInfo[] GetTypesProperties(this Type subType) + { + return subType.GetProperties( + BindingFlags.FlattenHierarchy | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Instance); + } + + [Obsolete("Use type.Assembly")] + public static Assembly GetAssembly(this Type type) => type.Assembly; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FieldInfo[] Fields(this Type type) + { + return type.GetFields( + BindingFlags.FlattenHierarchy | + BindingFlags.Instance | + BindingFlags.Static | + BindingFlags.Public | + BindingFlags.NonPublic); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyInfo[] Properties(this Type type) + { + return type.GetProperties( + BindingFlags.FlattenHierarchy | + BindingFlags.Instance | + BindingFlags.Static | + BindingFlags.Public | + BindingFlags.NonPublic); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FieldInfo[] GetAllFields(this Type type) => type.IsInterface ? TypeConstants.EmptyFieldInfoArray : type.Fields(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FieldInfo[] GetPublicFields(this Type type) => type.IsInterface + ? TypeConstants.EmptyFieldInfoArray + : type.GetFields(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance).ToArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemberInfo[] GetPublicMembers(this Type type) => type.GetMembers(BindingFlags.Public | BindingFlags.Instance); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemberInfo[] GetAllPublicMembers(this Type type) => + type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MethodInfo GetStaticMethod(this Type type, string methodName) => + type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MethodInfo GetInstanceMethod(this Type type, string methodName) => + type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + [Obsolete("Use fn.Method")] + public static MethodInfo Method(this Delegate fn) => fn.Method; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttribute(this Type type) => type.AllAttributes().Any(x => x.GetType() == typeof(T)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeOf(this Type type) => type.AllAttributes().Any(x => x is T); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttribute(this PropertyInfo pi) => pi.AllAttributes().Any(x => x.GetType() == typeof(T)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeOf(this PropertyInfo pi) => pi.AllAttributesLazy().Any(x => x is T); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttribute(this FieldInfo fi) => fi.AllAttributes().Any(x => x.GetType() == typeof(T)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeOf(this FieldInfo fi) => fi.AllAttributes().Any(x => x is T); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttribute(this MethodInfo mi) => mi.AllAttributes().Any(x => x.GetType() == typeof(T)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeOf(this MethodInfo mi) => mi.AllAttributes().Any(x => x is T); + + private static readonly ConcurrentDictionary, bool> hasAttributeCache = new(); + public static bool HasAttributeCached(this MemberInfo memberInfo) + { + var key = new Tuple(memberInfo, typeof(T)); + if (hasAttributeCache.TryGetValue(key , out var hasAttr)) + return hasAttr; + + hasAttr = memberInfo is Type t + ? t.AllAttributes().Any(x => x.GetType() == typeof(T)) + : memberInfo is PropertyInfo pi + ? pi.AllAttributes().Any(x => x.GetType() == typeof(T)) + : memberInfo is FieldInfo fi + ? fi.AllAttributes().Any(x => x.GetType() == typeof(T)) + : memberInfo is MethodInfo mi + ? mi.AllAttributes().Any(x => x.GetType() == typeof(T)) + : throw new NotSupportedException(memberInfo.GetType().Name); + + hasAttributeCache[key] = hasAttr; + + return hasAttr; + } + + private static readonly ConcurrentDictionary, bool> hasAttributeOfCache = new ConcurrentDictionary, bool>(); + public static bool HasAttributeOfCached(this MemberInfo memberInfo) + { + var key = new Tuple(memberInfo, typeof(T)); + if (hasAttributeOfCache.TryGetValue(key , out var hasAttr)) + return hasAttr; + + hasAttr = memberInfo is Type t + ? t.AllAttributes().Any(x => x is T) + : memberInfo is PropertyInfo pi + ? pi.AllAttributes().Any(x => x is T) + : memberInfo is FieldInfo fi + ? fi.AllAttributes().Any(x => x is T) + : memberInfo is MethodInfo mi + ? mi.AllAttributes().Any(x => x is T) + : throw new NotSupportedException(memberInfo.GetType().Name); + + hasAttributeOfCache[key] = hasAttr; + + return hasAttr; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeNamed(this Type type, string name) + { + var normalizedAttr = name.Replace("Attribute", "").ToLower(); + return type.AllAttributes().Any(x => x.GetType().Name.Replace("Attribute", "").ToLower() == normalizedAttr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeNamed(this PropertyInfo pi, string name) + { + var normalizedAttr = name.Replace("Attribute", "").ToLower(); + return pi.AllAttributesLazy().Any(x => x.GetType().Name.Replace("Attribute", "").ToLower() == normalizedAttr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeNamed(this FieldInfo fi, string name) + { + var normalizedAttr = name.Replace("Attribute", "").ToLower(); + return fi.AllAttributes().Any(x => x.GetType().Name.Replace("Attribute", "").ToLower() == normalizedAttr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeNamed(this MemberInfo mi, string name) + { + var normalizedAttr = name.Replace("Attribute", "").ToLower(); + return mi.AllAttributes().Any(x => x.GetType().Name.Replace("Attribute", "").ToLower() == normalizedAttr); + } + + const string DataContract = "DataContractAttribute"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDto(this Type type) + { + if (type == null) + return false; + + return !Env.IsMono + ? type.HasAttribute() + : type.GetCustomAttributes(true).Any(x => x.GetType().Name == DataContract); + } + + [Obsolete("Use pi.GetGetMethod(nonPublic)")] + public static MethodInfo PropertyGetMethod(this PropertyInfo pi, bool nonPublic = false) => pi.GetGetMethod(nonPublic); + + [Obsolete("Use type.GetInterfaces()")] + public static Type[] Interfaces(this Type type) => type.GetInterfaces(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyInfo[] AllProperties(this Type type) => + type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + + //Should only register Runtime Attributes on StartUp, So using non-ThreadSafe Dictionary is OK + static Dictionary> propertyAttributesMap = new(); + + static Dictionary> typeAttributesMap = new(); + + public static void ClearRuntimeAttributes() + { + propertyAttributesMap = new Dictionary>(); + typeAttributesMap = new Dictionary>(); + } + + internal static string UniqueKey(this PropertyInfo pi) + { + if (pi.DeclaringType == null) + throw new ArgumentException("Property '{0}' has no DeclaringType".Fmt(pi.Name)); + + return pi.DeclaringType.Namespace + "." + pi.DeclaringType.Name + "." + pi.Name; + } + + public static Type AddAttributes(this Type type, params Attribute[] attrs) + { + if (!typeAttributesMap.TryGetValue(type, out var typeAttrs)) + typeAttributesMap[type] = typeAttrs = new List(); + + typeAttrs.AddRange(attrs); + return type; + } + + /// + /// Add a Property attribute at runtime. + /// Not threadsafe, should only add attributes on Startup. + /// + public static PropertyInfo AddAttributes(this PropertyInfo propertyInfo, params Attribute[] attrs) + { + var key = propertyInfo.UniqueKey(); + if (!propertyAttributesMap.TryGetValue(key, out var propertyAttrs)) + propertyAttributesMap[key] = propertyAttrs = new List(); + + propertyAttrs.AddRange(attrs); + + return propertyInfo; + } + + /// + /// Add a Property attribute at runtime. + /// Not threadsafe, should only add attributes on Startup. + /// + public static PropertyInfo ReplaceAttribute(this PropertyInfo propertyInfo, Attribute attr) + { + var key = propertyInfo.UniqueKey(); + + if (!propertyAttributesMap.TryGetValue(key, out var propertyAttrs)) + propertyAttributesMap[key] = propertyAttrs = new List(); + + propertyAttrs.RemoveAll(x => x.GetType() == attr.GetType()); + + propertyAttrs.Add(attr); + + return propertyInfo; + } + + public static List GetAttributes(this PropertyInfo propertyInfo) + { + return !propertyAttributesMap.TryGetValue(propertyInfo.UniqueKey(), out var propertyAttrs) + ? new List() + : propertyAttrs.OfType().ToList(); + } + + public static List GetAttributes(this PropertyInfo propertyInfo) + { + return !propertyAttributesMap.TryGetValue(propertyInfo.UniqueKey(), out var propertyAttrs) + ? new List() + : propertyAttrs.ToList(); + } + + public static List GetAttributes(this PropertyInfo propertyInfo, Type attrType) + { + return !propertyAttributesMap.TryGetValue(propertyInfo.UniqueKey(), out var propertyAttrs) + ? new List() + : propertyAttrs.Where(x => attrType.IsInstanceOf(x.GetType())).ToList(); + } + + public static object[] AllAttributes(this PropertyInfo propertyInfo) + { + var attrs = propertyInfo.GetCustomAttributes(true); + var runtimeAttrs = propertyInfo.GetAttributes(); + if (runtimeAttrs.Count == 0) + return attrs; + + runtimeAttrs.AddRange(attrs.Cast()); + return runtimeAttrs.Cast().ToArray(); + } + + public static IEnumerable AllAttributesLazy(this PropertyInfo propertyInfo) + { + var attrs = propertyInfo.GetCustomAttributes(true); + var runtimeAttrs = propertyInfo.GetAttributes(); + foreach (var attr in runtimeAttrs) + { + yield return attr; + } + foreach (var attr in attrs) + { + yield return attr; + } + } + + public static object[] AllAttributes(this PropertyInfo propertyInfo, Type attrType) + { + var attrs = propertyInfo.GetCustomAttributes(attrType, true); + var runtimeAttrs = propertyInfo.GetAttributes(attrType); + if (runtimeAttrs.Count == 0) + return attrs; + + runtimeAttrs.AddRange(attrs.Cast()); + return runtimeAttrs.Cast().ToArray(); + } + + public static IEnumerable AllAttributesLazy(this PropertyInfo propertyInfo, Type attrType) + { + foreach (var attr in propertyInfo.GetAttributes(attrType)) + { + yield return attr; + } + foreach (var attr in propertyInfo.GetCustomAttributes(attrType, true)) + { + yield return attr; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object[] AllAttributes(this ParameterInfo paramInfo) => paramInfo.GetCustomAttributes(true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object[] AllAttributes(this FieldInfo fieldInfo) => fieldInfo.GetCustomAttributes(true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object[] AllAttributes(this MemberInfo memberInfo) => memberInfo.GetCustomAttributes(true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object[] AllAttributes(this ParameterInfo paramInfo, Type attrType) => paramInfo.GetCustomAttributes(attrType, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object[] AllAttributes(this MemberInfo memberInfo, Type attrType) + { + var prop = memberInfo as PropertyInfo; + return prop != null + ? prop.AllAttributes(attrType) + : memberInfo.GetCustomAttributes(attrType, true); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object[] AllAttributes(this FieldInfo fieldInfo, Type attrType) => fieldInfo.GetCustomAttributes(attrType, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object[] AllAttributes(this Type type) => type.GetCustomAttributes(true).Union(type.GetRuntimeAttributes()).ToArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable AllAttributesLazy(this Type type) + { + foreach (var attr in type.GetRuntimeAttributes()) + yield return attr; + foreach (var attr in type.GetCustomAttributes(true)) + yield return attr; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object[] AllAttributes(this Type type, Type attrType) => + type.GetCustomAttributes(attrType, true).Union(type.GetRuntimeAttributes(attrType)).ToArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object[] AllAttributes(this Assembly assembly) => assembly.GetCustomAttributes(true).ToArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TAttr[] AllAttributes(this ParameterInfo pi) => pi.AllAttributes(typeof(TAttr)).Cast().ToArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TAttr[] AllAttributes(this MemberInfo mi) => mi.AllAttributes(typeof(TAttr)).Cast().ToArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TAttr[] AllAttributes(this FieldInfo fi) => fi.AllAttributes(typeof(TAttr)).Cast().ToArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TAttr[] AllAttributes(this PropertyInfo pi) => pi.AllAttributes(typeof(TAttr)).Cast().ToArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable AllAttributesLazy(this PropertyInfo pi) => pi.AllAttributesLazy(typeof(TAttr)).Cast(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static IEnumerable GetRuntimeAttributes(this Type type) => typeAttributesMap.TryGetValue(type, out var attrs) + ? attrs.OfType() + : new List(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static IEnumerable GetRuntimeAttributes(this Type type, Type attrType = null) => typeAttributesMap.TryGetValue(type, out var attrs) + ? attrs.Where(x => attrType == null || attrType.IsInstanceOf(x.GetType())) + : new List(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TAttr[] AllAttributes(this Type type) + { + return type.GetCustomAttributes(typeof(TAttr), true) + .OfType() + .Union(type.GetRuntimeAttributes()) + .ToArray(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable AllAttributesLazy(this Type type) + { + foreach (var attr in type.GetCustomAttributes(typeof(TAttr), true).OfType()) + { + yield return attr; + } + foreach (var attr in type.GetRuntimeAttributes()) + { + yield return attr; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TAttr FirstAttribute(this Type type) where TAttr : class + { + return (TAttr)type.GetCustomAttributes(typeof(TAttr), true) + .FirstOrDefault() + ?? type.GetRuntimeAttributes().FirstOrDefault(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TAttribute FirstAttribute(this MemberInfo memberInfo) + { + return memberInfo.AllAttributes().FirstOrDefault(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TAttribute FirstAttribute(this ParameterInfo paramInfo) + { + return paramInfo.AllAttributes().FirstOrDefault(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TAttribute FirstAttribute(this PropertyInfo propertyInfo) => + propertyInfo.AllAttributesLazy().FirstOrDefault(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Type FirstGenericTypeDefinition(this Type type) + { + var genericType = type.FirstGenericType(); + return genericType != null ? genericType.GetGenericTypeDefinition() : null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDynamic(this Assembly assembly) => ReflectionOptimizer.Instance.IsDynamic(assembly); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MethodInfo GetStaticMethod(this Type type, string methodName, Type[] types) + { + return types == null + ? type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static) + : type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, null, types, null); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MethodInfo GetMethodInfo(this Type type, string methodName, Type[] types = null) => types == null + ? type.GetMethod(methodName) + : type.GetMethod(methodName, types); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object InvokeMethod(this Delegate fn, object instance, object[] parameters = null) => + fn.Method.Invoke(instance, parameters ?? new object[] { }); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FieldInfo GetPublicStaticField(this Type type, string fieldName) => + type.GetField(fieldName, BindingFlags.Public | BindingFlags.Static); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Delegate MakeDelegate(this MethodInfo mi, Type delegateType, bool throwOnBindFailure = true) => + Delegate.CreateDelegate(delegateType, mi, throwOnBindFailure); + + [Obsolete("Use type.GetGenericArguments()")] + public static Type[] GenericTypeArguments(this Type type) => type.GetGenericArguments(); + + [Obsolete("Use type.GetConstructors()")] + public static ConstructorInfo[] DeclaredConstructors(this Type type) => type.GetConstructors(); + + [Obsolete("Use type.IsAssignableFrom(fromType)")] + public static bool AssignableFrom(this Type type, Type fromType) => type.IsAssignableFrom(fromType); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsStandardClass(this Type type) => type.IsClass && !type.IsAbstract && !type.IsInterface; + + [Obsolete("Use type.IsAbstract")] + public static bool IsAbstract(this Type type) => type.IsAbstract; + + [Obsolete("Use type.GetProperty(propertyName)")] + public static PropertyInfo GetPropertyInfo(this Type type, string propertyName) => type.GetProperty(propertyName); + + [Obsolete("Use type.GetField(fieldName)")] + public static FieldInfo GetFieldInfo(this Type type, string fieldName) => type.GetField(fieldName); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FieldInfo[] GetWritableFields(this Type type) => + type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetField); + + [Obsolete("Use pi.GetSetMethod(nonPublic)")] + public static MethodInfo SetMethod(this PropertyInfo pi, bool nonPublic = true) => + pi.GetSetMethod(nonPublic); + + [Obsolete("Use pi.GetGetMethod(nonPublic)")] + public static MethodInfo GetMethodInfo(this PropertyInfo pi, bool nonPublic = true) => + pi.GetGetMethod(nonPublic); + + [Obsolete("Use type.IsInstanceOfType(instance)")] + public static bool InstanceOfType(this Type type, object instance) => type.IsInstanceOfType(instance); + + [Obsolete("Use type.IsAssignableFrom(fromType)")] + public static bool IsAssignableFromType(this Type type, Type fromType) => type.IsAssignableFrom(fromType); + + [Obsolete("Use type.IsClass")] + public static bool IsClass(this Type type) => type.IsClass; + + [Obsolete("Use type.IsEnum")] + public static bool IsEnum(this Type type) => type.IsEnum; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEnumFlags(this Type type) => type.IsEnum && type.FirstAttribute() != null; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsUnderlyingEnum(this Type type) => type.IsEnum || type.UnderlyingSystemType.IsEnum; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MethodInfo[] GetInstanceMethods(this Type type) => + type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + [Obsolete("Use type.GetMethods()")] + public static MethodInfo[] GetMethodInfos(this Type type) => type.GetMethods(); + + [Obsolete("Use type.GetProperties()")] + public static PropertyInfo[] GetPropertyInfos(this Type type) => type.GetProperties(); + + [Obsolete("Use type.IsGenericTypeDefinition")] + public static bool IsGenericTypeDefinition(this Type type) => type.IsGenericTypeDefinition; + + [Obsolete("Use type.IsGenericType")] + public static bool IsGenericType(this Type type) => type.IsGenericType; + + [Obsolete("Use type.ContainsGenericParameters")] + public static bool ContainsGenericParameters(this Type type) => type.ContainsGenericParameters; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetDeclaringTypeName(this Type type) + { + if (type.DeclaringType != null) + return type.DeclaringType.Name; + + if (type.ReflectedType != null) + return type.ReflectedType.Name; + + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetDeclaringTypeName(this MemberInfo mi) + { + if (mi.DeclaringType != null) + return mi.DeclaringType.Name; + + return mi.ReflectedType.Name; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Delegate CreateDelegate(this MethodInfo methodInfo, Type delegateType) + { + return Delegate.CreateDelegate(delegateType, methodInfo); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Delegate CreateDelegate(this MethodInfo methodInfo, Type delegateType, object target) + { + return Delegate.CreateDelegate(delegateType, target, methodInfo); + } + + [Obsolete("Use type.GetElementType()")] + public static Type ElementType(this Type type) => type.GetElementType(); + + public static Type GetCollectionType(this Type type) + { + return type.GetElementType() + ?? type.GetGenericArguments().LastOrDefault() //new[] { str }.Select(x => new Type()) => WhereSelectArrayIterator + ?? (type.BaseType != null && type.BaseType != typeof(object) ? type.BaseType.GetCollectionType() : null); //e.g. ArrayOfString : List + } + + static Dictionary GenericTypeCache = new Dictionary(); + + public static Type GetCachedGenericType(this Type type, params Type[] argTypes) + { + if (!type.IsGenericTypeDefinition) + throw new ArgumentException(type.FullName + " is not a Generic Type Definition"); + + if (argTypes == null) + argTypes = TypeConstants.EmptyTypeArray; + + var sb = StringBuilderThreadStatic.Allocate() + .Append(type.FullName); + + foreach (var argType in argTypes) + { + sb.Append('|') + .Append(argType.FullName); + } + + var key = StringBuilderThreadStatic.ReturnAndFree(sb); + + if (GenericTypeCache.TryGetValue(key, out var genericType)) + return genericType; + + genericType = type.MakeGenericType(argTypes); + + Dictionary snapshot, newCache; + do + { + snapshot = GenericTypeCache; + newCache = new Dictionary(GenericTypeCache) { + [key] = genericType + }; + + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref GenericTypeCache, newCache, snapshot), snapshot)); + + return genericType; + } + + private static readonly ConcurrentDictionary toObjectMapCache = + new ConcurrentDictionary(); + + internal class ObjectDictionaryDefinition + { + public Type Type; + public readonly List Fields = new List(); + public readonly Dictionary FieldsMap = new Dictionary(); + + public void Add(string name, ObjectDictionaryFieldDefinition fieldDef) + { + Fields.Add(fieldDef); + FieldsMap[name] = fieldDef; + } + } + + internal class ObjectDictionaryFieldDefinition + { + public string Name; + public Type Type; + + public GetMemberDelegate GetValueFn; + public SetMemberDelegate SetValueFn; + + public Type ConvertType; + public GetMemberDelegate ConvertValueFn; + + public void SetValue(object instance, object value) + { + if (SetValueFn == null) + return; + + if (Type != typeof(object)) + { + if (value is IEnumerable> dictionary) + { + value = dictionary.FromObjectDictionary(Type); + } + + if (!Type.IsInstanceOfType(value)) + { + lock (this) + { + //Only caches object converter used on first use + if (ConvertType == null) + { + ConvertType = value.GetType(); + ConvertValueFn = TypeConverter.CreateTypeConverter(ConvertType, Type); + } + } + + if (ConvertType.IsInstanceOfType(value)) + { + value = ConvertValueFn(value); + } + else + { + var tempConvertFn = TypeConverter.CreateTypeConverter(value.GetType(), Type); + value = tempConvertFn(value); + } + } + } + + SetValueFn(instance, value); + } + } + + private static Dictionary ConvertToDictionary( + IEnumerable> collection, + Func mapper = null) + { + if (mapper != null) + { + return mapper.MapToDictionary(collection); + } + + var to = new Dictionary(); + foreach (var entry in collection) + { + string key = entry.Key; + object value = entry.Value; + to[key] = value; + } + return to; + } + + private static Dictionary MapToDictionary( + this Func mapper, + IEnumerable> collection) + { + return collection.ToDictionary( + pair => pair.Key, + pair => mapper(pair.Key, pair.Value)); + } + + public static Dictionary ToObjectDictionary(this object obj) + { + return ToObjectDictionary(obj, null); + } + + public static Dictionary ToObjectDictionary( + this object obj, + Func mapper) + { + if (obj == null) + return null; + + if (obj is Dictionary alreadyDict) + { + if (mapper != null) + return mapper.MapToDictionary(alreadyDict); + return alreadyDict; + } + + if (obj is IDictionary interfaceDict) + { + if (mapper != null) + return mapper.MapToDictionary(interfaceDict); + return new Dictionary(interfaceDict); + } + + var to = new Dictionary(); + if (obj is Dictionary stringDict) + { + return ConvertToDictionary(stringDict, mapper); + } + + if (obj is IDictionary d) + { + foreach (var key in d.Keys) + { + string k = key.ToString(); + object v = d[key]; + v = mapper?.Invoke(k, v) ?? v; + to[k] = v; + } + return to; + } + + if (obj is NameValueCollection nvc) + { + for (var i = 0; i < nvc.Count; i++) + { + string k = nvc.GetKey(i); + object v = nvc.Get(i); + v = mapper?.Invoke(k, v) ?? v; + to[k] = v; + } + return to; + } + + if (obj is IEnumerable> objKvps) + { + return ConvertToDictionary(objKvps, mapper); + } + if (obj is IEnumerable> strKvps) + { + return ConvertToDictionary(strKvps, mapper); + } + + var type = obj.GetType(); + if (type.GetKeyValuePairsTypes(out var keyType, out var valueType, out var kvpType) && obj is IEnumerable e) + { + var keyGetter = TypeProperties.Get(kvpType).GetPublicGetter("Key"); + var valueGetter = TypeProperties.Get(kvpType).GetPublicGetter("Value"); + + foreach (var entry in e) + { + var key = keyGetter(entry); + var value = valueGetter(entry); + string k = key.ConvertTo(); + value = mapper?.Invoke(k, value) ?? value; + to[k] = value; + } + return to; + } + + + if (obj is KeyValuePair objKvp) + { + string kk = nameof(objKvp.Key); + object kv = objKvp.Key; + kv = mapper?.Invoke(kk, kv) ?? kv; + + string vk = nameof(objKvp.Value); + object vv = objKvp.Value; + vv = mapper?.Invoke(vk, vv) ?? vv; + + return new Dictionary + { + [kk] = kv, + [vk] = vv + }; + } + if (obj is KeyValuePair strKvp) + { + string kk = nameof(objKvp.Key); + object kv = strKvp.Key; + kv = mapper?.Invoke(kk, kv) ?? kv; + + string vk = nameof(strKvp.Value); + object vv = strKvp.Value; + vv = mapper?.Invoke(vk, vv) ?? vv; + + return new Dictionary + { + [kk] = kv, + [vk] = vv + }; + } + if (type.GetKeyValuePairTypes(out _, out var _)) + { + string kk = "Key"; + object kv = TypeProperties.Get(type).GetPublicGetter("Key")(obj).ConvertTo(); + kv = mapper?.Invoke(kk, kv) ?? kv; + + string vk = "Value"; + object vv = TypeProperties.Get(type).GetPublicGetter("Value")(obj); + vv = mapper?.Invoke(vk, vv) ?? vv; + + return new Dictionary + { + [kk] = kv, + [vk] = vv + }; + } + + if (!toObjectMapCache.TryGetValue(type, out var def)) + toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type); + + foreach (var fieldDef in def.Fields) + { + string k = fieldDef.Name; + object v = fieldDef.GetValueFn(obj); + v = mapper?.Invoke(k, v) ?? v; + to[k] = v; + } + + return to; + } + + public static Type GetKeyValuePairsTypeDef(this Type dictType) + { + //matches IDictionary<,>, IReadOnlyDictionary<,>, List> + var genericDef = dictType.GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + if (genericDef == null) + return null; + + var genericEnumType = genericDef.GetGenericArguments()[0]; + return GetKeyValuePairTypeDef(genericEnumType); + } + + public static Type GetKeyValuePairTypeDef(this Type genericEnumType) => genericEnumType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + + public static bool GetKeyValuePairsTypes(this Type dictType, out Type keyType, out Type valueType) => + dictType.GetKeyValuePairsTypes(out keyType, out valueType, out _); + + public static bool GetKeyValuePairsTypes(this Type dictType, out Type keyType, out Type valueType, out Type kvpType) + { + //matches IDictionary<,>, IReadOnlyDictionary<,>, List> + var genericDef = dictType.GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + if (genericDef != null) + { + kvpType = genericDef.GetGenericArguments()[0]; + if (GetKeyValuePairTypes(kvpType, out keyType, out valueType)) + return true; + } + kvpType = keyType = valueType = null; + return false; + } + + public static bool GetKeyValuePairTypes(this Type kvpType, out Type keyType, out Type valueType) + { + var genericKvps = kvpType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + if (genericKvps != null) + { + var genericArgs = kvpType.GetGenericArguments(); + keyType = genericArgs[0]; + valueType = genericArgs[1]; + return true; + } + + keyType = valueType = null; + return false; + } + + public static object FromObjectDictionary(this IEnumerable> values, Type type) + { + if (values == null) + return null; + + var alreadyDict = typeof(IEnumerable>).IsAssignableFrom(type); + if (alreadyDict) + return values; + + var to = type.CreateInstance(); + if (to is IDictionary d) + { + if (type.GetKeyValuePairsTypes(out var toKeyType, out var toValueType)) + { + foreach (var entry in values) + { + var toKey = entry.Key.ConvertTo(toKeyType); + var toValue = entry.Value.ConvertTo(toValueType); + d[toKey] = toValue; + } + } + else + { + foreach (var entry in values) + { + d[entry.Key] = entry.Value; + } + } + } + else + { + PopulateInstanceInternal(values, to, type); + } + + return to; + } + + public static void PopulateInstance(this IEnumerable> values, object instance) + { + if (values == null || instance == null) + return; + + PopulateInstanceInternal(values, instance, instance.GetType()); + } + + private static void PopulateInstanceInternal(IEnumerable> values, object to, Type type) + { + if (!toObjectMapCache.TryGetValue(type, out var def)) + toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type); + + foreach (var entry in values) + { + if (!def.FieldsMap.TryGetValue(entry.Key, out var fieldDef) && + !def.FieldsMap.TryGetValue(entry.Key.ToPascalCase(), out fieldDef) + || entry.Value == null + || entry.Value == DBNull.Value) + continue; + + fieldDef.SetValue(to, entry.Value); + } + } + + public static void PopulateInstance(this IEnumerable> values, object instance) + { + if (values == null || instance == null) + return; + + PopulateInstanceInternal(values, instance, instance.GetType()); + } + + private static void PopulateInstanceInternal(IEnumerable> values, object to, Type type) + { + if (!toObjectMapCache.TryGetValue(type, out var def)) + toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type); + + foreach (var entry in values) + { + if (!def.FieldsMap.TryGetValue(entry.Key, out var fieldDef) && + !def.FieldsMap.TryGetValue(entry.Key.ToPascalCase(), out fieldDef) + || entry.Value == null) + continue; + + fieldDef.SetValue(to, entry.Value); + } + } + + public static T FromObjectDictionary(this IEnumerable> values) + { + return (T)values.FromObjectDictionary(typeof(T)); + } + + private static ObjectDictionaryDefinition CreateObjectDictionaryDefinition(Type type) + { + var def = new ObjectDictionaryDefinition + { + Type = type, + }; + + foreach (var pi in type.GetSerializableProperties()) + { + def.Add(pi.Name, new ObjectDictionaryFieldDefinition + { + Name = pi.Name, + Type = pi.PropertyType, + GetValueFn = pi.CreateGetter(), + SetValueFn = pi.CreateSetter(), + }); + } + + if (JsConfig.IncludePublicFields) + { + foreach (var fi in type.GetSerializableFields()) + { + def.Add(fi.Name, new ObjectDictionaryFieldDefinition + { + Name = fi.Name, + Type = fi.FieldType, + GetValueFn = fi.CreateGetter(), + SetValueFn = fi.CreateSetter(), + }); + } + } + return def; + } + + public static Dictionary ToSafePartialObjectDictionary(this T instance) + { + var to = new Dictionary(); + var propValues = instance.ToObjectDictionary(); + if (propValues != null) + { + foreach (var entry in propValues) + { + var valueType = entry.Value?.GetType(); + + try + { + if (valueType == null || !valueType.IsClass || valueType == typeof(string)) + { + to[entry.Key] = entry.Value; + } + else if (!TypeSerializer.HasCircularReferences(entry.Value)) + { + if (entry.Value is IEnumerable enumerable) + { + to[entry.Key] = entry.Value; + } + else + { + to[entry.Key] = entry.Value.ToSafePartialObjectDictionary(); + } + } + else + { + to[entry.Key] = entry.Value.ToString(); + } + + } + catch (Exception ignore) + { + Tracer.Instance.WriteDebug($"Could not retrieve value from '{valueType?.GetType().Name}': ${ignore.Message}"); + } + } + } + return to; + } + + public static Dictionary MergeIntoObjectDictionary(this object obj, params object[] sources) + { + var to = obj.ToObjectDictionary(); + foreach (var source in sources) + foreach (var entry in source.ToObjectDictionary()) + { + to[entry.Key] = entry.Value; + } + return to; + } + + public static Dictionary ToStringDictionary(this IEnumerable> from) => ToStringDictionary(from, null); + + public static Dictionary ToStringDictionary(this IEnumerable> from, IEqualityComparer comparer) + { + var to = comparer != null + ? new Dictionary(comparer) + : new Dictionary(); + + if (from != null) + { + foreach (var entry in from) + { + to[entry.Key] = entry.Value is string s + ? s + : entry.Value.ConvertTo(); + } + } + + return to; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Pools/BufferPool.cs b/src/ServiceStack.Text/Pools/BufferPool.cs new file mode 100644 index 000000000..08a4e5f92 --- /dev/null +++ b/src/ServiceStack.Text/Pools/BufferPool.cs @@ -0,0 +1,172 @@ +using System; + +namespace ServiceStack.Text.Pools +{ + /// + /// Courtesy of @marcgravell + /// https://github.com/mgravell/protobuf-net/blob/master/src/protobuf-net/BufferPool.cs + /// + public sealed class BufferPool + { + public static void Flush() + { + lock (Pool) + { + for (var i = 0; i < Pool.Length; i++) + Pool[i] = null; + } + } + + private BufferPool() { } + private const int POOL_SIZE = 20; + public const int BUFFER_LENGTH = 1450; //<= MTU - DJB + private static readonly CachedBuffer[] Pool = new CachedBuffer[POOL_SIZE]; + + public static byte[] GetBuffer() + { + return GetBuffer(BUFFER_LENGTH); + } + + public static byte[] GetBuffer(int minSize) + { + byte[] cachedBuff = GetCachedBuffer(minSize); + return cachedBuff ?? new byte[minSize]; + } + + public static byte[] GetCachedBuffer(int minSize) + { + lock (Pool) + { + var bestIndex = -1; + byte[] bestMatch = null; + for (var i = 0; i < Pool.Length; i++) + { + var buffer = Pool[i]; + if (buffer == null || buffer.Size < minSize) + { + continue; + } + if (bestMatch != null && bestMatch.Length < buffer.Size) + { + continue; + } + + var tmp = buffer.Buffer; + if (tmp == null) + { + Pool[i] = null; + } + else + { + bestMatch = tmp; + bestIndex = i; + } + } + + if (bestIndex >= 0) + { + Pool[bestIndex] = null; + } + + return bestMatch; + } + } + + /// + /// https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element + /// + private const int MaxByteArraySize = int.MaxValue - 56; + + public static void ResizeAndFlushLeft(ref byte[] buffer, int toFitAtLeastBytes, int copyFromIndex, int copyBytes) + { + Helpers.DebugAssert(buffer != null); + Helpers.DebugAssert(toFitAtLeastBytes > buffer.Length); + Helpers.DebugAssert(copyFromIndex >= 0); + Helpers.DebugAssert(copyBytes >= 0); + + int newLength = buffer.Length * 2; + if (newLength < 0) + { + newLength = MaxByteArraySize; + } + + if (newLength < toFitAtLeastBytes) newLength = toFitAtLeastBytes; + + if (copyBytes == 0) + { + ReleaseBufferToPool(ref buffer); + } + + var newBuffer = GetCachedBuffer(toFitAtLeastBytes) ?? new byte[newLength]; + + if (copyBytes > 0) + { + Buffer.BlockCopy(buffer, copyFromIndex, newBuffer, 0, copyBytes); + ReleaseBufferToPool(ref buffer); + } + + buffer = newBuffer; + } + + public static void ReleaseBufferToPool(ref byte[] buffer) + { + if (buffer == null) return; + + lock (Pool) + { + var minIndex = 0; + var minSize = int.MaxValue; + for (var i = 0; i < Pool.Length; i++) + { + var tmp = Pool[i]; + if (tmp == null || !tmp.IsAlive) + { + minIndex = 0; + break; + } + if (tmp.Size < minSize) + { + minIndex = i; + minSize = tmp.Size; + } + } + + Pool[minIndex] = new CachedBuffer(buffer); + } + + buffer = null; + } + + private class CachedBuffer + { + private readonly WeakReference _reference; + + public int Size { get; } + + public bool IsAlive => _reference.IsAlive; + public byte[] Buffer => (byte[])_reference.Target; + + public CachedBuffer(byte[] buffer) + { + Size = buffer.Length; + _reference = new WeakReference(buffer); + } + } + } + + internal sealed class Helpers + { + private Helpers() { } + + [System.Diagnostics.Conditional("DEBUG")] + internal static void DebugAssert(bool condition) + { +#if DEBUG + if (!condition && System.Diagnostics.Debugger.IsAttached) + System.Diagnostics.Debugger.Break(); + System.Diagnostics.Debug.Assert(condition); +#endif + } + + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Pools/CharPool .cs b/src/ServiceStack.Text/Pools/CharPool .cs new file mode 100644 index 000000000..2bfeb1adc --- /dev/null +++ b/src/ServiceStack.Text/Pools/CharPool .cs @@ -0,0 +1,152 @@ +using System; + +namespace ServiceStack.Text.Pools +{ + public sealed class CharPool + { + public static void Flush() + { + lock (Pool) + { + for (var i = 0; i < Pool.Length; i++) + Pool[i] = null; + } + } + + private CharPool() { } + private const int POOL_SIZE = 20; + public const int BUFFER_LENGTH = 1450; //<= MTU - DJB + private static readonly CachedBuffer[] Pool = new CachedBuffer[POOL_SIZE]; + + public static char[] GetBuffer() + { + return GetBuffer(BUFFER_LENGTH); + } + + public static char[] GetBuffer(int minSize) + { + char[] cachedBuff = GetCachedBuffer(minSize); + return cachedBuff ?? new char[minSize]; + } + + public static char[] GetCachedBuffer(int minSize) + { + lock (Pool) + { + var bestIndex = -1; + char[] bestMatch = null; + for (var i = 0; i < Pool.Length; i++) + { + var buffer = Pool[i]; + if (buffer == null || buffer.Size < minSize) + { + continue; + } + if (bestMatch != null && bestMatch.Length < buffer.Size) + { + continue; + } + + var tmp = buffer.Buffer; + if (tmp == null) + { + Pool[i] = null; + } + else + { + bestMatch = tmp; + bestIndex = i; + } + } + + if (bestIndex >= 0) + { + Pool[bestIndex] = null; + } + + return bestMatch; + } + } + + /// + /// https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element + /// + private const int MaxcharArraySize = int.MaxValue - 56; + + public static void ResizeAndFlushLeft(ref char[] buffer, int toFitAtLeastchars, int copyFromIndex, int copychars) + { + Helpers.DebugAssert(buffer != null); + Helpers.DebugAssert(toFitAtLeastchars > buffer.Length); + Helpers.DebugAssert(copyFromIndex >= 0); + Helpers.DebugAssert(copychars >= 0); + + int newLength = buffer.Length * 2; + if (newLength < 0) + { + newLength = MaxcharArraySize; + } + + if (newLength < toFitAtLeastchars) newLength = toFitAtLeastchars; + + if (copychars == 0) + { + ReleaseBufferToPool(ref buffer); + } + + var newBuffer = GetCachedBuffer(toFitAtLeastchars) ?? new char[newLength]; + + if (copychars > 0) + { + Buffer.BlockCopy(buffer, copyFromIndex, newBuffer, 0, copychars); + ReleaseBufferToPool(ref buffer); + } + + buffer = newBuffer; + } + + public static void ReleaseBufferToPool(ref char[] buffer) + { + if (buffer == null) return; + + lock (Pool) + { + var minIndex = 0; + var minSize = int.MaxValue; + for (var i = 0; i < Pool.Length; i++) + { + var tmp = Pool[i]; + if (tmp == null || !tmp.IsAlive) + { + minIndex = 0; + break; + } + if (tmp.Size < minSize) + { + minIndex = i; + minSize = tmp.Size; + } + } + + Pool[minIndex] = new CachedBuffer(buffer); + } + + buffer = null; + } + + private class CachedBuffer + { + private readonly WeakReference _reference; + + public int Size { get; } + + public bool IsAlive => _reference.IsAlive; + public char[] Buffer => (char[])_reference.Target; + + public CachedBuffer(char[] buffer) + { + Size = buffer.Length; + _reference = new WeakReference(buffer); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Pools/ObjectPool.cs b/src/ServiceStack.Text/Pools/ObjectPool.cs new file mode 100644 index 000000000..ec41dee5f --- /dev/null +++ b/src/ServiceStack.Text/Pools/ObjectPool.cs @@ -0,0 +1,288 @@ +namespace ServiceStack.Text.Pools +{ + // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + // define TRACE_LEAKS to get additional diagnostics that can lead to the leak sources. note: it will + // make everything about 2-3x slower + // + // #define TRACE_LEAKS + + // define DETECT_LEAKS to detect possible leaks + // #if DEBUG + // #define DETECT_LEAKS //for now always enable DETECT_LEAKS in debug. + // #endif + + using System; + using System.Diagnostics; + using System.Threading; + +#if DETECT_LEAKS +using System.Runtime.CompilerServices; +#endif + /// + /// Generic implementation of object pooling pattern with predefined pool size limit. The main + /// purpose is that limited number of frequently used objects can be kept in the pool for + /// further recycling. + /// + /// Notes: + /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there + /// is no space in the pool, extra returned objects will be dropped. + /// + /// 2) it is implied that if object was obtained from a pool, the caller will return it back in + /// a relatively short time. Keeping checked out objects for long durations is ok, but + /// reduces usefulness of pooling. Just new up your own. + /// + /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. + /// Rationale: + /// If there is no intent for reusing the object, do not use pool - just use "new". + /// + public class ObjectPool where T : class + { +#if !PCL + [DebuggerDisplay("{Value,nq}")] +#endif + private struct Element + { + internal T Value; + } + + /// + /// Not using System.Func{T} because this file is linked into the (debugger) Formatter, + /// which does not have that type (since it compiles against .NET 2.0). + /// + public delegate T Factory(); + + // Storage for the pool objects. The first item is stored in a dedicated field because we + // expect to be able to satisfy most requests from it. + private T _firstItem; + private readonly Element[] _items; + + // factory is stored for the lifetime of the pool. We will call this only when pool needs to + // expand. compared to "new T()", Func gives more flexibility to implementers and faster + // than "new T()". + private readonly Factory _factory; + +#if DETECT_LEAKS + private static readonly ConditionalWeakTable leakTrackers = new ConditionalWeakTable(); + + private class LeakTracker : IDisposable + { + private volatile bool disposed; + +#if TRACE_LEAKS + internal volatile object Trace = null; +#endif + + public void Dispose() + { + disposed = true; + GC.SuppressFinalize(this); + } + + private string GetTrace() + { +#if TRACE_LEAKS + return Trace == null ? "" : Trace.ToString(); +#else + return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n"; +#endif + } + + ~LeakTracker() + { + if (!this.disposed && !Environment.HasShutdownStarted) + { + var trace = GetTrace(); + + // If you are seeing this message it means that object has been allocated from the pool + // and has not been returned back. This is not critical, but turns pool into rather + // inefficient kind of "new". + Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nPool detected potential leaking of {typeof(T)}. \n Location of the leak: \n {GetTrace()} TRACEOBJECTPOOLLEAKS_END"); + } + } + } +#endif + + public ObjectPool(Factory factory) + : this(factory, Environment.ProcessorCount * 2) + { } + + public ObjectPool(Factory factory, int size) + { +#if !PCL + Debug.Assert(size >= 1); +#endif + _factory = factory; + _items = new Element[size - 1]; + } + + private T CreateInstance() + { + var inst = _factory(); + return inst; + } + + /// + /// Produces an instance. + /// + /// + /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. + /// Note that Free will try to store recycled objects close to the start thus statistically + /// reducing how far we will typically search. + /// + public T Allocate() + { + // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements. + // Note that the initial read is optimistically not synchronized. That is intentional. + // We will interlock only when we have a candidate. in a worst case we may miss some + // recently returned objects. Not a big deal. + T inst = _firstItem; + if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst)) + { + inst = AllocateSlow(); + } + +#if DETECT_LEAKS + var tracker = new LeakTracker(); + leakTrackers.Add(inst, tracker); + +#if TRACE_LEAKS + var frame = CaptureStackTrace(); + tracker.Trace = frame; +#endif +#endif + return inst; + } + + private T AllocateSlow() + { + var items = _items; + + for (int i = 0; i < items.Length; i++) + { + // Note that the initial read is optimistically not synchronized. That is intentional. + // We will interlock only when we have a candidate. in a worst case we may miss some + // recently returned objects. Not a big deal. + T inst = items[i].Value; + if (inst != null) + { + if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst)) + { + return inst; + } + } + } + + return CreateInstance(); + } + + /// + /// Returns objects to the pool. + /// + /// + /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. + /// Note that Free will try to store recycled objects close to the start thus statistically + /// reducing how far we will typically search in Allocate. + /// + public void Free(T obj) + { + Validate(obj); + ForgetTrackedObject(obj); + + if (_firstItem == null) + { + // Intentionally not using interlocked here. + // In a worst case scenario two objects may be stored into same slot. + // It is very unlikely to happen and will only mean that one of the objects will get collected. + _firstItem = obj; + } + else + { + FreeSlow(obj); + } + } + + private void FreeSlow(T obj) + { + var items = _items; + for (int i = 0; i < items.Length; i++) + { + if (items[i].Value == null) + { + // Intentionally not using interlocked here. + // In a worst case scenario two objects may be stored into same slot. + // It is very unlikely to happen and will only mean that one of the objects will get collected. + items[i].Value = obj; + break; + } + } + } + + /// + /// Removes an object from leak tracking. + /// + /// This is called when an object is returned to the pool. It may also be explicitly + /// called if an object allocated from the pool is intentionally not being returned + /// to the pool. This can be of use with pooled arrays if the consumer wants to + /// return a larger array to the pool than was originally allocated. + /// + [Conditional("DEBUG")] + public void ForgetTrackedObject(T old, T replacement = null) + { +#if DETECT_LEAKS + LeakTracker tracker; + if (leakTrackers.TryGetValue(old, out tracker)) + { + tracker.Dispose(); + leakTrackers.Remove(old); + } + else + { + var trace = CaptureStackTrace(); + Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nObject of type {typeof(T)} was freed, but was not from pool. \n Callstack: \n {trace} TRACEOBJECTPOOLLEAKS_END"); + } + + if (replacement != null) + { + tracker = new LeakTracker(); + leakTrackers.Add(replacement, tracker); + } +#endif + } + +#if DETECT_LEAKS + private static Lazy _stackTraceType = new Lazy(() => Type.GetType("System.Diagnostics.StackTrace")); + + private static object CaptureStackTrace() + { + return Activator.CreateInstance(_stackTraceType.Value); + } +#endif + +#if !PCL + [Conditional("DEBUG")] +#endif + private void Validate(object obj) + { +#if !PCL + Debug.Assert(obj != null, "freeing null?"); + + Debug.Assert(_firstItem != obj, "freeing twice?"); +#endif + + var items = _items; + for (int i = 0; i < items.Length; i++) + { + var value = items[i].Value; + if (value == null) + { + return; + } + +#if !PCL + Debug.Assert(value != obj, "freeing twice?"); +#endif + } + } + } +} diff --git a/src/ServiceStack.Text/Pools/PooledObject.cs b/src/ServiceStack.Text/Pools/PooledObject.cs new file mode 100644 index 000000000..016251c41 --- /dev/null +++ b/src/ServiceStack.Text/Pools/PooledObject.cs @@ -0,0 +1,136 @@ +namespace ServiceStack.Text.Pools +{ + // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// this is RAII object to automatically release pooled object when its owning pool + /// + public struct PooledObject : IDisposable where T : class + { + private readonly Action, T> _releaser; + private readonly ObjectPool _pool; + private T _pooledObject; + + public PooledObject(ObjectPool pool, Func, T> allocator, Action, T> releaser) : this() + { + _pool = pool; + _pooledObject = allocator(pool); + _releaser = releaser; + } + + public T Object + { + get + { + return _pooledObject; + } + } + + public void Dispose() + { + if (_pooledObject != null) + { + _releaser(_pool, _pooledObject); + _pooledObject = null; + } + } + + #region factory + public static PooledObject Create(ObjectPool pool) + { + return new PooledObject(pool, Allocator, Releaser); + } + + public static PooledObject> Create(ObjectPool> pool) + { + return new PooledObject>(pool, Allocator, Releaser); + } + + public static PooledObject> Create(ObjectPool> pool) + { + return new PooledObject>(pool, Allocator, Releaser); + } + + public static PooledObject> Create(ObjectPool> pool) + { + return new PooledObject>(pool, Allocator, Releaser); + } + + public static PooledObject> Create(ObjectPool> pool) + { + return new PooledObject>(pool, Allocator, Releaser); + } + + public static PooledObject> Create(ObjectPool> pool) + { + return new PooledObject>(pool, Allocator, Releaser); + } + #endregion + + #region allocators and releasers + private static StringBuilder Allocator(ObjectPool pool) + { + return pool.AllocateAndClear(); + } + + private static void Releaser(ObjectPool pool, StringBuilder sb) + { + pool.ClearAndFree(sb); + } + + private static Stack Allocator(ObjectPool> pool) + { + return pool.AllocateAndClear(); + } + + private static void Releaser(ObjectPool> pool, Stack obj) + { + pool.ClearAndFree(obj); + } + + private static Queue Allocator(ObjectPool> pool) + { + return pool.AllocateAndClear(); + } + + private static void Releaser(ObjectPool> pool, Queue obj) + { + pool.ClearAndFree(obj); + } + + private static HashSet Allocator(ObjectPool> pool) + { + return pool.AllocateAndClear(); + } + + private static void Releaser(ObjectPool> pool, HashSet obj) + { + pool.ClearAndFree(obj); + } + + private static Dictionary Allocator(ObjectPool> pool) + { + return pool.AllocateAndClear(); + } + + private static void Releaser(ObjectPool> pool, Dictionary obj) + { + pool.ClearAndFree(obj); + } + + private static List Allocator(ObjectPool> pool) + { + return pool.AllocateAndClear(); + } + + private static void Releaser(ObjectPool> pool, List obj) + { + pool.ClearAndFree(obj); + } + #endregion + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Pools/SharedPoolExtensions.cs b/src/ServiceStack.Text/Pools/SharedPoolExtensions.cs new file mode 100644 index 000000000..d485c2571 --- /dev/null +++ b/src/ServiceStack.Text/Pools/SharedPoolExtensions.cs @@ -0,0 +1,201 @@ +namespace ServiceStack.Text.Pools +{ + // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + using System.Collections.Generic; + using System.Text; + + internal static class SharedPoolExtensions + { + private const int Threshold = 512; + + public static PooledObject GetPooledObject(this ObjectPool pool) + { + return PooledObject.Create(pool); + } + + public static PooledObject> GetPooledObject(this ObjectPool> pool) + { + return PooledObject>.Create(pool); + } + + public static PooledObject> GetPooledObject(this ObjectPool> pool) + { + return PooledObject>.Create(pool); + } + + public static PooledObject> GetPooledObject(this ObjectPool> pool) + { + return PooledObject>.Create(pool); + } + + public static PooledObject> GetPooledObject(this ObjectPool> pool) + { + return PooledObject>.Create(pool); + } + + public static PooledObject> GetPooledObject(this ObjectPool> pool) + { + return PooledObject>.Create(pool); + } + + public static PooledObject GetPooledObject(this ObjectPool pool) where T : class + { + return new PooledObject(pool, p => p.Allocate(), (p, o) => p.Free(o)); + } + + public static StringBuilder AllocateAndClear(this ObjectPool pool) + { + var sb = pool.Allocate(); + sb.Clear(); + + return sb; + } + + public static Stack AllocateAndClear(this ObjectPool> pool) + { + var set = pool.Allocate(); + set.Clear(); + + return set; + } + + public static Queue AllocateAndClear(this ObjectPool> pool) + { + var set = pool.Allocate(); + set.Clear(); + + return set; + } + + public static HashSet AllocateAndClear(this ObjectPool> pool) + { + var set = pool.Allocate(); + set.Clear(); + + return set; + } + + public static Dictionary AllocateAndClear(this ObjectPool> pool) + { + var map = pool.Allocate(); + map.Clear(); + + return map; + } + + public static List AllocateAndClear(this ObjectPool> pool) + { + var list = pool.Allocate(); + list.Clear(); + + return list; + } + + public static void ClearAndFree(this ObjectPool pool, StringBuilder sb) + { + if (sb == null) + { + return; + } + + sb.Clear(); + + if (sb.Capacity > Threshold) + { + sb.Capacity = Threshold; + } + + pool.Free(sb); + } + + public static void ClearAndFree(this ObjectPool> pool, HashSet set) + { + if (set == null) + { + return; + } + + var count = set.Count; + set.Clear(); + + if (count > Threshold) + { + set.TrimExcess(); + } + + pool.Free(set); + } + + public static void ClearAndFree(this ObjectPool> pool, Stack set) + { + if (set == null) + { + return; + } + + var count = set.Count; + set.Clear(); + + if (count > Threshold) + { + set.TrimExcess(); + } + + pool.Free(set); + } + + public static void ClearAndFree(this ObjectPool> pool, Queue set) + { + if (set == null) + { + return; + } + + var count = set.Count; + set.Clear(); + + if (count > Threshold) + { + set.TrimExcess(); + } + + pool.Free(set); + } + + public static void ClearAndFree(this ObjectPool> pool, Dictionary map) + { + if (map == null) + { + return; + } + + // if map grew too big, don't put it back to pool + if (map.Count > Threshold) + { + pool.ForgetTrackedObject(map); + return; + } + + map.Clear(); + pool.Free(map); + } + + public static void ClearAndFree(this ObjectPool> pool, List list) + { + if (list == null) + { + return; + } + + list.Clear(); + + if (list.Capacity > Threshold) + { + list.Capacity = Threshold; + } + + pool.Free(list); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Pools/SharedPools.cs b/src/ServiceStack.Text/Pools/SharedPools.cs new file mode 100644 index 000000000..e8f6e6dd0 --- /dev/null +++ b/src/ServiceStack.Text/Pools/SharedPools.cs @@ -0,0 +1,83 @@ +namespace ServiceStack.Text.Pools +{ + // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + using System; + using System.Collections.Generic; + + /// + /// Shared object pool for roslyn + /// + /// Use this shared pool if only concern is reducing object allocations. + /// if perf of an object pool itself is also a concern, use ObjectPool directly. + /// + /// For example, if you want to create a million of small objects within a second, + /// use the ObjectPool directly. it should have much less overhead than using this. + /// + public static class SharedPools + { + /// + /// pool that uses default constructor with 100 elements pooled + /// + public static ObjectPool BigDefault() where T : class, new() + { + return DefaultBigPool.Instance; + } + + /// + /// pool that uses default constructor with 20 elements pooled + /// + public static ObjectPool Default() where T : class, new() + { + return DefaultNormalPool.Instance; + } + + /// + /// pool that uses string as key with StringComparer.OrdinalIgnoreCase as key comparer + /// + public static ObjectPool> StringIgnoreCaseDictionary() + { + return StringIgnoreCaseDictionaryNormalPool.Instance; + } + + /// + /// pool that uses string as element with StringComparer.OrdinalIgnoreCase as element comparer + /// + public static readonly ObjectPool> StringIgnoreCaseHashSet = + new ObjectPool>(() => new HashSet(StringComparer.OrdinalIgnoreCase), 20); + + /// + /// pool that uses string as element with StringComparer.Ordinal as element comparer + /// + public static readonly ObjectPool> StringHashSet = + new ObjectPool>(() => new HashSet(StringComparer.Ordinal), 20); + + /// + /// Used to reduce the # of temporary byte[]s created to satisfy serialization and + /// other I/O requests + /// + public static readonly ObjectPool ByteArray = new ObjectPool(() => new byte[ByteBufferSize], ByteBufferCount); + + /// pooled memory : 4K * 512 = 4MB + public const int ByteBufferSize = 4 * 1024; + private const int ByteBufferCount = 512; + + public static readonly ObjectPool AsyncByteArray = new ObjectPool(() => new byte[StreamExtensions.AsyncBufferSize], 50); + + private static class DefaultBigPool where T : class, new() + { + public static readonly ObjectPool Instance = new ObjectPool(() => new T(), 100); + } + + private static class DefaultNormalPool where T : class, new() + { + public static readonly ObjectPool Instance = new ObjectPool(() => new T(), 20); + } + + private static class StringIgnoreCaseDictionaryNormalPool + { + public static readonly ObjectPool> Instance = + new ObjectPool>(() => new Dictionary(StringComparer.OrdinalIgnoreCase), 20); + } + } +} diff --git a/src/ServiceStack.Text/Pools/StringBuilderPool.cs b/src/ServiceStack.Text/Pools/StringBuilderPool.cs new file mode 100644 index 000000000..fdef6eb91 --- /dev/null +++ b/src/ServiceStack.Text/Pools/StringBuilderPool.cs @@ -0,0 +1,25 @@ +namespace ServiceStack.Text.Pools +{ + // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + using System.Text; + + public static class StringBuilderPool + { + public static StringBuilder Allocate() + { + return SharedPools.Default().AllocateAndClear(); + } + + public static void Free(StringBuilder builder) + { + SharedPools.Default().ClearAndFree(builder); + } + + public static string ReturnAndFree(StringBuilder builder) + { + SharedPools.Default().ForgetTrackedObject(builder); + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Properties/AssemblyInfo.cs b/src/ServiceStack.Text/Properties/AssemblyInfo.cs index d6a0ccaa3..b64975d01 100644 --- a/src/ServiceStack.Text/Properties/AssemblyInfo.cs +++ b/src/ServiceStack.Text/Properties/AssemblyInfo.cs @@ -1,35 +1,5 @@ -using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Text")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("ServiceStack")] -[assembly: AssemblyProduct("ServiceStack.Text")] -[assembly: AssemblyCopyright("Copyright © ServiceStack 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("a352d4d3-df2a-4c78-b646-67181a6333a6")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.9.32.0")] -//[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: System.Reflection.AssemblyVersion("6.0.0.0")] diff --git a/src/ServiceStack.Text/QueryStringSerializer.cs b/src/ServiceStack.Text/QueryStringSerializer.cs index a8a40c3f6..58c4c593a 100644 --- a/src/ServiceStack.Text/QueryStringSerializer.cs +++ b/src/ServiceStack.Text/QueryStringSerializer.cs @@ -5,40 +5,53 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; +using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Reflection; +using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; +using ServiceStack.Text; using ServiceStack.Text.Common; +using ServiceStack.Text.Json; using ServiceStack.Text.Jsv; -namespace ServiceStack.Text +namespace ServiceStack { - public static class QueryStringSerializer - { - internal static readonly JsWriter Instance = new JsWriter(); + public static class QueryStringSerializer + { + static QueryStringSerializer() + { + JsConfig.InitStatics(); + Instance = new JsWriter(); + } - private static Dictionary WriteFnCache = new Dictionary(); + internal static readonly JsWriter Instance; - internal static WriteObjectDelegate GetWriteFn(Type type) - { - try - { - WriteObjectDelegate writeFn; - if (WriteFnCache.TryGetValue(type, out writeFn)) return writeFn; + private static Dictionary WriteFnCache = new Dictionary(); + + public static WriteComplexTypeDelegate ComplexTypeStrategy { get; set; } + + internal static WriteObjectDelegate GetWriteFn(Type type) + { + try + { + if (WriteFnCache.TryGetValue(type, out var writeFn)) return writeFn; var genericType = typeof(QueryStringWriter<>).MakeGenericType(type); - var mi = genericType.GetMethod("WriteFn", BindingFlags.NonPublic | BindingFlags.Static); - var writeFactoryFn = (Func)Delegate.CreateDelegate( - typeof(Func), mi); + var mi = genericType.GetStaticMethod("WriteFn"); + var writeFactoryFn = (Func)mi.MakeDelegate( + typeof(Func)); + writeFn = writeFactoryFn(); Dictionary snapshot, newCache; @@ -50,79 +63,231 @@ internal static WriteObjectDelegate GetWriteFn(Type type) } while (!ReferenceEquals( Interlocked.CompareExchange(ref WriteFnCache, newCache, snapshot), snapshot)); - + return writeFn; - } - catch (Exception ex) - { - Tracer.Instance.WriteError(ex); - throw; - } - } - - public static void WriteLateBoundObject(TextWriter writer, object value) - { - if (value == null) return; - var writeFn = GetWriteFn(value.GetType()); - writeFn(writer, value); - } - - internal static WriteObjectDelegate GetValueTypeToStringMethod(Type type) - { - return Instance.GetValueTypeToStringMethod(type); - } - - public static string SerializeToString(T value) - { - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture)) - { - GetWriteFn(value.GetType())(writer, value); - } - return sb.ToString(); - } - } - - /// - /// Implement the serializer using a more static approach - /// - /// - public static class QueryStringWriter - { - private static readonly WriteObjectDelegate CacheFn; - - internal static WriteObjectDelegate WriteFn() - { - return CacheFn; - } - - static QueryStringWriter() - { - if (typeof(T) == typeof(object)) - { - CacheFn = QueryStringSerializer.WriteLateBoundObject; - } - else - { - if (typeof(T).IsClass || typeof(T).IsInterface) - { - var canWriteType = WriteType.Write; - if (canWriteType != null) - { - CacheFn = WriteType.WriteQueryString; - return; - } - } - - CacheFn = QueryStringSerializer.Instance.GetWriteFn(); - } - } - - public static void WriteObject(TextWriter writer, object value) - { - if (writer == null) return; - CacheFn(writer, value); - } - } - + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + throw; + } + } + + public static void WriteLateBoundObject(TextWriter writer, object value) + { + if (value == null) return; + var writeFn = GetWriteFn(value.GetType()); + writeFn(writer, value); + } + + internal static WriteObjectDelegate GetValueTypeToStringMethod(Type type) + { + return Instance.GetValueTypeToStringMethod(type); + } + + public static string SerializeToString(T value) + { + var writer = StringWriterThreadStatic.Allocate(); + GetWriteFn(value.GetType())(writer, value); + return StringWriterThreadStatic.ReturnAndFree(writer); + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + QueryStringWriter.WriteFn(); + } + } + + /// + /// Implement the serializer using a more static approach + /// + /// + public static class QueryStringWriter + { + private static readonly WriteObjectDelegate CacheFn; + + public static WriteObjectDelegate WriteFn() + { + return CacheFn; + } + + static QueryStringWriter() + { + if (typeof(T) == typeof(object)) + { + CacheFn = QueryStringSerializer.WriteLateBoundObject; + } + else if (typeof(T).IsAssignableFrom(typeof(IDictionary)) + || typeof(T).HasInterface(typeof(IDictionary))) + { + CacheFn = WriteIDictionary; + } + else + { + var isEnumerable = typeof(T).IsAssignableFrom(typeof(IEnumerable)) + || typeof(T).HasInterface(typeof(IEnumerable)); + + if ((typeof(T).IsClass || typeof(T).IsInterface) + && !isEnumerable) + { + var canWriteType = WriteType.Write; + if (canWriteType != null) + { + CacheFn = WriteType.WriteQueryString; + return; + } + } + + CacheFn = QueryStringSerializer.Instance.GetWriteFn(); + } + } + + public static void WriteObject(TextWriter writer, object value) + { + if (writer == null) return; + CacheFn(writer, value); + } + + private static readonly ITypeSerializer Serializer = JsvTypeSerializer.Instance; + public static void WriteIDictionary(TextWriter writer, object oMap) + { + WriteObjectDelegate writeKeyFn = null; + WriteObjectDelegate writeValueFn = null; + + try + { + JsState.QueryStringMode = true; + + var isObjectDictionary = typeof(T) == typeof(Dictionary); + var map = (IDictionary)oMap; + var ranOnce = false; + foreach (var key in map.Keys) + { + var dictionaryValue = map[key]; + if (dictionaryValue == null) + continue; + + if (writeKeyFn == null) + { + var keyType = key.GetType(); + writeKeyFn = Serializer.GetWriteFn(keyType); + } + + if (writeValueFn == null || isObjectDictionary) + { + writeValueFn = dictionaryValue is string + ? (w,x) => w.Write(((string)x).UrlEncode()) + : Serializer.GetWriteFn(dictionaryValue.GetType()); + } + + if (ranOnce) + writer.Write("&"); + else + ranOnce = true; + + JsState.WritingKeyCount++; + try + { + JsState.IsWritingValue = false; + + writeKeyFn(writer, key); + } + finally + { + JsState.WritingKeyCount--; + } + + writer.Write("="); + + JsState.IsWritingValue = true; + try + { + writeValueFn(writer, dictionaryValue); + } + finally + { + JsState.IsWritingValue = false; + } + } + } + finally + { + JsState.QueryStringMode = false; + } + } + } + + public delegate bool WriteComplexTypeDelegate(TextWriter writer, string propertyName, object obj); + + internal class PropertyTypeConfig + { + public TypeConfig TypeConfig; + public Action WriteFn; + } + + internal class PropertyTypeConfig + { + public static PropertyTypeConfig Config; + + static PropertyTypeConfig() + { + Config = new PropertyTypeConfig + { + TypeConfig = TypeConfig.GetState(), + WriteFn = WriteType.WriteComplexQueryStringProperties, + }; + } + } + + public static class QueryStringStrategy + { + static readonly ConcurrentDictionary typeConfigCache = + new ConcurrentDictionary(); + + public static bool FormUrlEncoded(TextWriter writer, string propertyName, object obj) + { + if (obj is IDictionary map) + { + var i = 0; + foreach (var key in map.Keys) + { + if (i++ > 0) + writer.Write('&'); + + var value = map[key]; + writer.Write(propertyName); + writer.Write('['); + writer.Write(key.ToString()); + writer.Write("]="); + + if (value == null) + { + writer.Write(JsonUtils.Null); + } + else if (value is string strValue && strValue == string.Empty) { /*ignore*/ } + else + { + var writeFn = JsvWriter.GetWriteFn(value.GetType()); + writeFn(writer, value); + } + } + + return true; + } + + var typeConfig = typeConfigCache.GetOrAdd(obj.GetType(), t => + { + var genericType = typeof(PropertyTypeConfig<>).MakeGenericType(t); + var fi = genericType.Fields().First(x => x.Name == "Config"); + + var config = (PropertyTypeConfig)fi.GetValue(null); + return config; + }); + + typeConfig.WriteFn(propertyName, writer, obj); + + return true; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/RecyclableMemoryStream.cs b/src/ServiceStack.Text/RecyclableMemoryStream.cs new file mode 100644 index 000000000..13ee0e6c9 --- /dev/null +++ b/src/ServiceStack.Text/RecyclableMemoryStream.cs @@ -0,0 +1,2228 @@ +// --------------------------------------------------------------------- +// Copyright (c) 2015 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; +using System.IO; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Tracing; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace ServiceStack.Text //Internalize to avoid conflicts +{ + public static class MemoryStreamFactory + { + public static bool UseRecyclableMemoryStream { get; set; } + + public static RecyclableMemoryStreamManager RecyclableInstance = new(); + + public static MemoryStream GetStream() + { + return UseRecyclableMemoryStream + ? RecyclableInstance.GetStream() + : new MemoryStream(); + } + + public static MemoryStream GetStream(int capacity) + { + return UseRecyclableMemoryStream + ? RecyclableInstance.GetStream(nameof(MemoryStreamFactory), capacity) + : new MemoryStream(capacity); + } + + public static MemoryStream GetStream(byte[] bytes) + { + return UseRecyclableMemoryStream + ? RecyclableInstance.GetStream(nameof(MemoryStreamFactory), bytes, 0, bytes.Length) + : new MemoryStream(bytes, 0, bytes.Length, writable:true, publiclyVisible:true); + } + + public static MemoryStream GetStream(byte[] bytes, int index, int count) + { + return UseRecyclableMemoryStream + ? RecyclableInstance.GetStream(nameof(MemoryStreamFactory), bytes, index, count) + : new MemoryStream(bytes, index, count, writable:true, publiclyVisible:true); + } + } + +#if !NETCORE + public enum EventLevel + { + LogAlways = 0, + Critical, + Error, + Warning, + Informational, + Verbose, + } + + public enum EventKeywords : long + { + None = 0x0, + } + + [AttributeUsage(AttributeTargets.Class)] + public sealed class EventSourceAttribute : Attribute + { + public string Name { get; set; } + public string Guid { get; set; } + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class EventAttribute : Attribute + { + public EventAttribute(int id) { } + + public EventLevel Level { get; set; } + } + + public class EventSource + { + public void WriteEvent(params object[] unused) + { + return; + } + + public bool IsEnabled() + { + return false; + } + + public bool IsEnabled(EventLevel level, EventKeywords keywords) + { + return false; + } + } +#endif + + public sealed partial class RecyclableMemoryStreamManager + { + /// + /// ETW events for RecyclableMemoryStream + /// + [EventSource(Name = "Microsoft-IO-RecyclableMemoryStream", Guid = "{B80CD4E4-890E-468D-9CBA-90EB7C82DFC7}")] + public sealed class Events : EventSource + { + /// + /// Static log object, through which all events are written. + /// + public static Events Writer = new Events(); + + /// + /// Type of buffer + /// + public enum MemoryStreamBufferType + { + /// + /// Small block buffer + /// + Small, + /// + /// Large pool buffer + /// + Large + } + + /// + /// The possible reasons for discarding a buffer + /// + public enum MemoryStreamDiscardReason + { + /// + /// Buffer was too large to be re-pooled + /// + TooLarge, + /// + /// There are enough free bytes in the pool + /// + EnoughFree + } + + /// + /// Logged when a stream object is created. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Requested size of the stream + [Event(1, Level = EventLevel.Verbose)] + public void MemoryStreamCreated(Guid guid, string tag, int requestedSize) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(1, guid, tag ?? string.Empty, requestedSize); + } + } + + /// + /// Logged when the stream is disposed + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + [Event(2, Level = EventLevel.Verbose)] + public void MemoryStreamDisposed(Guid guid, string tag) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(2, guid, tag ?? string.Empty); + } + } + + /// + /// Logged when the stream is disposed for the second time. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of initial allocation. + /// Call stack of the first dispose. + /// Call stack of the second dispose. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(3, Level = EventLevel.Critical)] + public void MemoryStreamDoubleDispose(Guid guid, string tag, string allocationStack, string disposeStack1, + string disposeStack2) + { + if (this.IsEnabled()) + { + this.WriteEvent(3, guid, tag ?? string.Empty, allocationStack ?? string.Empty, + disposeStack1 ?? string.Empty, disposeStack2 ?? string.Empty); + } + } + + /// + /// Logged when a stream is finalized. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of initial allocation. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(4, Level = EventLevel.Error)] + public void MemoryStreamFinalized(Guid guid, string tag, string allocationStack) + { + if (this.IsEnabled()) + { + WriteEvent(4, guid, tag ?? string.Empty, allocationStack ?? string.Empty); + } + } + + /// + /// Logged when ToArray is called on a stream. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of the ToArray call. + /// Length of stream + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(5, Level = EventLevel.Verbose)] + public void MemoryStreamToArray(Guid guid, string tag, string stack, int size) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(5, guid, tag ?? string.Empty, stack ?? string.Empty, size); + } + } + + /// + /// Logged when the RecyclableMemoryStreamManager is initialized. + /// + /// Size of blocks, in bytes. + /// Size of the large buffer multiple, in bytes. + /// Maximum buffer size, in bytes. + [Event(6, Level = EventLevel.Informational)] + public void MemoryStreamManagerInitialized(int blockSize, int largeBufferMultiple, int maximumBufferSize) + { + if (this.IsEnabled()) + { + WriteEvent(6, blockSize, largeBufferMultiple, maximumBufferSize); + } + } + + /// + /// Logged when a new block is created. + /// + /// Number of bytes in the small pool currently in use. + [Event(7, Level = EventLevel.Verbose)] + public void MemoryStreamNewBlockCreated(long smallPoolInUseBytes) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(7, smallPoolInUseBytes); + } + } + + /// + /// Logged when a new large buffer is created. + /// + /// Requested size + /// Number of bytes in the large pool in use. + [Event(8, Level = EventLevel.Verbose)] + public void MemoryStreamNewLargeBufferCreated(int requiredSize, long largePoolInUseBytes) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(8, requiredSize, largePoolInUseBytes); + } + } + + /// + /// Logged when a buffer is created that is too large to pool. + /// + /// Size requested by the caller + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of the requested stream. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(9, Level = EventLevel.Verbose)] + public void MemoryStreamNonPooledLargeBufferCreated(int requiredSize, string tag, string allocationStack) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(9, requiredSize, tag ?? string.Empty, allocationStack ?? string.Empty); + } + } + + /// + /// Logged when a buffer is discarded (not put back in the pool, but given to GC to clean up). + /// + /// Type of the buffer being discarded. + /// A temporary ID for this stream, usually indicates current usage. + /// Reason for the discard. + [Event(10, Level = EventLevel.Warning)] + public void MemoryStreamDiscardBuffer(MemoryStreamBufferType bufferType, string tag, + MemoryStreamDiscardReason reason) + { + if (this.IsEnabled()) + { + WriteEvent(10, bufferType, tag ?? string.Empty, reason); + } + } + + /// + /// Logged when a stream grows beyond the maximum capacity. + /// + /// The requested capacity. + /// Maximum capacity, as configured by RecyclableMemoryStreamManager. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack for the capacity request. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(11, Level = EventLevel.Error)] + public void MemoryStreamOverCapacity(int requestedCapacity, long maxCapacity, string tag, + string allocationStack) + { + if (this.IsEnabled()) + { + WriteEvent(11, requestedCapacity, maxCapacity, tag ?? string.Empty, allocationStack ?? string.Empty); + } + } + } + } + + /// + /// Manages pools of RecyclableMemoryStream objects. + /// + /// + /// There are two pools managed in here. The small pool contains same-sized buffers that are handed to streams + /// as they write more data. + /// + /// For scenarios that need to call GetBuffer(), the large pool contains buffers of various sizes, all + /// multiples/exponentials of LargeBufferMultiple (1 MB by default). They are split by size to avoid overly-wasteful buffer + /// usage. There should be far fewer 8 MB buffers than 1 MB buffers, for example. + /// + public partial class RecyclableMemoryStreamManager + { + /// + /// Generic delegate for handling events without any arguments. + /// + public delegate void EventHandler(); + + /// + /// Delegate for handling large buffer discard reports. + /// + /// Reason the buffer was discarded. + public delegate void LargeBufferDiscardedEventHandler(Events.MemoryStreamDiscardReason reason); + + /// + /// Delegate for handling reports of stream size when streams are allocated + /// + /// Bytes allocated. + public delegate void StreamLengthReportHandler(long bytes); + + /// + /// Delegate for handling periodic reporting of memory use statistics. + /// + /// Bytes currently in use in the small pool. + /// Bytes currently free in the small pool. + /// Bytes currently in use in the large pool. + /// Bytes currently free in the large pool. + public delegate void UsageReportEventHandler( + long smallPoolInUseBytes, long smallPoolFreeBytes, long largePoolInUseBytes, long largePoolFreeBytes); + + /// + /// Default block size, in bytes + /// + public const int DefaultBlockSize = 128 * 1024; + /// + /// Default large buffer multiple, in bytes + /// + public const int DefaultLargeBufferMultiple = 1024 * 1024; + /// + /// Default maximum buffer size, in bytes + /// + public const int DefaultMaximumBufferSize = 128 * 1024 * 1024; + + private readonly long[] largeBufferFreeSize; + private readonly long[] largeBufferInUseSize; + + + private readonly ConcurrentStack[] largePools; + + private readonly ConcurrentStack smallPool; + + private long smallPoolFreeSize; + private long smallPoolInUseSize; + + /// + /// Initializes the memory manager with the default block/buffer specifications. + /// + public RecyclableMemoryStreamManager() + : this(DefaultBlockSize, DefaultLargeBufferMultiple, DefaultMaximumBufferSize, false) { } + + /// + /// Initializes the memory manager with the given block requiredSize. + /// + /// Size of each block that is pooled. Must be > 0. + /// Each large buffer will be a multiple of this value. + /// Buffers larger than this are not pooled + /// blockSize is not a positive number, or largeBufferMultiple is not a positive number, or maximumBufferSize is less than blockSize. + /// maximumBufferSize is not a multiple of largeBufferMultiple + public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize) + : this(blockSize, largeBufferMultiple, maximumBufferSize, false) { } + + /// + /// Initializes the memory manager with the given block requiredSize. + /// + /// Size of each block that is pooled. Must be > 0. + /// Each large buffer will be a multiple/exponential of this value. + /// Buffers larger than this are not pooled + /// Switch to exponential large buffer allocation strategy + /// blockSize is not a positive number, or largeBufferMultiple is not a positive number, or maximumBufferSize is less than blockSize. + /// maximumBufferSize is not a multiple/exponential of largeBufferMultiple + public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize, bool useExponentialLargeBuffer) + { + if (blockSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(blockSize), blockSize, "blockSize must be a positive number"); + } + + if (largeBufferMultiple <= 0) + { + throw new ArgumentOutOfRangeException(nameof(largeBufferMultiple), + "largeBufferMultiple must be a positive number"); + } + + if (maximumBufferSize < blockSize) + { + throw new ArgumentOutOfRangeException(nameof(maximumBufferSize), + "maximumBufferSize must be at least blockSize"); + } + + this.BlockSize = blockSize; + this.LargeBufferMultiple = largeBufferMultiple; + this.MaximumBufferSize = maximumBufferSize; + this.UseExponentialLargeBuffer = useExponentialLargeBuffer; + + if (!this.IsLargeBufferSize(maximumBufferSize)) + { + throw new ArgumentException(String.Format("maximumBufferSize is not {0} of largeBufferMultiple", + this.UseExponentialLargeBuffer ? "an exponential" : "a multiple"), + nameof(maximumBufferSize)); + } + + this.smallPool = new ConcurrentStack(); + var numLargePools = useExponentialLargeBuffer + ? ((int)Math.Log(maximumBufferSize / largeBufferMultiple, 2) + 1) + : (maximumBufferSize / largeBufferMultiple); + + // +1 to store size of bytes in use that are too large to be pooled + this.largeBufferInUseSize = new long[numLargePools + 1]; + this.largeBufferFreeSize = new long[numLargePools]; + + this.largePools = new ConcurrentStack[numLargePools]; + + for (var i = 0; i < this.largePools.Length; ++i) + { + this.largePools[i] = new ConcurrentStack(); + } + + Events.Writer.MemoryStreamManagerInitialized(blockSize, largeBufferMultiple, maximumBufferSize); + } + + /// + /// The size of each block. It must be set at creation and cannot be changed. + /// + public int BlockSize { get; } + + /// + /// All buffers are multiples/exponentials of this number. It must be set at creation and cannot be changed. + /// + public int LargeBufferMultiple { get; } + + /// + /// Use multiple large buffer allocation strategy. It must be set at creation and cannot be changed. + /// + public bool UseMultipleLargeBuffer => !this.UseExponentialLargeBuffer; + + /// + /// Use exponential large buffer allocation strategy. It must be set at creation and cannot be changed. + /// + public bool UseExponentialLargeBuffer { get; } + + /// + /// Gets the maximum buffer size. + /// + /// Any buffer that is returned to the pool that is larger than this will be + /// discarded and garbage collected. + public int MaximumBufferSize { get; } + + /// + /// Number of bytes in small pool not currently in use + /// + public long SmallPoolFreeSize => this.smallPoolFreeSize; + + /// + /// Number of bytes currently in use by stream from the small pool + /// + public long SmallPoolInUseSize => this.smallPoolInUseSize; + + /// + /// Number of bytes in large pool not currently in use + /// + public long LargePoolFreeSize + { + get + { + long sum = 0; + foreach (long freeSize in this.largeBufferFreeSize) + { + sum += freeSize; + } + + return sum; + } + } + + /// + /// Number of bytes currently in use by streams from the large pool + /// + public long LargePoolInUseSize + { + get + { + long sum = 0; + foreach (long inUseSize in this.largeBufferInUseSize) + { + sum += inUseSize; + } + + return sum; + } + } + + /// + /// How many blocks are in the small pool + /// + public long SmallBlocksFree => this.smallPool.Count; + + /// + /// How many buffers are in the large pool + /// + public long LargeBuffersFree + { + get + { + long free = 0; + foreach (var pool in this.largePools) + { + free += pool.Count; + } + return free; + } + } + + /// + /// How many bytes of small free blocks to allow before we start dropping + /// those returned to us. + /// + public long MaximumFreeSmallPoolBytes { get; set; } + + /// + /// How many bytes of large free buffers to allow before we start dropping + /// those returned to us. + /// + public long MaximumFreeLargePoolBytes { get; set; } + + /// + /// Maximum stream capacity in bytes. Attempts to set a larger capacity will + /// result in an exception. + /// + /// A value of 0 indicates no limit. + public long MaximumStreamCapacity { get; set; } + + /// + /// Whether to save callstacks for stream allocations. This can help in debugging. + /// It should NEVER be turned on generally in production. + /// + public bool GenerateCallStacks { get; set; } + + /// + /// Whether dirty buffers can be immediately returned to the buffer pool. E.g. when GetBuffer() is called on + /// a stream and creates a single large buffer, if this setting is enabled, the other blocks will be returned + /// to the buffer pool immediately. + /// Note when enabling this setting that the user is responsible for ensuring that any buffer previously + /// retrieved from a stream which is subsequently modified is not used after modification (as it may no longer + /// be valid). + /// + public bool AggressiveBufferReturn { get; set; } + + /// + /// Causes an exception to be thrown if ToArray is ever called. + /// + /// Calling ToArray defeats the purpose of a pooled buffer. Use this property to discover code that is calling ToArray. If this is + /// set and stream.ToArray() is called, a NotSupportedException will be thrown. + public bool ThrowExceptionOnToArray { get; set; } + + /// + /// Removes and returns a single block from the pool. + /// + /// A byte[] array + internal byte[] GetBlock() + { + byte[] block; + if (!this.smallPool.TryPop(out block)) + { + // We'll add this back to the pool when the stream is disposed + // (unless our free pool is too large) + block = new byte[this.BlockSize]; + Events.Writer.MemoryStreamNewBlockCreated(this.smallPoolInUseSize); + ReportBlockCreated(); + } + else + { + Interlocked.Add(ref this.smallPoolFreeSize, -this.BlockSize); + } + + Interlocked.Add(ref this.smallPoolInUseSize, this.BlockSize); + return block; + } + + /// + /// Returns a buffer of arbitrary size from the large buffer pool. This buffer + /// will be at least the requiredSize and always be a multiple/exponential of largeBufferMultiple. + /// + /// The minimum length of the buffer + /// The tag of the stream returning this buffer, for logging if necessary. + /// A buffer of at least the required size. + internal byte[] GetLargeBuffer(int requiredSize, string tag) + { + requiredSize = this.RoundToLargeBufferSize(requiredSize); + + var poolIndex = this.GetPoolIndex(requiredSize); + + byte[] buffer; + if (poolIndex < this.largePools.Length) + { + if (!this.largePools[poolIndex].TryPop(out buffer)) + { + buffer = new byte[requiredSize]; + + Events.Writer.MemoryStreamNewLargeBufferCreated(requiredSize, this.LargePoolInUseSize); + ReportLargeBufferCreated(); + } + else + { + Interlocked.Add(ref this.largeBufferFreeSize[poolIndex], -buffer.Length); + } + } + else + { + // Buffer is too large to pool. They get a new buffer. + + // We still want to track the size, though, and we've reserved a slot + // in the end of the inuse array for nonpooled bytes in use. + poolIndex = this.largeBufferInUseSize.Length - 1; + + // We still want to round up to reduce heap fragmentation. + buffer = new byte[requiredSize]; + string callStack = null; + if (this.GenerateCallStacks) + { + // Grab the stack -- we want to know who requires such large buffers + callStack = Environment.StackTrace; + } + Events.Writer.MemoryStreamNonPooledLargeBufferCreated(requiredSize, tag, callStack); + ReportLargeBufferCreated(); + } + + Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], buffer.Length); + + return buffer; + } + + private int RoundToLargeBufferSize(int requiredSize) + { + if (this.UseExponentialLargeBuffer) + { + int pow = 1; + while (this.LargeBufferMultiple * pow < requiredSize) + { + pow <<= 1; + } + return this.LargeBufferMultiple * pow; + } + else + { + return ((requiredSize + this.LargeBufferMultiple - 1) / this.LargeBufferMultiple) * this.LargeBufferMultiple; + } + } + + private bool IsLargeBufferSize(int value) + { + return (value != 0) && (this.UseExponentialLargeBuffer + ? (value == RoundToLargeBufferSize(value)) + : (value % this.LargeBufferMultiple) == 0); + } + + private int GetPoolIndex(int length) + { + if (this.UseExponentialLargeBuffer) + { + int index = 0; + while ((this.LargeBufferMultiple << index) < length) + { + ++index; + } + return index; + } + else + { + return length / this.LargeBufferMultiple - 1; + } + } + + /// + /// Returns the buffer to the large pool + /// + /// The buffer to return. + /// The tag of the stream returning this buffer, for logging if necessary. + /// buffer is null + /// buffer.Length is not a multiple/exponential of LargeBufferMultiple (it did not originate from this pool) + internal void ReturnLargeBuffer(byte[] buffer, string tag) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (!this.IsLargeBufferSize(buffer.Length)) + { + throw new ArgumentException( + String.Format("buffer did not originate from this memory manager. The size is not {0} of ", + this.UseExponentialLargeBuffer ? "an exponential" : "a multiple") + + this.LargeBufferMultiple); + } + + var poolIndex = this.GetPoolIndex(buffer.Length); + + if (poolIndex < this.largePools.Length) + { + if ((this.largePools[poolIndex].Count + 1) * buffer.Length <= this.MaximumFreeLargePoolBytes || + this.MaximumFreeLargePoolBytes == 0) + { + this.largePools[poolIndex].Push(buffer); + Interlocked.Add(ref this.largeBufferFreeSize[poolIndex], buffer.Length); + } + else + { + Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag, + Events.MemoryStreamDiscardReason.EnoughFree); + ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason.EnoughFree); + } + } + else + { + // This is a non-poolable buffer, but we still want to track its size for inuse + // analysis. We have space in the inuse array for this. + poolIndex = this.largeBufferInUseSize.Length - 1; + + Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag, + Events.MemoryStreamDiscardReason.TooLarge); + ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason.TooLarge); + } + + Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], -buffer.Length); + + ReportUsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize, + this.LargePoolFreeSize); + } + + /// + /// Returns the blocks to the pool + /// + /// Collection of blocks to return to the pool + /// The tag of the stream returning these blocks, for logging if necessary. + /// blocks is null + /// blocks contains buffers that are the wrong size (or null) for this memory manager + internal void ReturnBlocks(ICollection blocks, string tag) + { + if (blocks == null) + { + throw new ArgumentNullException(nameof(blocks)); + } + + var bytesToReturn = blocks.Count * this.BlockSize; + Interlocked.Add(ref this.smallPoolInUseSize, -bytesToReturn); + + foreach (var block in blocks) + { + if (block == null || block.Length != this.BlockSize) + { + throw new ArgumentException("blocks contains buffers that are not BlockSize in length"); + } + } + + foreach (var block in blocks) + { + if (this.MaximumFreeSmallPoolBytes == 0 || this.SmallPoolFreeSize < this.MaximumFreeSmallPoolBytes) + { + Interlocked.Add(ref this.smallPoolFreeSize, this.BlockSize); + this.smallPool.Push(block); + } + else + { + Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Small, tag, + Events.MemoryStreamDiscardReason.EnoughFree); + ReportBlockDiscarded(); + break; + } + } + + ReportUsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize, + this.LargePoolFreeSize); + } + + internal void ReportBlockCreated() + { + this.BlockCreated?.Invoke(); + } + + internal void ReportBlockDiscarded() + { + this.BlockDiscarded?.Invoke(); + } + + internal void ReportLargeBufferCreated() + { + this.LargeBufferCreated?.Invoke(); + } + + internal void ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason reason) + { + this.LargeBufferDiscarded?.Invoke(reason); + } + + internal void ReportStreamCreated() + { + this.StreamCreated?.Invoke(); + } + + internal void ReportStreamDisposed() + { + this.StreamDisposed?.Invoke(); + } + + internal void ReportStreamFinalized() + { + this.StreamFinalized?.Invoke(); + } + + internal void ReportStreamLength(long bytes) + { + this.StreamLength?.Invoke(bytes); + } + + internal void ReportStreamToArray() + { + this.StreamConvertedToArray?.Invoke(); + } + + internal void ReportUsageReport( + long smallPoolInUseBytes, long smallPoolFreeBytes, long largePoolInUseBytes, long largePoolFreeBytes) + { + this.UsageReport?.Invoke(smallPoolInUseBytes, smallPoolFreeBytes, largePoolInUseBytes, largePoolFreeBytes); + } + + /// + /// Retrieve a new MemoryStream object with no tag and a default initial capacity. + /// + /// A MemoryStream. + public MemoryStream GetStream() + { + return new RecyclableMemoryStream(this); + } + + /// + /// Retrieve a new MemoryStream object with no tag and a default initial capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id) + { + return new RecyclableMemoryStream(this, id); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and a default initial capacity. + /// + /// A tag which can be used to track the source of the stream. + /// A MemoryStream. + public MemoryStream GetStream(string tag) + { + return new RecyclableMemoryStream(this, tag); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and a default initial capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag) + { + return new RecyclableMemoryStream(this, id, tag); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity. + /// + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// A MemoryStream. + public MemoryStream GetStream(string tag, int requiredSize) + { + return new RecyclableMemoryStream(this, tag, requiredSize); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, int requiredSize) + { + return new RecyclableMemoryStream(this, id, tag, requiredSize); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using + /// a single contiguous underlying buffer. + /// + /// Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations + /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying + /// buffers to a single large one. This is most helpful when you know that you will always call GetBuffer + /// on the underlying stream. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// Whether to attempt to use a single contiguous buffer. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, int requiredSize, bool asContiguousBuffer) + { + if (!asContiguousBuffer || requiredSize <= this.BlockSize) + { + return this.GetStream(id, tag, requiredSize); + } + + return new RecyclableMemoryStream(this, id, tag, requiredSize, this.GetLargeBuffer(requiredSize, tag)); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using + /// a single contiguous underlying buffer. + /// + /// Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations + /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying + /// buffers to a single large one. This is most helpful when you know that you will always call GetBuffer + /// on the underlying stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// Whether to attempt to use a single contiguous buffer. + /// A MemoryStream. + public MemoryStream GetStream(string tag, int requiredSize, bool asContiguousBuffer) + { + return GetStream(Guid.NewGuid(), tag, requiredSize, asContiguousBuffer); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// The offset from the start of the buffer to copy from. + /// The number of bytes to copy from the buffer. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(this, id, tag, count); + stream.Write(buffer, offset, count); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + + /// + /// Retrieve a new MemoryStream object with the contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(byte[] buffer) + { + return GetStream(null, buffer, 0, buffer.Length); + } + + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// The offset from the start of the buffer to copy from. + /// The number of bytes to copy from the buffer. + /// A MemoryStream. + public MemoryStream GetStream(string tag, byte[] buffer, int offset, int count) + { + return GetStream(Guid.NewGuid(), tag, buffer, offset, count); + } + +#if NETCORE && !NETSTANDARD2_0 + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, Memory buffer) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(this, id, tag, buffer.Length); + stream.Write(buffer.Span); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + + /// + /// Retrieve a new MemoryStream object with the contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(Memory buffer) + { + return GetStream(null, buffer); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(string tag, Memory buffer) + { + return GetStream(Guid.NewGuid(), tag, buffer); + } +#endif + /// + /// Triggered when a new block is created. + /// + public event EventHandler BlockCreated; + + /// + /// Triggered when a new block is created. + /// + public event EventHandler BlockDiscarded; + + /// + /// Triggered when a new large buffer is created. + /// + public event EventHandler LargeBufferCreated; + + /// + /// Triggered when a new stream is created. + /// + public event EventHandler StreamCreated; + + /// + /// Triggered when a stream is disposed. + /// + public event EventHandler StreamDisposed; + + /// + /// Triggered when a stream is finalized. + /// + public event EventHandler StreamFinalized; + + /// + /// Triggered when a stream is finalized. + /// + public event StreamLengthReportHandler StreamLength; + + /// + /// Triggered when a user converts a stream to array. + /// + public event EventHandler StreamConvertedToArray; + + /// + /// Triggered when a large buffer is discarded, along with the reason for the discard. + /// + public event LargeBufferDiscardedEventHandler LargeBufferDiscarded; + + /// + /// Periodically triggered to report usage statistics. + /// + public event UsageReportEventHandler UsageReport; + } + + + /// + /// MemoryStream implementation that deals with pooling and managing memory streams which use potentially large + /// buffers. + /// + /// + /// This class works in tandem with the RecyclableMemoryStreamManager to supply MemoryStream + /// objects to callers, while avoiding these specific problems: + /// 1. LOH allocations - since all large buffers are pooled, they will never incur a Gen2 GC + /// 2. Memory waste - A standard memory stream doubles its size when it runs out of room. This + /// leads to continual memory growth as each stream approaches the maximum allowed size. + /// 3. Memory copying - Each time a MemoryStream grows, all the bytes are copied into new buffers. + /// This implementation only copies the bytes when GetBuffer is called. + /// 4. Memory fragmentation - By using homogeneous buffer sizes, it ensures that blocks of memory + /// can be easily reused. + /// + /// The stream is implemented on top of a series of uniformly-sized blocks. As the stream's length grows, + /// additional blocks are retrieved from the memory manager. It is these blocks that are pooled, not the stream + /// object itself. + /// + /// The biggest wrinkle in this implementation is when GetBuffer() is called. This requires a single + /// contiguous buffer. If only a single block is in use, then that block is returned. If multiple blocks + /// are in use, we retrieve a larger buffer from the memory manager. These large buffers are also pooled, + /// split by size--they are multiples/exponentials of a chunk size (1 MB by default). + /// + /// Once a large buffer is assigned to the stream the small blocks are NEVER again used for this stream. All operations take place on the + /// large buffer. The large buffer can be replaced by a larger buffer from the pool as needed. All blocks and large buffers + /// are maintained in the stream until the stream is disposed (unless AggressiveBufferReturn is enabled in the stream manager). + /// + /// + public sealed class RecyclableMemoryStream : MemoryStream + { + private const long MaxStreamLength = Int32.MaxValue; + + private static readonly byte[] emptyArray = new byte[0]; + + /// + /// All of these blocks must be the same size + /// + private readonly List blocks = new List(1); + + private readonly Guid id; + + private readonly RecyclableMemoryStreamManager memoryManager; + + private readonly string tag; + + /// + /// This list is used to store buffers once they're replaced by something larger. + /// This is for the cases where you have users of this class that may hold onto the buffers longer + /// than they should and you want to prevent race conditions which could corrupt the data. + /// + private List dirtyBuffers; + + // long to allow Interlocked.Read (for .NET Standard 1.4 compat) + private long disposedState; + + /// + /// This is only set by GetBuffer() if the necessary buffer is larger than a single block size, or on + /// construction if the caller immediately requests a single large buffer. + /// + /// If this field is non-null, it contains the concatenation of the bytes found in the individual + /// blocks. Once it is created, this (or a larger) largeBuffer will be used for the life of the stream. + /// + private byte[] largeBuffer; + + /// + /// Unique identifier for this stream across its entire lifetime + /// + /// Object has been disposed + internal Guid Id + { + get + { + this.CheckDisposed(); + return this.id; + } + } + + /// + /// A temporary identifier for the current usage of this stream. + /// + /// Object has been disposed + internal string Tag + { + get + { + this.CheckDisposed(); + return this.tag; + } + } + + /// + /// Gets the memory manager being used by this stream. + /// + /// Object has been disposed + internal RecyclableMemoryStreamManager MemoryManager + { + get + { + this.CheckDisposed(); + return this.memoryManager; + } + } + + /// + /// Callstack of the constructor. It is only set if MemoryManager.GenerateCallStacks is true, + /// which should only be in debugging situations. + /// + internal string AllocationStack { get; } + + /// + /// Callstack of the Dispose call. It is only set if MemoryManager.GenerateCallStacks is true, + /// which should only be in debugging situations. + /// + internal string DisposeStack { get; private set; } + + #region Constructors + /// + /// Allocate a new RecyclableMemoryStream object. + /// + /// The memory manager + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager) + : this(memoryManager, Guid.NewGuid(), null, 0, null) { } + + /// + /// Allocate a new RecyclableMemoryStream object. + /// + /// The memory manager + /// A unique identifier which can be used to trace usages of the stream. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id) + : this(memoryManager, id, null, 0, null) { } + + /// + /// Allocate a new RecyclableMemoryStream object + /// + /// The memory manager + /// A string identifying this stream for logging and debugging purposes + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag) + : this(memoryManager, Guid.NewGuid(), tag, 0, null) { } + + /// + /// Allocate a new RecyclableMemoryStream object + /// + /// The memory manager + /// A unique identifier which can be used to trace usages of the stream. + /// A string identifying this stream for logging and debugging purposes + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag) + : this(memoryManager, id, tag, 0, null) { } + + /// + /// Allocate a new RecyclableMemoryStream object + /// + /// The memory manager + /// A string identifying this stream for logging and debugging purposes + /// The initial requested size to prevent future allocations + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag, int requestedSize) + : this(memoryManager, Guid.NewGuid(), tag, requestedSize, null) { } + + /// + /// Allocate a new RecyclableMemoryStream object + /// + /// The memory manager + /// A unique identifier which can be used to trace usages of the stream. + /// A string identifying this stream for logging and debugging purposes + /// The initial requested size to prevent future allocations + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag, int requestedSize) + : this(memoryManager, id, tag, requestedSize, null) { } + + /// + /// Allocate a new RecyclableMemoryStream object + /// + /// The memory manager + /// A unique identifier which can be used to trace usages of the stream. + /// A string identifying this stream for logging and debugging purposes + /// The initial requested size to prevent future allocations + /// An initial buffer to use. This buffer will be owned by the stream and returned to the memory manager upon Dispose. + internal RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag, int requestedSize, byte[] initialLargeBuffer) + : base(emptyArray) + { + this.memoryManager = memoryManager; + this.id = id; + this.tag = tag; + + if (requestedSize < memoryManager.BlockSize) + { + requestedSize = memoryManager.BlockSize; + } + + if (initialLargeBuffer == null) + { + this.EnsureCapacity(requestedSize); + } + else + { + this.largeBuffer = initialLargeBuffer; + } + + if (this.memoryManager.GenerateCallStacks) + { + this.AllocationStack = Environment.StackTrace; + } + + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamCreated(this.id, this.tag, requestedSize); + this.memoryManager.ReportStreamCreated(); + } + #endregion + + #region Dispose and Finalize + /// + /// The finalizer will be called when a stream is not disposed properly. + /// + /// Failing to dispose indicates a bug in the code using streams. Care should be taken to properly account for stream lifetime. + ~RecyclableMemoryStream() + { + this.Dispose(false); + } + + /// + /// Returns the memory used by this stream back to the pool. + /// + /// Whether we're disposing (true), or being called by the finalizer (false) + [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly", + Justification = "We have different disposal semantics, so SuppressFinalize is in a different spot.")] + protected override void Dispose(bool disposing) + { + if (Interlocked.CompareExchange(ref this.disposedState, 1, 0) != 0) + { + string doubleDisposeStack = null; + if (this.memoryManager.GenerateCallStacks) + { + doubleDisposeStack = Environment.StackTrace; + } + + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamDoubleDispose(this.id, this.tag, + this.AllocationStack, this.DisposeStack, doubleDisposeStack); + return; + } + + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamDisposed(this.id, this.tag); + + if (this.memoryManager.GenerateCallStacks) + { + this.DisposeStack = Environment.StackTrace; + } + + if (disposing) + { + this.memoryManager.ReportStreamDisposed(); + + GC.SuppressFinalize(this); + } + else + { + // We're being finalized. + + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamFinalized(this.id, this.tag, this.AllocationStack); + +#if !NETSTANDARD1_4 + if (AppDomain.CurrentDomain.IsFinalizingForUnload()) + { + // If we're being finalized because of a shutdown, don't go any further. + // We have no idea what's already been cleaned up. Triggering events may cause + // a crash. + base.Dispose(disposing); + return; + } +#endif + + this.memoryManager.ReportStreamFinalized(); + } + + this.memoryManager.ReportStreamLength(this.length); + + if (this.largeBuffer != null) + { + this.memoryManager.ReturnLargeBuffer(this.largeBuffer, this.tag); + } + + if (this.dirtyBuffers != null) + { + foreach (var buffer in this.dirtyBuffers) + { + this.memoryManager.ReturnLargeBuffer(buffer, this.tag); + } + } + + this.memoryManager.ReturnBlocks(this.blocks, this.tag); + this.blocks.Clear(); + + base.Dispose(disposing); + } + + /// + /// Equivalent to Dispose + /// +#if NETSTANDARD1_4 + public void Close() +#else + public override void Close() +#endif + { + this.Dispose(true); + } + #endregion + + #region MemoryStream overrides + /// + /// Gets or sets the capacity + /// + /// Capacity is always in multiples of the memory manager's block size, unless + /// the large buffer is in use. Capacity never decreases during a stream's lifetime. + /// Explicitly setting the capacity to a lower value than the current value will have no effect. + /// This is because the buffers are all pooled by chunks and there's little reason to + /// allow stream truncation. + /// + /// Writing past the current capacity will cause Capacity to automatically increase, until MaximumStreamCapacity is reached. + /// + /// Object has been disposed + public override int Capacity + { + get + { + this.CheckDisposed(); + if (this.largeBuffer != null) + { + return this.largeBuffer.Length; + } + + long size = (long)this.blocks.Count * this.memoryManager.BlockSize; + return (int)Math.Min(int.MaxValue, size); + } + set + { + this.CheckDisposed(); + this.EnsureCapacity(value); + } + } + + private int length; + + /// + /// Gets the number of bytes written to this stream. + /// + /// Object has been disposed + public override long Length + { + get + { + this.CheckDisposed(); + return this.length; + } + } + + private int position; + + /// + /// Gets the current position in the stream + /// + /// Object has been disposed + public override long Position + { + get + { + this.CheckDisposed(); + return this.position; + } + set + { + this.CheckDisposed(); + if (value < 0) + { + throw new ArgumentOutOfRangeException("value", "value must be non-negative"); + } + + if (value > MaxStreamLength) + { + throw new ArgumentOutOfRangeException("value", "value cannot be more than " + MaxStreamLength); + } + + this.position = (int)value; + } + } + + /// + /// Whether the stream can currently read + /// + public override bool CanRead => !this.Disposed; + + /// + /// Whether the stream can currently seek + /// + public override bool CanSeek => !this.Disposed; + + /// + /// Always false + /// + public override bool CanTimeout => false; + + /// + /// Whether the stream can currently write + /// + public override bool CanWrite => !this.Disposed; + + /// + /// Returns a single buffer containing the contents of the stream. + /// The buffer may be longer than the stream length. + /// + /// A byte[] buffer + /// IMPORTANT: Doing a Write() after calling GetBuffer() invalidates the buffer. The old buffer is held onto + /// until Dispose is called, but the next time GetBuffer() is called, a new buffer from the pool will be required. + /// Object has been disposed +#if NETSTANDARD1_4 + public byte[] GetBuffer() +#else + public override byte[] GetBuffer() +#endif + { + this.CheckDisposed(); + + if (this.largeBuffer != null) + { + return this.largeBuffer; + } + + if (this.blocks.Count == 1) + { + return this.blocks[0]; + } + + // Buffer needs to reflect the capacity, not the length, because + // it's possible that people will manipulate the buffer directly + // and set the length afterward. Capacity sets the expectation + // for the size of the buffer. + var newBuffer = this.memoryManager.GetLargeBuffer(this.Capacity, this.tag); + + // InternalRead will check for existence of largeBuffer, so make sure we + // don't set it until after we've copied the data. + this.InternalRead(newBuffer, 0, this.length, 0); + this.largeBuffer = newBuffer; + + if (this.blocks.Count > 0 && this.memoryManager.AggressiveBufferReturn) + { + this.memoryManager.ReturnBlocks(this.blocks, this.tag); + this.blocks.Clear(); + } + + return this.largeBuffer; + } + + /// + /// Returns an ArraySegment that wraps a single buffer containing the contents of the stream. + /// + /// An ArraySegment containing a reference to the underlying bytes. + /// Always returns true. + /// GetBuffer has no failure modes (it always returns something, even if it's an empty buffer), therefore this method + /// always returns a valid ArraySegment to the same buffer returned by GetBuffer. +#if NET40 || NET45 + public bool TryGetBuffer(out ArraySegment buffer) +#else + public override bool TryGetBuffer(out ArraySegment buffer) +#endif + { + this.CheckDisposed(); + buffer = new ArraySegment(this.GetBuffer(), 0, (int)this.Length); + // GetBuffer has no failure modes, so this should always succeed + return true; + } + + /// + /// Returns a new array with a copy of the buffer's contents. You should almost certainly be using GetBuffer combined with the Length to + /// access the bytes in this stream. Calling ToArray will destroy the benefits of pooled buffers, but it is included + /// for the sake of completeness. + /// + /// Object has been disposed + /// The current RecyclableStreamManager object disallows ToArray calls. +#pragma warning disable CS0809 + [Obsolete("This method has degraded performance vs. GetBuffer and should be avoided.")] + public override byte[] ToArray() + { + this.CheckDisposed(); + + string stack = this.memoryManager.GenerateCallStacks ? Environment.StackTrace : null; + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamToArray(this.id, this.tag, stack, this.length); + + if (this.memoryManager.ThrowExceptionOnToArray) + { + throw new NotSupportedException("The underlying RecyclableMemoryStreamManager is configured to not allow calls to ToArray."); + } + + var newBuffer = new byte[this.Length]; + + this.InternalRead(newBuffer, 0, this.length, 0); + this.memoryManager.ReportStreamToArray(); + + return newBuffer; + } +#pragma warning restore CS0809 + + /// + /// Reads from the current position into the provided buffer + /// + /// Destination buffer + /// Offset into buffer at which to start placing the read bytes. + /// Number of bytes to read. + /// The number of bytes read + /// buffer is null + /// offset or count is less than 0 + /// offset subtracted from the buffer length is less than count + /// Object has been disposed + public override int Read(byte[] buffer, int offset, int count) + { + return this.SafeRead(buffer, offset, count, ref this.position); + } + + /// + /// Reads from the specified position into the provided buffer + /// + /// Destination buffer + /// Offset into buffer at which to start placing the read bytes. + /// Number of bytes to read. + /// Position in the stream to start reading from + /// The number of bytes read + /// buffer is null + /// offset or count is less than 0 + /// offset subtracted from the buffer length is less than count + /// Object has been disposed + public int SafeRead(byte[] buffer, int offset, int count, ref int streamPosition) + { + this.CheckDisposed(); + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be negative"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative"); + } + + if (offset + count > buffer.Length) + { + throw new ArgumentException("buffer length must be at least offset + count"); + } + + int amountRead = this.InternalRead(buffer, offset, count, streamPosition); + streamPosition += amountRead; + return amountRead; + } + +#if !NETSTANDARD2_0 && NETCORE + /// + /// Reads from the current position into the provided buffer + /// + /// Destination buffer + /// The number of bytes read + /// Object has been disposed + public override int Read(Span buffer) + { + return this.SafeRead(buffer, ref this.position); + } + + /// + /// Reads from the specified position into the provided buffer + /// + /// Destination buffer + /// Position in the stream to start reading from + /// The number of bytes read + /// Object has been disposed + public int SafeRead(Span buffer, ref int streamPosition) + { + this.CheckDisposed(); + + int amountRead = this.InternalRead(buffer, streamPosition); + streamPosition += amountRead; + return amountRead; + } +#endif + + /// + /// Writes the buffer to the stream + /// + /// Source buffer + /// Start position + /// Number of bytes to write + /// buffer is null + /// offset or count is negative + /// buffer.Length - offset is not less than count + /// Object has been disposed + public override void Write(byte[] buffer, int offset, int count) + { + this.CheckDisposed(); + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), offset, + "Offset must be in the range of 0 - buffer.Length-1"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), count, "count must be non-negative"); + } + + if (count + offset > buffer.Length) + { + throw new ArgumentException("count must be greater than buffer.Length - offset"); + } + + int blockSize = this.memoryManager.BlockSize; + long end = (long)this.position + count; + // Check for overflow + if (end > MaxStreamLength) + { + throw new IOException("Maximum capacity exceeded"); + } + + this.EnsureCapacity((int)end); + + if (this.largeBuffer == null) + { + int bytesRemaining = count; + int bytesWritten = 0; + var blockAndOffset = this.GetBlockAndRelativeOffset(this.position); + + while (bytesRemaining > 0) + { + byte[] currentBlock = this.blocks[blockAndOffset.Block]; + int remainingInBlock = blockSize - blockAndOffset.Offset; + int amountToWriteInBlock = Math.Min(remainingInBlock, bytesRemaining); + + Buffer.BlockCopy(buffer, offset + bytesWritten, currentBlock, blockAndOffset.Offset, + amountToWriteInBlock); + + bytesRemaining -= amountToWriteInBlock; + bytesWritten += amountToWriteInBlock; + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + } + else + { + Buffer.BlockCopy(buffer, offset, this.largeBuffer, this.position, count); + } + this.position = (int)end; + this.length = Math.Max(this.position, this.length); + } + +#if !NETSTANDARD2_0 && NETCORE + /// + /// Writes the buffer to the stream + /// + /// Source buffer + /// buffer is null + /// Object has been disposed + public override void Write(ReadOnlySpan source) + { + this.CheckDisposed(); + + int blockSize = this.memoryManager.BlockSize; + long end = (long)this.position + source.Length; + // Check for overflow + if (end > MaxStreamLength) + { + throw new IOException("Maximum capacity exceeded"); + } + + this.EnsureCapacity((int)end); + + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(this.position); + + while (source.Length > 0) + { + byte[] currentBlock = this.blocks[blockAndOffset.Block]; + int remainingInBlock = blockSize - blockAndOffset.Offset; + int amountToWriteInBlock = Math.Min(remainingInBlock, source.Length); + + source.Slice(0, amountToWriteInBlock) + .CopyTo(currentBlock.AsSpan(blockAndOffset.Offset)); + + source = source.Slice(amountToWriteInBlock); + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + } + else + { + source.CopyTo(this.largeBuffer.AsSpan(this.position)); + } + this.position = (int)end; + this.length = Math.Max(this.position, this.length); + } +#endif + + /// + /// Returns a useful string for debugging. This should not normally be called in actual production code. + /// + public override string ToString() + { + return $"Id = {this.Id}, Tag = {this.Tag}, Length = {this.Length:N0} bytes"; + } + + /// + /// Writes a single byte to the current position in the stream. + /// + /// byte value to write + /// Object has been disposed + public override void WriteByte(byte value) + { + this.CheckDisposed(); + + long end = (long)this.position + 1; + + // Check for overflow + if (end > MaxStreamLength) + { + throw new IOException("Maximum capacity exceeded"); + } + + if (this.largeBuffer == null) + { + var blockSize = this.memoryManager.BlockSize; + + var block = this.position / blockSize; + + if (block >= this.blocks.Count) + { + this.EnsureCapacity((int)end); + } + + this.blocks[block][this.position % blockSize] = value; + } + else + { + if (this.position >= this.largeBuffer.Length) + { + this.EnsureCapacity((int)end); + } + + this.largeBuffer[this.position] = value; + } + + this.position = (int)end; + + if (this.position > this.length) + { + this.length = this.position; + } + } + + /// + /// Reads a single byte from the current position in the stream. + /// + /// The byte at the current position, or -1 if the position is at the end of the stream. + /// Object has been disposed + public override int ReadByte() + { + return this.SafeReadByte(ref this.position); + } + + /// + /// Reads a single byte from the specified position in the stream. + /// + /// The position in the stream to read from + /// The byte at the current position, or -1 if the position is at the end of the stream. + /// Object has been disposed + public int SafeReadByte(ref int streamPosition) + { + this.CheckDisposed(); + if (streamPosition == this.length) + { + return -1; + } + byte value; + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(streamPosition); + value = this.blocks[blockAndOffset.Block][blockAndOffset.Offset]; + } + else + { + value = this.largeBuffer[streamPosition]; + } + streamPosition++; + return value; + } + + /// + /// Sets the length of the stream + /// + /// value is negative or larger than MaxStreamLength + /// Object has been disposed + public override void SetLength(long value) + { + this.CheckDisposed(); + if (value < 0 || value > MaxStreamLength) + { + throw new ArgumentOutOfRangeException(nameof(value), + "value must be non-negative and at most " + MaxStreamLength); + } + + this.EnsureCapacity((int)value); + + this.length = (int)value; + if (this.position > value) + { + this.position = (int)value; + } + } + + /// + /// Sets the position to the offset from the seek location + /// + /// How many bytes to move + /// From where + /// The new position + /// Object has been disposed + /// offset is larger than MaxStreamLength + /// Invalid seek origin + /// Attempt to set negative position + public override long Seek(long offset, SeekOrigin loc) + { + this.CheckDisposed(); + if (offset > MaxStreamLength) + { + throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be larger than " + MaxStreamLength); + } + + int newPosition; + switch (loc) + { + case SeekOrigin.Begin: + newPosition = (int)offset; + break; + case SeekOrigin.Current: + newPosition = (int)offset + this.position; + break; + case SeekOrigin.End: + newPosition = (int)offset + this.length; + break; + default: + throw new ArgumentException("Invalid seek origin", nameof(loc)); + } + if (newPosition < 0) + { + throw new IOException("Seek before beginning"); + } + this.position = newPosition; + return this.position; + } + + /// + /// Synchronously writes this stream's bytes to the argument stream. + /// + /// Destination stream + /// Important: This does a synchronous write, which may not be desired in some situations + /// stream is null + public override void WriteTo(Stream stream) + { + this.WriteTo(stream, 0, this.length); + } + + /// + /// Synchronously writes this stream's bytes, starting at offset, for count bytes, to the argument stream. + /// + /// Destination stream + /// Offset in source + /// Number of bytes to write + /// stream is null + /// Offset is less than 0, or offset + count is beyond this stream's length. + public void WriteTo(Stream stream, int offset, int count) + { + this.CheckDisposed(); + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (offset < 0 || offset + count > this.length) + { + throw new ArgumentOutOfRangeException(message: "offset must not be negative and offset + count must not exceed the length of the stream", innerException: null); + } + + if (this.largeBuffer == null) + { + var blockAndOffset = GetBlockAndRelativeOffset(offset); + int bytesRemaining = count; + int currentBlock = blockAndOffset.Block; + int currentOffset = blockAndOffset.Offset; + + while (bytesRemaining > 0) + { + int amountToCopy = Math.Min(this.blocks[currentBlock].Length - currentOffset, bytesRemaining); + stream.Write(this.blocks[currentBlock], currentOffset, amountToCopy); + + bytesRemaining -= amountToCopy; + + ++currentBlock; + currentOffset = 0; + } + } + else + { + stream.Write(this.largeBuffer, offset, count); + } + + } +#endregion + +#region Helper Methods + private bool Disposed => Interlocked.Read(ref this.disposedState) != 0; + + [MethodImpl((MethodImplOptions)256)] + private void CheckDisposed() + { + if (this.Disposed) + { + this.ThrowDisposedException(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowDisposedException() + { + throw new ObjectDisposedException($"The stream with Id {this.id} and Tag {this.tag} is disposed."); + } + + private int InternalRead(byte[] buffer, int offset, int count, int fromPosition) + { + if (this.length - fromPosition <= 0) + { + return 0; + } + + int amountToCopy; + + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(fromPosition); + int bytesWritten = 0; + int bytesRemaining = Math.Min(count, this.length - fromPosition); + + while (bytesRemaining > 0) + { + amountToCopy = Math.Min(this.blocks[blockAndOffset.Block].Length - blockAndOffset.Offset, + bytesRemaining); + Buffer.BlockCopy(this.blocks[blockAndOffset.Block], blockAndOffset.Offset, buffer, + bytesWritten + offset, amountToCopy); + + bytesWritten += amountToCopy; + bytesRemaining -= amountToCopy; + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + return bytesWritten; + } + amountToCopy = Math.Min(count, this.length - fromPosition); + Buffer.BlockCopy(this.largeBuffer, fromPosition, buffer, offset, amountToCopy); + return amountToCopy; + } + +#if NETCORE + private int InternalRead(Span buffer, int fromPosition) + { + if (this.length - fromPosition <= 0) + { + return 0; + } + + int amountToCopy; + + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(fromPosition); + int bytesWritten = 0; + int bytesRemaining = Math.Min(buffer.Length, this.length - fromPosition); + + while (bytesRemaining > 0) + { + amountToCopy = Math.Min(this.blocks[blockAndOffset.Block].Length - blockAndOffset.Offset, + bytesRemaining); + this.blocks[blockAndOffset.Block].AsSpan(blockAndOffset.Offset, amountToCopy) + .CopyTo(buffer.Slice(bytesWritten)); + + bytesWritten += amountToCopy; + bytesRemaining -= amountToCopy; + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + return bytesWritten; + } + amountToCopy = Math.Min(buffer.Length, this.length - fromPosition); + this.largeBuffer.AsSpan(fromPosition, amountToCopy).CopyTo(buffer); + return amountToCopy; + } +#endif + + private struct BlockAndOffset + { + public int Block; + public int Offset; + + public BlockAndOffset(int block, int offset) + { + this.Block = block; + this.Offset = offset; + } + } + + [MethodImpl((MethodImplOptions)256)] + private BlockAndOffset GetBlockAndRelativeOffset(int offset) + { + var blockSize = this.memoryManager.BlockSize; + return new BlockAndOffset(offset / blockSize, offset % blockSize); + } + + private void EnsureCapacity(int newCapacity) + { + if (newCapacity > this.memoryManager.MaximumStreamCapacity && this.memoryManager.MaximumStreamCapacity > 0) + { + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamOverCapacity(newCapacity, + this.memoryManager.MaximumStreamCapacity, this.tag, this.AllocationStack); + throw new InvalidOperationException("Requested capacity is too large: " + newCapacity + ". Limit is " + + this.memoryManager.MaximumStreamCapacity); + } + + if (this.largeBuffer != null) + { + if (newCapacity > this.largeBuffer.Length) + { + var newBuffer = this.memoryManager.GetLargeBuffer(newCapacity, this.tag); + this.InternalRead(newBuffer, 0, this.length, 0); + this.ReleaseLargeBuffer(); + this.largeBuffer = newBuffer; + } + } + else + { + while (this.Capacity < newCapacity) + { + blocks.Add((this.memoryManager.GetBlock())); + } + } + } + + /// + /// Release the large buffer (either stores it for eventual release or returns it immediately). + /// + private void ReleaseLargeBuffer() + { + if (this.memoryManager.AggressiveBufferReturn) + { + this.memoryManager.ReturnLargeBuffer(this.largeBuffer, this.tag); + } + else + { + if (this.dirtyBuffers == null) + { + // We most likely will only ever need space for one + this.dirtyBuffers = new List(1); + } + this.dirtyBuffers.Add(this.largeBuffer); + } + + this.largeBuffer = null; + } +#endregion + } +} diff --git a/src/ServiceStack.Text/Reflection/StaticAccessors.cs b/src/ServiceStack.Text/Reflection/StaticAccessors.cs deleted file mode 100644 index fb6893ce8..000000000 --- a/src/ServiceStack.Text/Reflection/StaticAccessors.cs +++ /dev/null @@ -1,76 +0,0 @@ -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// -// Copyright 2012 ServiceStack Ltd. -// -// Licensed under the same terms of ServiceStack: new BSD license. -// -using System; -using System.Reflection; - -#if !XBOX -using System.Linq.Expressions; -#endif -namespace ServiceStack.Text.Reflection -{ - public static class StaticAccessors - { - public static Func GetValueGetter(this PropertyInfo propertyInfo, Type type) - { -#if (SILVERLIGHT && !WINDOWS_PHONE) || MONOTOUCH || XBOX - var getMethodInfo = propertyInfo.GetGetMethod(); - if (getMethodInfo == null) return null; - return x => getMethodInfo.Invoke(x, new object[0]); -#else - - var instance = Expression.Parameter(typeof(object), "i"); - var convertInstance = Expression.TypeAs(instance, propertyInfo.DeclaringType); - var property = Expression.Property(convertInstance, propertyInfo); - var convertProperty = Expression.TypeAs(property, typeof(object)); - return Expression.Lambda>(convertProperty, instance).Compile(); -#endif - } - - public static Func GetValueGetter(this PropertyInfo propertyInfo) - { -#if (SILVERLIGHT && !WINDOWS_PHONE) || MONOTOUCH || XBOX - var getMethodInfo = propertyInfo.GetGetMethod(); - if (getMethodInfo == null) return null; - return x => getMethodInfo.Invoke(x, new object[0]); -#else - var instance = Expression.Parameter(propertyInfo.DeclaringType, "i"); - var property = Expression.Property(instance, propertyInfo); - var convert = Expression.TypeAs(property, typeof(object)); - return Expression.Lambda>(convert, instance).Compile(); -#endif - } - -#if !XBOX - public static Action GetValueSetter(this PropertyInfo propertyInfo) - { - if (typeof(T) != propertyInfo.DeclaringType) - { - throw new ArgumentException(); - } - - var instance = Expression.Parameter(propertyInfo.DeclaringType, "i"); - var argument = Expression.Parameter(typeof(object), "a"); - var setterCall = Expression.Call( - instance, - propertyInfo.GetSetMethod(), - Expression.Convert(argument, propertyInfo.PropertyType)); - - return Expression.Lambda> - ( - setterCall, instance, argument - ).Compile(); - } -#endif - - } -} - diff --git a/src/ServiceStack.Text/ReflectionExtensions.cs b/src/ServiceStack.Text/ReflectionExtensions.cs index 3b05b3642..4f3aeae96 100644 --- a/src/ServiceStack.Text/ReflectionExtensions.cs +++ b/src/ServiceStack.Text/ReflectionExtensions.cs @@ -5,52 +5,35 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Reflection; -using System.Runtime.Serialization; using System.Threading; using ServiceStack.Text.Support; -#if WINDOWS_PHONE using System.Linq.Expressions; -#endif +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; + +using ServiceStack.Text; -namespace ServiceStack.Text +namespace ServiceStack { public delegate EmptyCtorDelegate EmptyCtorFactoryDelegate(Type type); public delegate object EmptyCtorDelegate(); public static class ReflectionExtensions { - private static Dictionary DefaultValueTypes = new Dictionary(); - - public static object GetDefaultValue(this Type type) + public static TypeCode GetTypeCode(this Type type) { - if (!type.IsValueType) return null; - - object defaultValue; - if (DefaultValueTypes.TryGetValue(type, out defaultValue)) return defaultValue; - - defaultValue = Activator.CreateInstance(type); - - Dictionary snapshot, newCache; - do - { - snapshot = DefaultValueTypes; - newCache = new Dictionary(DefaultValueTypes); - newCache[type] = defaultValue; - - } while (!ReferenceEquals( - Interlocked.CompareExchange(ref DefaultValueTypes, newCache, snapshot), snapshot)); - - return defaultValue; - } + return Type.GetTypeCode(type); + } public static bool IsInstanceOf(this Type type, Type thisOrBaseType) { @@ -61,10 +44,11 @@ public static bool IsInstanceOf(this Type type, Type thisOrBaseType) type = type.BaseType; } + return false; } - public static bool IsGenericType(this Type type) + public static bool HasGenericType(this Type type) { while (type != null) { @@ -76,7 +60,7 @@ public static bool IsGenericType(this Type type) return false; } - public static Type GetGenericType(this Type type) + public static Type FirstGenericType(this Type type) { while (type != null) { @@ -88,6 +72,22 @@ public static Type GetGenericType(this Type type) return null; } + public static Type GetTypeWithGenericTypeDefinitionOfAny(this Type type, params Type[] genericTypeDefinitions) + { + foreach (var genericTypeDefinition in genericTypeDefinitions) + { + var genericType = type.GetTypeWithGenericTypeDefinitionOf(genericTypeDefinition); + if (genericType == null && type == genericTypeDefinition) + { + genericType = type; + } + + if (genericType != null) + return genericType; + } + return null; + } + public static bool IsOrHasGenericInterfaceTypeOf(this Type type, Type genericTypeDefinition) { return (type.GetTypeWithGenericTypeDefinitionOf(genericTypeDefinition) != null) @@ -104,7 +104,7 @@ public static Type GetTypeWithGenericTypeDefinitionOf(this Type type, Type gener } } - var genericType = type.GetGenericType(); + var genericType = type.FirstGenericType(); if (genericType != null && genericType.GetGenericTypeDefinition() == genericTypeDefinition) { return genericType; @@ -146,45 +146,112 @@ public static bool AllHaveInterfacesOfType( return true; } + public static bool IsNullableType(this Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + public static TypeCode GetUnderlyingTypeCode(this Type type) + { + return GetTypeCode(Nullable.GetUnderlyingType(type) ?? type); + } + public static bool IsNumericType(this Type type) { - if (!type.IsValueType) return false; - return type.IsIntegerType() || type.IsRealNumberType(); + if (type == null) return false; + + if (type.IsEnum) //TypeCode can be TypeCode.Int32 + { + return JsConfig.TreatEnumAsInteger || type.IsEnumFlags(); + } + + switch (GetTypeCode(type)) + { + case TypeCode.Byte: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.SByte: + case TypeCode.Single: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return true; + + case TypeCode.Object: + if (type.IsNullableType()) + { + return IsNumericType(Nullable.GetUnderlyingType(type)); + } + if (type.IsEnum) + { + return JsConfig.TreatEnumAsInteger || type.IsEnumFlags(); + } + return false; + } + return false; } public static bool IsIntegerType(this Type type) { - if (!type.IsValueType) return false; - var underlyingType = Nullable.GetUnderlyingType(type) ?? type; - return underlyingType == typeof(byte) - || underlyingType == typeof(sbyte) - || underlyingType == typeof(short) - || underlyingType == typeof(ushort) - || underlyingType == typeof(int) - || underlyingType == typeof(uint) - || underlyingType == typeof(long) - || underlyingType == typeof(ulong); + if (type == null) return false; + + switch (GetTypeCode(type)) + { + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.SByte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return true; + + case TypeCode.Object: + if (type.IsNullableType()) + { + return IsNumericType(Nullable.GetUnderlyingType(type)); + } + return false; + } + return false; } public static bool IsRealNumberType(this Type type) { - if (!type.IsValueType) return false; - var underlyingType = Nullable.GetUnderlyingType(type) ?? type; - return underlyingType == typeof(float) - || underlyingType == typeof(double) - || underlyingType == typeof(decimal); + if (type == null) return false; + + switch (GetTypeCode(type)) + { + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Single: + return true; + + case TypeCode.Object: + if (type.IsNullableType()) + { + return IsNumericType(Nullable.GetUnderlyingType(type)); + } + return false; + } + return false; } public static Type GetTypeWithGenericInterfaceOf(this Type type, Type genericInterfaceType) { foreach (var t in type.GetInterfaces()) { - if (t.IsGenericType && t.GetGenericTypeDefinition() == genericInterfaceType) return t; + if (t.IsGenericType && t.GetGenericTypeDefinition() == genericInterfaceType) + return t; } if (!type.IsGenericType) return null; - var genericType = type.GetGenericType(); + var genericType = type.FirstGenericType(); return genericType.GetGenericTypeDefinition() == genericInterfaceType ? genericType : null; @@ -193,6 +260,7 @@ public static Type GetTypeWithGenericInterfaceOf(this Type type, Type genericInt public static bool HasAnyTypeDefinitionsOf(this Type genericType, params Type[] theseGenericTypes) { if (!genericType.IsGenericType) return false; + var genericTypeDefinition = genericType.GetGenericTypeDefinition(); foreach (var thisGenericType in theseGenericTypes) @@ -215,6 +283,7 @@ public static Type[] GetGenericArgumentsIfBothHaveSameGenericDefinitionTypeAndAr var typeAGenericArgs = typeAInterface.GetGenericArguments(); var typeBGenericArgs = typeBInterface.GetGenericArguments(); + if (typeAGenericArgs.Length != typeBGenericArgs.Length) return null; for (var i = 0; i < typeBGenericArgs.Length; i++) @@ -239,6 +308,7 @@ public static TypePair GetGenericArgumentsIfBothHaveConvertibleGenericDefinition var typeAGenericArgs = typeAInterface.GetGenericArguments(); var typeBGenericArgs = typeBInterface.GetGenericArguments(); + if (typeAGenericArgs.Length != typeBGenericArgs.Length) return null; for (var i = 0; i < typeBGenericArgs.Length; i++) @@ -264,8 +334,8 @@ public static bool AreAllStringOrValueTypes(params Type[] types) static Dictionary ConstructorMethods = new Dictionary(); public static EmptyCtorDelegate GetConstructorMethod(Type type) { - EmptyCtorDelegate emptyCtorFn; - if (ConstructorMethods.TryGetValue(type, out emptyCtorFn)) return emptyCtorFn; + if (ConstructorMethods.TryGetValue(type, out var emptyCtorFn)) + return emptyCtorFn; emptyCtorFn = GetConstructorMethodToCache(type); @@ -285,10 +355,10 @@ public static EmptyCtorDelegate GetConstructorMethod(Type type) static Dictionary TypeNamesMap = new Dictionary(); public static EmptyCtorDelegate GetConstructorMethod(string typeName) { - EmptyCtorDelegate emptyCtorFn; - if (TypeNamesMap.TryGetValue(typeName, out emptyCtorFn)) return emptyCtorFn; + if (TypeNamesMap.TryGetValue(typeName, out var emptyCtorFn)) + return emptyCtorFn; - var type = JsConfig.TypeFinder.Invoke(typeName); + var type = JsConfig.TypeFinder(typeName); if (type == null) return null; emptyCtorFn = GetConstructorMethodToCache(type); @@ -296,8 +366,9 @@ public static EmptyCtorDelegate GetConstructorMethod(string typeName) do { snapshot = TypeNamesMap; - newCache = new Dictionary(TypeNamesMap); - newCache[typeName] = emptyCtorFn; + newCache = new Dictionary(TypeNamesMap) { + [typeName] = emptyCtorFn + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref TypeNamesMap, newCache, snapshot), snapshot)); @@ -307,38 +378,52 @@ public static EmptyCtorDelegate GetConstructorMethod(string typeName) public static EmptyCtorDelegate GetConstructorMethodToCache(Type type) { - var emptyCtor = type.GetConstructor(Type.EmptyTypes); - if (emptyCtor != null) + if (type == typeof(string)) + return () => string.Empty; + + if (type.IsInterface) { + if (type.HasGenericType()) + { + var genericType = type.GetTypeWithGenericTypeDefinitionOfAny( + typeof(IDictionary<,>)); + + if (genericType != null) + { + var keyType = genericType.GetGenericArguments()[0]; + var valueType = genericType.GetGenericArguments()[1]; + return GetConstructorMethodToCache(typeof(Dictionary<,>).MakeGenericType(keyType, valueType)); + } -#if MONOTOUCH || c|| XBOX - return () => Activator.CreateInstance(type); - -#elif WINDOWS_PHONE - return Expression.Lambda(Expression.New(type)).Compile(); -#else -#if SILVERLIGHT - var dm = new System.Reflection.Emit.DynamicMethod("MyCtor", type, Type.EmptyTypes); -#else - var dm = new System.Reflection.Emit.DynamicMethod("MyCtor", type, Type.EmptyTypes, typeof(ReflectionExtensions).Module, true); -#endif - var ilgen = dm.GetILGenerator(); - ilgen.Emit(System.Reflection.Emit.OpCodes.Nop); - ilgen.Emit(System.Reflection.Emit.OpCodes.Newobj, emptyCtor); - ilgen.Emit(System.Reflection.Emit.OpCodes.Ret); - - return (EmptyCtorDelegate)dm.CreateDelegate(typeof(EmptyCtorDelegate)); -#endif + genericType = type.GetTypeWithGenericTypeDefinitionOfAny( + typeof(IEnumerable<>), + typeof(ICollection<>), + typeof(IList<>)); + + if (genericType != null) + { + var elementType = genericType.GetGenericArguments()[0]; + return GetConstructorMethodToCache(typeof(List<>).MakeGenericType(elementType)); + } + } + } + else if (type.IsArray) + { + return () => Array.CreateInstance(type.GetElementType(), 0); } + else if (type.IsGenericTypeDefinition) + { + var genericArgs = type.GetGenericArguments(); + var typeArgs = new Type[genericArgs.Length]; + for (var i = 0; i < genericArgs.Length; i++) + typeArgs[i] = typeof(object); + + var realizedType = type.MakeGenericType(typeArgs); -#if (SILVERLIGHT && !WINDOWS_PHONE) || XBOX - return () => Activator.CreateInstance(type); -#elif WINDOWS_PHONE - return Expression.Lambda(Expression.New(type)).Compile(); -#else - //Anonymous types don't have empty constructors - return () => FormatterServices.GetUninitializedObject(type); -#endif + return realizedType.CreateInstance; + } + + return ReflectionOptimizer.Instance.CreateConstructor(type); } private static class TypeMeta @@ -350,23 +435,114 @@ static TypeMeta() } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object CreateInstance() { return TypeMeta.EmptyCtorFn(); } + /// + /// Creates a new instance of type. + /// First looks at JsConfig.ModelFactory before falling back to CreateInstance + /// + public static T New(this Type type) + { + var factoryFn = JsConfig.ModelFactory(type) + ?? GetConstructorMethod(type); + return (T)factoryFn(); + } + + /// + /// Creates a new instance of type. + /// First looks at JsConfig.ModelFactory before falling back to CreateInstance + /// + public static object New(this Type type) + { + var factoryFn = JsConfig.ModelFactory(type) + ?? GetConstructorMethod(type); + return factoryFn(); + } + + /// + /// Creates a new instance from the default constructor of type + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object CreateInstance(this Type type) { + if (type == null) + return null; + var ctorFn = GetConstructorMethod(type); return ctorFn(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T CreateInstance(this Type type) + { + if (type == null) + return default(T); + + var ctorFn = GetConstructorMethod(type); + return (T)ctorFn(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object CreateInstance(string typeName) { + if (typeName == null) + return null; + var ctorFn = GetConstructorMethod(typeName); return ctorFn(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Module GetModule(this Type type) + { + if (type == null) + return null; + + return type.Module; + } + + public static PropertyInfo[] GetAllProperties(this Type type) + { + if (type.IsInterface) + { + var propertyInfos = new List(); + + var considered = new List(); + var queue = new Queue(); + considered.Add(type); + queue.Enqueue(type); + + while (queue.Count > 0) + { + var subType = queue.Dequeue(); + foreach (var subInterface in subType.GetInterfaces()) + { + if (considered.Contains(subInterface)) continue; + + considered.Add(subInterface); + queue.Enqueue(subInterface); + } + + var typeProperties = subType.GetTypesProperties(); + + var newPropertyInfos = typeProperties + .Where(x => !propertyInfos.Contains(x)); + + propertyInfos.InsertRange(0, newPropertyInfos); + } + + return propertyInfos.ToArray(); + } + + return type.GetTypesProperties() + .Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties + .ToArray(); + } + public static PropertyInfo[] GetPublicProperties(this Type type) { if (type.IsInterface) @@ -377,6 +553,7 @@ public static PropertyInfo[] GetPublicProperties(this Type type) var queue = new Queue(); considered.Add(type); queue.Enqueue(type); + while (queue.Count > 0) { var subType = queue.Dequeue(); @@ -388,10 +565,7 @@ public static PropertyInfo[] GetPublicProperties(this Type type) queue.Enqueue(subInterface); } - var typeProperties = subType.GetProperties( - BindingFlags.FlattenHierarchy - | BindingFlags.Public - | BindingFlags.Instance); + var typeProperties = subType.GetTypesPublicProperties(); var newPropertyInfos = typeProperties .Where(x => !propertyInfos.Contains(x)); @@ -402,127 +576,146 @@ public static PropertyInfo[] GetPublicProperties(this Type type) return propertyInfos.ToArray(); } - return type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance) + return type.GetTypesPublicProperties() .Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties .ToArray(); } - const string DataContract = "DataContractAttribute"; - const string DataMember = "DataMemberAttribute"; - const string IgnoreDataMember = "IgnoreDataMemberAttribute"; + public const string DataMember = "DataMemberAttribute"; + + internal static string[] IgnoreAttributesNamed = new[] { + "IgnoreDataMemberAttribute", + "JsonIgnoreAttribute" + }; + + internal static void Reset() + { + IgnoreAttributesNamed = new[] { + "IgnoreDataMemberAttribute", + "JsonIgnoreAttribute" + }; + + try + { + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + } + catch (Exception e) + { + Tracer.Instance.WriteError("ReflectionExtensions JsConfig", e); + } + } public static PropertyInfo[] GetSerializableProperties(this Type type) { - var publicProperties = GetPublicProperties(type); - var publicReadableProperties = publicProperties.Where(x => x.GetGetMethod(false) != null); + var properties = type.IsDto() + ? type.GetAllProperties() + : type.GetPublicProperties(); + return properties.OnlySerializableProperties(type); + } + + public static PropertyInfo[] OnlySerializableProperties(this PropertyInfo[] properties, Type type = null) + { + var isDto = type.IsDto(); + var readableProperties = properties.Where(x => x.GetGetMethod(nonPublic: isDto) != null); - if (type.IsDto()) + if (isDto) { - return !Env.IsMono - ? publicReadableProperties.Where(attr => - attr.IsDefined(typeof(DataMemberAttribute), false)).ToArray() - : publicReadableProperties.Where(attr => - attr.GetCustomAttributes(false).Any(x => x.GetType().Name == DataMember)).ToArray(); + return readableProperties.Where(attr => + attr.HasAttribute()).ToArray(); } // else return those properties that are not decorated with IgnoreDataMember - return publicReadableProperties.Where(prop => !prop.GetCustomAttributes(false).Any(attr => attr.GetType().Name == IgnoreDataMember)).ToArray(); + return readableProperties + .Where(prop => prop.AllAttributes() + .All(attr => + { + var name = attr.GetType().Name; + return !IgnoreAttributesNamed.Contains(name); + })) + .Where(prop => !(JsConfig.ExcludeTypes.Contains(prop.PropertyType) || + JsConfig.ExcludeTypeNames.Contains(prop.PropertyType.FullName))) + .ToArray(); } - public static bool IsDto(this Type type) + public static Func GetOnDeserializing() { - return !Env.IsMono - ? type.IsDefined(typeof(DataContractAttribute), false) - : type.GetCustomAttributes(true).Any(x => x.GetType().Name == DataContract); + var method = typeof(T).GetMethodInfo("OnDeserializing"); + if (method == null || method.ReturnType != typeof(object)) + return null; + var obj = (Func)method.CreateDelegate(typeof(Func)); + return (instance, memberName, value) => obj((T)instance, memberName, value); } - public static bool HasAttr(this Type type) where T : Attribute + public static FieldInfo[] GetSerializableFields(this Type type) { - return type.GetCustomAttributes(true).Any(x => x.GetType() == typeof(T)); - } + if (type.IsDto()) + { + return type.GetAllFields().Where(f => + f.HasAttribute()).ToArray(); + } -#if !SILVERLIGHT && !MONOTOUCH - static readonly Dictionary typeAccessorMap - = new Dictionary(); -#endif + var config = JsConfig.GetConfig(); + + if (!config.IncludePublicFields) + return TypeConstants.EmptyFieldInfoArray; + + var publicFields = type.GetPublicFields(); + + // else return those properties that are not decorated with IgnoreDataMember + return publicFields + .Where(prop => prop.AllAttributes() + .All(attr => !IgnoreAttributesNamed.Contains(attr.GetType().Name))) + .Where(prop => !config.ExcludeTypes.Contains(prop.FieldType)) + .ToArray(); + } public static DataContractAttribute GetDataContract(this Type type) { - var dataContract = type.GetCustomAttributes(typeof(DataContractAttribute), true) - .FirstOrDefault() as DataContractAttribute; + var dataContract = type.FirstAttribute(); -#if !SILVERLIGHT && !MONOTOUCH && !XBOX if (dataContract == null && Env.IsMono) - return type.GetWeakDataContract(); -#endif + return PclExport.Instance.GetWeakDataContract(type); + return dataContract; } public static DataMemberAttribute GetDataMember(this PropertyInfo pi) { - var dataMember = pi.GetCustomAttributes(typeof(DataMemberAttribute), false) + var dataMember = pi.AllAttributes(typeof(DataMemberAttribute)) .FirstOrDefault() as DataMemberAttribute; -#if !SILVERLIGHT && !MONOTOUCH && !XBOX if (dataMember == null && Env.IsMono) - return pi.GetWeakDataMember(); -#endif + return PclExport.Instance.GetWeakDataMember(pi); + return dataMember; } -#if !SILVERLIGHT && !MONOTOUCH && !XBOX - public static DataContractAttribute GetWeakDataContract(this Type type) + public static DataMemberAttribute GetDataMember(this FieldInfo pi) { - var attr = type.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().Name == DataContract); - if (attr != null) - { - var attrType = attr.GetType(); + var dataMember = pi.AllAttributes(typeof(DataMemberAttribute)) + .FirstOrDefault() as DataMemberAttribute; - FastMember.TypeAccessor accessor; - lock (typeAccessorMap) - { - if (!typeAccessorMap.TryGetValue(attrType, out accessor)) - typeAccessorMap[attrType] = accessor = FastMember.TypeAccessor.Create(attr.GetType()); - } + if (dataMember == null && Env.IsMono) + return PclExport.Instance.GetWeakDataMember(pi); - return new DataContractAttribute { - Name = (string)accessor[attr, "Name"], - Namespace = (string)accessor[attr, "Namespace"], - }; - } - return null; + return dataMember; } - public static DataMemberAttribute GetWeakDataMember(this PropertyInfo pi) + public static string GetDataMemberName(this PropertyInfo pi) { - var attr = pi.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().Name == DataMember); - if (attr != null) - { - var attrType = attr.GetType(); - - FastMember.TypeAccessor accessor; - lock (typeAccessorMap) - { - if (!typeAccessorMap.TryGetValue(attrType, out accessor)) - typeAccessorMap[attrType] = accessor = FastMember.TypeAccessor.Create(attr.GetType()); - } - - var newAttr = new DataMemberAttribute { - Name = (string) accessor[attr, "Name"], - EmitDefaultValue = (bool)accessor[attr, "EmitDefaultValue"], - IsRequired = (bool)accessor[attr, "IsRequired"], - }; - - var order = (int)accessor[attr, "Order"]; - if (order >= 0) - newAttr.Order = order; //Throws Exception if set to -1 - - return newAttr; - } - return null; + var attr = pi.GetDataMember(); + return attr?.Name; } -#endif + public static string GetDataMemberName(this FieldInfo fi) + { + var attr = fi.GetDataMember(); + return attr?.Name; + } } - } \ No newline at end of file diff --git a/src/ServiceStack.Text/ReflectionOptimizer.Emit.cs b/src/ServiceStack.Text/ReflectionOptimizer.Emit.cs new file mode 100644 index 000000000..cac57f78f --- /dev/null +++ b/src/ServiceStack.Text/ReflectionOptimizer.Emit.cs @@ -0,0 +1,450 @@ +#if NETFX || (NETCORE && !NETSTANDARD2_0) + +using System; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.Serialization; + +namespace ServiceStack.Text +{ + public sealed class EmitReflectionOptimizer : ReflectionOptimizer + { + private static EmitReflectionOptimizer provider; + public static EmitReflectionOptimizer Provider => provider ??= new EmitReflectionOptimizer(); + private EmitReflectionOptimizer() { } + + public override Type UseType(Type type) + { + if (type.IsInterface || type.IsAbstract) + { + return DynamicProxy.GetInstanceFor(type).GetType(); + } + + return type; + } + + internal static DynamicMethod CreateDynamicGetMethod(MemberInfo memberInfo) + { + var memberType = memberInfo is FieldInfo ? "Field" : "Property"; + var name = $"_Get{memberType}[T]_{memberInfo.Name}_"; + var returnType = typeof(object); + + return !memberInfo.DeclaringType.IsInterface + ? new DynamicMethod(name, returnType, new[] {typeof(T)}, memberInfo.DeclaringType, true) + : new DynamicMethod(name, returnType, new[] {typeof(T)}, memberInfo.Module, true); + } + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var getter = CreateDynamicGetMethod(propertyInfo); + + var gen = getter.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + + if (propertyInfo.DeclaringType.IsValueType) + { + gen.Emit(OpCodes.Unbox, propertyInfo.DeclaringType); + } + else + { + gen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); + } + + var mi = propertyInfo.GetGetMethod(true); + if (mi == null) + return null; + gen.Emit(mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi); + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Box, propertyInfo.PropertyType); + } + + gen.Emit(OpCodes.Ret); + + return (GetMemberDelegate) getter.CreateDelegate(typeof(GetMemberDelegate)); + } + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var getter = CreateDynamicGetMethod(propertyInfo); + + var gen = getter.GetILGenerator(); + var mi = propertyInfo.GetGetMethod(true); + if (mi == null) + return null; + + if (typeof(T).IsValueType) + { + gen.Emit(OpCodes.Ldarga_S, 0); + + if (typeof(T) != propertyInfo.DeclaringType) + { + gen.Emit(OpCodes.Unbox, propertyInfo.DeclaringType); + } + } + else + { + gen.Emit(OpCodes.Ldarg_0); + + if (typeof(T) != propertyInfo.DeclaringType) + { + gen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); + } + } + + gen.Emit(mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi); + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Box, propertyInfo.PropertyType); + } + + gen.Emit(OpCodes.Isinst, typeof(object)); + + gen.Emit(OpCodes.Ret); + + return (GetMemberDelegate) getter.CreateDelegate(typeof(GetMemberDelegate)); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + var mi = propertyInfo.GetSetMethod(true); + if (mi == null) + return null; + + var setter = CreateDynamicSetMethod(propertyInfo); + + var gen = setter.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + + if (propertyInfo.DeclaringType.IsValueType) + { + gen.Emit(OpCodes.Unbox, propertyInfo.DeclaringType); + } + else + { + gen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); + } + + gen.Emit(OpCodes.Ldarg_1); + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType); + } + else + { + gen.Emit(OpCodes.Castclass, propertyInfo.PropertyType); + } + + gen.EmitCall(mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi, (Type[]) null); + + gen.Emit(OpCodes.Ret); + + return (SetMemberDelegate) setter.CreateDelegate(typeof(SetMemberDelegate)); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) => + ExpressionReflectionOptimizer.Provider.CreateSetter(propertyInfo); + + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) + { + var getter = CreateDynamicGetMethod(fieldInfo); + + var gen = getter.GetILGenerator(); + + gen.Emit(OpCodes.Ldarg_0); + + if (fieldInfo.DeclaringType.IsValueType) + { + gen.Emit(OpCodes.Unbox, fieldInfo.DeclaringType); + } + else + { + gen.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); + } + + gen.Emit(OpCodes.Ldfld, fieldInfo); + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Box, fieldInfo.FieldType); + } + + gen.Emit(OpCodes.Ret); + + return (GetMemberDelegate) getter.CreateDelegate(typeof(GetMemberDelegate)); + } + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) + { + var getter = CreateDynamicGetMethod(fieldInfo); + + var gen = getter.GetILGenerator(); + + gen.Emit(OpCodes.Ldarg_0); + + gen.Emit(OpCodes.Ldfld, fieldInfo); + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Box, fieldInfo.FieldType); + } + + gen.Emit(OpCodes.Ret); + + return (GetMemberDelegate) getter.CreateDelegate(typeof(GetMemberDelegate)); + } + + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) + { + var setter = CreateDynamicSetMethod(fieldInfo); + + var gen = setter.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + + if (fieldInfo.DeclaringType.IsValueType) + { + gen.Emit(OpCodes.Unbox, fieldInfo.DeclaringType); + } + else + { + gen.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); + } + + gen.Emit(OpCodes.Ldarg_1); + + gen.Emit(fieldInfo.FieldType.IsClass + ? OpCodes.Castclass + : OpCodes.Unbox_Any, + fieldInfo.FieldType); + + gen.Emit(OpCodes.Stfld, fieldInfo); + gen.Emit(OpCodes.Ret); + + return (SetMemberDelegate) setter.CreateDelegate(typeof(SetMemberDelegate)); + } + + static readonly Type[] DynamicGetMethodArgs = {typeof(object)}; + + internal static DynamicMethod CreateDynamicGetMethod(MemberInfo memberInfo) + { + var memberType = memberInfo is FieldInfo ? "Field" : "Property"; + var name = $"_Get{memberType}_{memberInfo.Name}_"; + var returnType = typeof(object); + + return !memberInfo.DeclaringType.IsInterface + ? new DynamicMethod(name, returnType, DynamicGetMethodArgs, memberInfo.DeclaringType, true) + : new DynamicMethod(name, returnType, DynamicGetMethodArgs, memberInfo.Module, true); + } + + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => + ExpressionReflectionOptimizer.Provider.CreateSetter(fieldInfo); + + public override SetMemberRefDelegate CreateSetterRef(FieldInfo fieldInfo) => + ExpressionReflectionOptimizer.Provider.CreateSetterRef(fieldInfo); + + public override bool IsDynamic(Assembly assembly) + { + try + { + var isDynamic = assembly is AssemblyBuilder + || string.IsNullOrEmpty(assembly.Location); + return isDynamic; + } + catch (NotSupportedException) + { + //Ignore assembly.Location not supported in a dynamic assembly. + return true; + } + } + + public override EmptyCtorDelegate CreateConstructor(Type type) + { + var emptyCtor = type.GetConstructor(Type.EmptyTypes); + if (emptyCtor != null) + { + var dm = new DynamicMethod("MyCtor", type, Type.EmptyTypes, typeof(ReflectionExtensions).Module, true); + var ilgen = dm.GetILGenerator(); + ilgen.Emit(OpCodes.Nop); + ilgen.Emit(OpCodes.Newobj, emptyCtor); + ilgen.Emit(OpCodes.Ret); + + return (EmptyCtorDelegate) dm.CreateDelegate(typeof(EmptyCtorDelegate)); + } + + //Anonymous types don't have empty constructors + return () => FormatterServices.GetUninitializedObject(type); + } + + static readonly Type[] DynamicSetMethodArgs = {typeof(object), typeof(object)}; + + internal static DynamicMethod CreateDynamicSetMethod(MemberInfo memberInfo) + { + var memberType = memberInfo is FieldInfo ? "Field" : "Property"; + var name = $"_Set{memberType}_{memberInfo.Name}_"; + var returnType = typeof(void); + + return !memberInfo.DeclaringType.IsInterface + ? new DynamicMethod(name, returnType, DynamicSetMethodArgs, memberInfo.DeclaringType, true) + : new DynamicMethod(name, returnType, DynamicSetMethodArgs, memberInfo.Module, true); + } + } + + + public static class DynamicProxy + { + public static T GetInstanceFor() + { + return (T)GetInstanceFor(typeof(T)); + } + + static readonly ModuleBuilder ModuleBuilder; + static readonly AssemblyBuilder DynamicAssembly; + static readonly Type[] EmptyTypes = new Type[0]; + + public static object GetInstanceFor(Type targetType) + { + lock (DynamicAssembly) + { + var constructedType = DynamicAssembly.GetType(ProxyName(targetType)) ?? GetConstructedType(targetType); + var instance = Activator.CreateInstance(constructedType); + return instance; + } + } + + static string ProxyName(Type targetType) + { + return targetType.Name + "Proxy"; + } + + static DynamicProxy() + { + var assemblyName = new AssemblyName("DynImpl"); +#if NETCORE + DynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); +#else + DynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); +#endif + ModuleBuilder = DynamicAssembly.DefineDynamicModule("DynImplModule"); + } + + static Type GetConstructedType(Type targetType) + { + var typeBuilder = ModuleBuilder.DefineType(targetType.Name + "Proxy", TypeAttributes.Public); + + var ctorBuilder = typeBuilder.DefineConstructor( + MethodAttributes.Public, + CallingConventions.Standard, + new Type[] { }); + var ilGenerator = ctorBuilder.GetILGenerator(); + ilGenerator.Emit(OpCodes.Ret); + + IncludeType(targetType, typeBuilder); + + foreach (var face in targetType.GetInterfaces()) + IncludeType(face, typeBuilder); + +#if NETCORE + return typeBuilder.CreateTypeInfo().AsType(); +#else + return typeBuilder.CreateType(); +#endif + } + + static void IncludeType(Type typeOfT, TypeBuilder typeBuilder) + { + var methodInfos = typeOfT.GetMethods(); + foreach (var methodInfo in methodInfos) + { + if (methodInfo.Name.StartsWith("set_", StringComparison.Ordinal)) continue; // we always add a set for a get. + + if (methodInfo.Name.StartsWith("get_", StringComparison.Ordinal)) + { + BindProperty(typeBuilder, methodInfo); + } + else + { + BindMethod(typeBuilder, methodInfo); + } + } + + typeBuilder.AddInterfaceImplementation(typeOfT); + } + + static void BindMethod(TypeBuilder typeBuilder, MethodInfo methodInfo) + { + var methodBuilder = typeBuilder.DefineMethod( + methodInfo.Name, + MethodAttributes.Public | MethodAttributes.Virtual, + methodInfo.ReturnType, + methodInfo.GetParameters().Select(p => p.GetType()).ToArray() + ); + var methodILGen = methodBuilder.GetILGenerator(); + if (methodInfo.ReturnType == typeof(void)) + { + methodILGen.Emit(OpCodes.Ret); + } + else + { + if (methodInfo.ReturnType.IsValueType || methodInfo.ReturnType.IsEnum) + { + MethodInfo getMethod = typeof(Activator).GetMethod("CreateInstance", new[] { typeof(Type) }); + LocalBuilder lb = methodILGen.DeclareLocal(methodInfo.ReturnType); + methodILGen.Emit(OpCodes.Ldtoken, lb.LocalType); + methodILGen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); + methodILGen.Emit(OpCodes.Callvirt, getMethod); + methodILGen.Emit(OpCodes.Unbox_Any, lb.LocalType); + } + else + { + methodILGen.Emit(OpCodes.Ldnull); + } + methodILGen.Emit(OpCodes.Ret); + } + typeBuilder.DefineMethodOverride(methodBuilder, methodInfo); + } + + public static void BindProperty(TypeBuilder typeBuilder, MethodInfo methodInfo) + { + // Backing Field + string propertyName = methodInfo.Name.Replace("get_", ""); + Type propertyType = methodInfo.ReturnType; + FieldBuilder backingField = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); + + //Getter + MethodBuilder backingGet = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | + MethodAttributes.SpecialName | MethodAttributes.Virtual | + MethodAttributes.HideBySig, propertyType, EmptyTypes); + ILGenerator getIl = backingGet.GetILGenerator(); + + getIl.Emit(OpCodes.Ldarg_0); + getIl.Emit(OpCodes.Ldfld, backingField); + getIl.Emit(OpCodes.Ret); + + + //Setter + MethodBuilder backingSet = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | + MethodAttributes.SpecialName | MethodAttributes.Virtual | + MethodAttributes.HideBySig, null, new[] { propertyType }); + + ILGenerator setIl = backingSet.GetILGenerator(); + + setIl.Emit(OpCodes.Ldarg_0); + setIl.Emit(OpCodes.Ldarg_1); + setIl.Emit(OpCodes.Stfld, backingField); + setIl.Emit(OpCodes.Ret); + + // Property + PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, null); + propertyBuilder.SetGetMethod(backingGet); + propertyBuilder.SetSetMethod(backingSet); + } + } + +} + +#endif \ No newline at end of file diff --git a/src/ServiceStack.Text/ReflectionOptimizer.cs b/src/ServiceStack.Text/ReflectionOptimizer.cs new file mode 100644 index 000000000..0042abcbc --- /dev/null +++ b/src/ServiceStack.Text/ReflectionOptimizer.cs @@ -0,0 +1,355 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.Serialization; + +namespace ServiceStack.Text +{ + public abstract class ReflectionOptimizer + { + public static ReflectionOptimizer Instance = +#if NETFX || (NETCORE && !NETSTANDARD2_0) + EmitReflectionOptimizer.Provider +#else + ExpressionReflectionOptimizer.Provider +#endif + ; + + public abstract Type UseType(Type type); + + public abstract GetMemberDelegate CreateGetter(PropertyInfo propertyInfo); + public abstract GetMemberDelegate CreateGetter(PropertyInfo propertyInfo); + public abstract SetMemberDelegate CreateSetter(PropertyInfo propertyInfo); + public abstract SetMemberDelegate CreateSetter(PropertyInfo propertyInfo); + + public abstract GetMemberDelegate CreateGetter(FieldInfo fieldInfo); + public abstract GetMemberDelegate CreateGetter(FieldInfo fieldInfo); + public abstract SetMemberDelegate CreateSetter(FieldInfo fieldInfo); + public abstract SetMemberDelegate CreateSetter(FieldInfo fieldInfo); + + public abstract SetMemberRefDelegate CreateSetterRef(FieldInfo fieldInfo); + + public abstract bool IsDynamic(Assembly assembly); + public abstract EmptyCtorDelegate CreateConstructor(Type type); + } + + public sealed class RuntimeReflectionOptimizer : ReflectionOptimizer + { + private static RuntimeReflectionOptimizer provider; + public static RuntimeReflectionOptimizer Provider => provider ??= new RuntimeReflectionOptimizer(); + private RuntimeReflectionOptimizer(){} + + public override Type UseType(Type type) => type; + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var getMethodInfo = propertyInfo.GetGetMethod(nonPublic:true); + if (getMethodInfo == null) return null; + + return o => propertyInfo.GetGetMethod(nonPublic:true).Invoke(o, TypeConstants.EmptyObjectArray); + } + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var getMethodInfo = propertyInfo.GetGetMethod(nonPublic:true); + if (getMethodInfo == null) return null; + + return o => propertyInfo.GetGetMethod(nonPublic:true).Invoke(o, TypeConstants.EmptyObjectArray); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + var propertySetMethod = propertyInfo.GetSetMethod(nonPublic:true); + if (propertySetMethod == null) return null; + + return (o, convertedValue) => + propertySetMethod.Invoke(o, new[] { convertedValue }); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + var propertySetMethod = propertyInfo.GetSetMethod(nonPublic:true); + if (propertySetMethod == null) return null; + + return (o, convertedValue) => + propertySetMethod.Invoke(o, new[] { convertedValue }); + } + + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) => fieldInfo.GetValue; + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) => x => fieldInfo.GetValue(x); + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => fieldInfo.SetValue; + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => (o,x) => fieldInfo.SetValue(o,x); + + public override SetMemberRefDelegate CreateSetterRef(FieldInfo fieldInfo) => + ExpressionReflectionOptimizer.Provider.CreateSetterRef(fieldInfo); + + public override bool IsDynamic(Assembly assembly) + { + try + { + var isDyanmic = string.IsNullOrEmpty(assembly.Location); + return isDyanmic; + } + catch (NotSupportedException) + { + //Ignore assembly.Location not supported in a dynamic assembly. + return true; + } + } + + public override EmptyCtorDelegate CreateConstructor(Type type) + { + var emptyCtor = type.GetConstructor(Type.EmptyTypes); + if (emptyCtor != null) + return () => Activator.CreateInstance(type); + + //Anonymous types don't have empty constructors + return () => FormatterServices.GetUninitializedObject(type); + } + } + + public sealed class ExpressionReflectionOptimizer : ReflectionOptimizer + { + private static ExpressionReflectionOptimizer provider; + public static ExpressionReflectionOptimizer Provider => provider ?? (provider = new ExpressionReflectionOptimizer()); + private ExpressionReflectionOptimizer(){} + + public override Type UseType(Type type) => type; + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var lambda = GetExpressionLambda(propertyInfo); + var propertyGetFn = lambda.Compile(); + return propertyGetFn; + } + + public static Expression GetExpressionLambda(PropertyInfo propertyInfo) + { + var getMethodInfo = propertyInfo.GetGetMethod(nonPublic:true); + if (getMethodInfo == null) return null; + + var oInstanceParam = Expression.Parameter(typeof(object), "oInstanceParam"); + var instanceParam = Expression.Convert(oInstanceParam, propertyInfo.ReflectedType); //propertyInfo.DeclaringType doesn't work on Proxy types + + var exprCallPropertyGetFn = Expression.Call(instanceParam, getMethodInfo); + var oExprCallPropertyGetFn = Expression.Convert(exprCallPropertyGetFn, typeof(object)); + + return Expression.Lambda + ( + oExprCallPropertyGetFn, + oInstanceParam + ); + } + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var expr = GetExpressionLambda(propertyInfo); + return expr.Compile(); + } + + public static Expression> GetExpressionLambda(PropertyInfo propertyInfo) + { + var instance = Expression.Parameter(typeof(T), "i"); + var property = typeof(T) != propertyInfo.DeclaringType + ? Expression.Property(Expression.TypeAs(instance, propertyInfo.DeclaringType), propertyInfo) + : Expression.Property(instance, propertyInfo); + var convertProperty = Expression.TypeAs(property, typeof(object)); + return Expression.Lambda>(convertProperty, instance); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + var propertySetMethod = propertyInfo.GetSetMethod(nonPublic:true); + if (propertySetMethod == null) return null; + + try + { + var declaringType = propertyInfo.ReflectedType; + + var instance = Expression.Parameter(typeof(object), "i"); + var argument = Expression.Parameter(typeof(object), "a"); + + var instanceParam = declaringType.IsValueType && !declaringType.IsNullableType() + ? Expression.Unbox(instance, declaringType) + : Expression.Convert(instance, declaringType); + + var valueParam = Expression.Convert(argument, propertyInfo.PropertyType); + + var setterCall = Expression.Call(instanceParam, propertySetMethod, valueParam); + + return Expression.Lambda(setterCall, instance, argument).Compile(); + } + catch //fallback for Android + { + return (o, convertedValue) => + propertySetMethod.Invoke(o, new[] { convertedValue }); + } + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + try + { + var lambda = SetExpressionLambda(propertyInfo); + return lambda?.Compile(); + } + catch //fallback for Android + { + var mi = propertyInfo.GetSetMethod(nonPublic: true); + return (o, convertedValue) => + mi.Invoke(o, new[] { convertedValue }); + } + } + + public static Expression> SetExpressionLambda(PropertyInfo propertyInfo) + { + var mi = propertyInfo.GetSetMethod(nonPublic: true); + if (mi == null) return null; + + var instance = Expression.Parameter(typeof(T), "i"); + var argument = Expression.Parameter(typeof(object), "a"); + + var instanceType = typeof(T) != propertyInfo.DeclaringType + ? (Expression)Expression.TypeAs(instance, propertyInfo.DeclaringType) + : instance; + + var setterCall = Expression.Call( + instanceType, + mi, + Expression.Convert(argument, propertyInfo.PropertyType)); + + return Expression.Lambda> + ( + setterCall, instance, argument + ); + } + + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) + { + var fieldDeclaringType = fieldInfo.DeclaringType; + + var oInstanceParam = Expression.Parameter(typeof(object), "source"); + var instanceParam = GetCastOrConvertExpression(oInstanceParam, fieldDeclaringType); + + var exprCallFieldGetFn = Expression.Field(instanceParam, fieldInfo); + var oExprCallFieldGetFn = Expression.Convert(exprCallFieldGetFn, typeof(object)); + + var fieldGetterFn = Expression.Lambda + ( + oExprCallFieldGetFn, + oInstanceParam + ) + .Compile(); + + return fieldGetterFn; + } + + private static Expression GetCastOrConvertExpression(Expression expression, Type targetType) + { + Expression result; + var expressionType = expression.Type; + + if (targetType.IsAssignableFrom(expressionType)) + { + result = expression; + } + else + { + // Check if we can use the as operator for casting or if we must use the convert method + if (targetType.IsValueType && !targetType.IsNullableType()) + { + result = Expression.Convert(expression, targetType); + } + else + { + result = Expression.TypeAs(expression, targetType); + } + } + + return result; + } + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) + { + var instance = Expression.Parameter(typeof(T), "i"); + var field = typeof(T) != fieldInfo.DeclaringType + ? Expression.Field(Expression.TypeAs(instance, fieldInfo.DeclaringType), fieldInfo) + : Expression.Field(instance, fieldInfo); + var convertField = Expression.TypeAs(field, typeof(object)); + return Expression.Lambda>(convertField, instance).Compile(); + } + + private static readonly MethodInfo setFieldMethod = typeof(ExpressionReflectionOptimizer).GetStaticMethod(nameof(SetField)); + internal static void SetField(ref TValue field, TValue newValue) => field = newValue; + + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) + { + var declaringType = fieldInfo.DeclaringType; + + var sourceParameter = Expression.Parameter(typeof(object), "source"); + var valueParameter = Expression.Parameter(typeof(object), "value"); + + var sourceExpression = declaringType.IsValueType && !declaringType.IsNullableType() + ? Expression.Unbox(sourceParameter, declaringType) + : GetCastOrConvertExpression(sourceParameter, declaringType); + + var fieldExpression = Expression.Field(sourceExpression, fieldInfo); + + var valueExpression = GetCastOrConvertExpression(valueParameter, fieldExpression.Type); + + var genericSetFieldMethodInfo = setFieldMethod.MakeGenericMethod(fieldExpression.Type); + + var setFieldMethodCallExpression = Expression.Call( + null, genericSetFieldMethodInfo, fieldExpression, valueExpression); + + var setterFn = Expression.Lambda( + setFieldMethodCallExpression, sourceParameter, valueParameter).Compile(); + + return setterFn; + } + + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) + { + var instance = Expression.Parameter(typeof(T), "i"); + var argument = Expression.Parameter(typeof(object), "a"); + + var field = typeof(T) != fieldInfo.DeclaringType + ? Expression.Field(Expression.TypeAs(instance, fieldInfo.DeclaringType), fieldInfo) + : Expression.Field(instance, fieldInfo); + + var setterCall = Expression.Assign( + field, + Expression.Convert(argument, fieldInfo.FieldType)); + + return Expression.Lambda> + ( + setterCall, instance, argument + ).Compile(); + } + + public override SetMemberRefDelegate CreateSetterRef(FieldInfo fieldInfo) + { + var instance = Expression.Parameter(typeof(T).MakeByRefType(), "i"); + var argument = Expression.Parameter(typeof(object), "a"); + + var field = typeof(T) != fieldInfo.DeclaringType + ? Expression.Field(Expression.TypeAs(instance, fieldInfo.DeclaringType), fieldInfo) + : Expression.Field(instance, fieldInfo); + + var setterCall = Expression.Assign( + field, + Expression.Convert(argument, fieldInfo.FieldType)); + + return Expression.Lambda> + ( + setterCall, instance, argument + ).Compile(); + } + + public override bool IsDynamic(Assembly assembly) => RuntimeReflectionOptimizer.Provider.IsDynamic(assembly); + + public override EmptyCtorDelegate CreateConstructor(Type type) => RuntimeReflectionOptimizer.Provider.CreateConstructor(type); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/RuntimeSerializableAttribute.cs b/src/ServiceStack.Text/RuntimeSerializableAttribute.cs new file mode 100644 index 000000000..ccd155802 --- /dev/null +++ b/src/ServiceStack.Text/RuntimeSerializableAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace ServiceStack.Text +{ + /// + /// Allow Type to be deserialized into late-bound object Types using __type info + /// + [AttributeUsage(AttributeTargets.Class)] + public class RuntimeSerializableAttribute : Attribute {} + + /// + /// Allow Type to be deserialized into late-bound object Types using __type info + /// + public interface IRuntimeSerializable { } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/ServiceStack.Text.Core.csproj b/src/ServiceStack.Text/ServiceStack.Text.Core.csproj new file mode 100644 index 000000000..14f46e2db --- /dev/null +++ b/src/ServiceStack.Text/ServiceStack.Text.Core.csproj @@ -0,0 +1,29 @@ + + + ServiceStack.Text.Core + ServiceStack.Text + ServiceStack.Text + netstandard2.0;net6.0 + ServiceStack.Text .NET Standard 2.0 + + .NET's fastest JSON, JSV and CSV Text Serializers. Fast, Light, Resilient. + Contains ServiceStack's high-performance text-processing powers, for more info see: + https://github.com/ServiceStack/ServiceStack.Text + + JSON;Text;Serializer;CSV;JSV;HTTP;Auto Mapping;Dump;Reflection;JS;Utils;Fast + 1591 + + + $(DefineConstants);NETSTANDARD;NET6_0 + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ServiceStack.Text/ServiceStack.Text.XBox360.csproj b/src/ServiceStack.Text/ServiceStack.Text.XBox360.csproj deleted file mode 100644 index f1bbd5e99..000000000 --- a/src/ServiceStack.Text/ServiceStack.Text.XBox360.csproj +++ /dev/null @@ -1,138 +0,0 @@ - - - - {E2B0C358-6CC5-4D15-AD73-41730FBF5530} - {6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Debug - Xbox 360 - Library - Properties - ServiceStack.Text.XBox360 - ServiceStack.Text.XBox360 - v4.0 - Client - v4.0 - Xbox 360 - HiDef - 0ed135ea-1216-457e-bc3e-9a191a60dafa - Library - - - true - full - false - bin\Xbox 360\Debug - DEBUG;TRACE;XBOX;XBOX360 - prompt - 4 - true - false - true - - - pdbonly - true - bin\Xbox 360\Release - TRACE;XBOX;XBOX360 - prompt - 4 - true - false - true - - - - - - - - - - - - - - - - 4.0 - - - 4.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/ServiceStack.Text/ServiceStack.Text.csproj b/src/ServiceStack.Text/ServiceStack.Text.csproj index d1b95a193..23980e788 100644 --- a/src/ServiceStack.Text/ServiceStack.Text.csproj +++ b/src/ServiceStack.Text/ServiceStack.Text.csproj @@ -1,311 +1,37 @@ - - + - Debug - AnyCPU - 9.0.30729 - 2.0 - {579B3FDB-CDAD-44E1-8417-885C38E49A0E} - Library - Properties - ServiceStack.Text + ServiceStack.Text ServiceStack.Text - v3.5 - 512 - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - + net472;netstandard2.0;net6.0 + .NET's fastest JSON Serializer by ServiceStack + + .NET's fastest JSON, JSV and CSV Text Serializers. Fast, Light, Resilient. + Contains ServiceStack's high-performance text-processing powers, for more info see: + https://github.com/ServiceStack/ServiceStack.Text + + JSON;Text;Serializer;CSV;JSV;HTTP;Auto Mapping;Dump;Reflection;JS;Utils;Fast + 1591 - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset + + $(DefineConstants);NETFX;NET472 - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - bin\Release\ServiceStack.Text.XML - - - pdbonly - true - bin\MonoTouch - MONOTOUCH;TRACE - prompt - 4 - AllRules.ruleset + + $(DefineConstants);NETCORE;NETSTANDARD;NETSTANDARD2_0 + + + $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - 3.5 - - - - 3.0 - - - 3.5 - - - + + - - - Code - - - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - - Code - - - - Code - - - Code - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - + + + + + + - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - + - - \ No newline at end of file diff --git a/src/ServiceStack.Text/ServiceStack.Text.suo b/src/ServiceStack.Text/ServiceStack.Text.suo deleted file mode 100644 index 5de62b60c..000000000 Binary files a/src/ServiceStack.Text/ServiceStack.Text.suo and /dev/null differ diff --git a/src/ServiceStack.Text/StreamExtensions.cs b/src/ServiceStack.Text/StreamExtensions.cs index eaa28adaa..6ea626c7d 100644 --- a/src/ServiceStack.Text/StreamExtensions.cs +++ b/src/ServiceStack.Text/StreamExtensions.cs @@ -1,230 +1,675 @@ +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + using System; +using System.Buffers.Text; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text; +using ServiceStack.Text.Pools; -namespace ServiceStack.Text +namespace ServiceStack { - public static class StreamExtensions - { - public static void WriteTo(this Stream inStream, Stream outStream) - { - var memoryStream = inStream as MemoryStream; - if (memoryStream != null) - { - memoryStream.WriteTo(outStream); - return; - } - - var data = new byte[4096]; - int bytesRead; - - while ((bytesRead = inStream.Read(data, 0, data.Length)) > 0) - { - outStream.Write(data, 0, bytesRead); - } - } - - public static IEnumerable ReadLines(this StreamReader reader) - { - if (reader == null) - throw new ArgumentNullException("reader"); - - string line; - while ((line = reader.ReadLine()) != null) - { - yield return line; - } - } - - /// - /// @jonskeet: Collection of utility methods which operate on streams. - /// r285, February 26th 2009: http://www.yoda.arachsys.com/csharp/miscutil/ - /// - const int DefaultBufferSize = 8 * 1024; - - /// - /// Reads the given stream up to the end, returning the data as a byte - /// array. - /// - public static byte[] ReadFully(this Stream input) - { - return ReadFully(input, DefaultBufferSize); - } - - /// - /// Reads the given stream up to the end, returning the data as a byte - /// array, using the given buffer size. - /// - public static byte[] ReadFully(this Stream input, int bufferSize) - { - if (bufferSize < 1) - { - throw new ArgumentOutOfRangeException("bufferSize"); - } - return ReadFully(input, new byte[bufferSize]); - } - - /// - /// Reads the given stream up to the end, returning the data as a byte - /// array, using the given buffer for transferring data. Note that the - /// current contents of the buffer is ignored, so the buffer needn't - /// be cleared beforehand. - /// - public static byte[] ReadFully(this Stream input, byte[] buffer) - { - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } - if (input == null) - { - throw new ArgumentNullException("input"); - } - if (buffer.Length == 0) - { - throw new ArgumentException("Buffer has length of 0"); - } - // We could do all our own work here, but using MemoryStream is easier - // and likely to be just as efficient. - using (var tempStream = new MemoryStream()) - { - CopyTo(input, tempStream, buffer); - // No need to copy the buffer if it's the right size - if (tempStream.Length == tempStream.GetBuffer().Length) - { - return tempStream.GetBuffer(); - } - // Okay, make a copy that's the right size - return tempStream.ToArray(); - } - } - - /// - /// Copies all the data from one stream into another. - /// - public static void CopyTo(this Stream input, Stream output) - { - CopyTo(input, output, DefaultBufferSize); - } - - /// - /// Copies all the data from one stream into another, using a buffer - /// of the given size. - /// - public static void CopyTo(this Stream input, Stream output, int bufferSize) - { - if (bufferSize < 1) - { - throw new ArgumentOutOfRangeException("bufferSize"); - } - CopyTo(input, output, new byte[bufferSize]); - } - - /// - /// Copies all the data from one stream into another, using the given - /// buffer for transferring data. Note that the current contents of - /// the buffer is ignored, so the buffer needn't be cleared beforehand. - /// - public static void CopyTo(this Stream input, Stream output, byte[] buffer) - { - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } - if (input == null) - { - throw new ArgumentNullException("input"); - } - if (output == null) - { - throw new ArgumentNullException("output"); - } - if (buffer.Length == 0) - { - throw new ArgumentException("Buffer has length of 0"); - } - int read; - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) - { - output.Write(buffer, 0, read); - } - } - - /// - /// Reads exactly the given number of bytes from the specified stream. - /// If the end of the stream is reached before the specified amount - /// of data is read, an exception is thrown. - /// - public static byte[] ReadExactly(this Stream input, int bytesToRead) - { - return ReadExactly(input, new byte[bytesToRead]); - } - - /// - /// Reads into a buffer, filling it completely. - /// - public static byte[] ReadExactly(this Stream input, byte[] buffer) - { - return ReadExactly(input, buffer, buffer.Length); - } - - /// - /// Reads exactly the given number of bytes from the specified stream, - /// into the given buffer, starting at position 0 of the array. - /// - public static byte[] ReadExactly(this Stream input, byte[] buffer, int bytesToRead) - { - return ReadExactly(input, buffer, 0, bytesToRead); - } - - /// - /// Reads exactly the given number of bytes from the specified stream, - /// into the given buffer, starting at position 0 of the array. - /// - public static byte[] ReadExactly(this Stream input, byte[] buffer, int startIndex, int bytesToRead) - { - if (input == null) - { - throw new ArgumentNullException("input"); - } - - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } - - if (startIndex < 0 || startIndex >= buffer.Length) - { - throw new ArgumentOutOfRangeException("startIndex"); - } - - if (bytesToRead < 1 || startIndex + bytesToRead > buffer.Length) - { - throw new ArgumentOutOfRangeException("bytesToRead"); - } - - return ReadExactlyFast(input, buffer, startIndex, bytesToRead); - } - - /// - /// Same as ReadExactly, but without the argument checks. - /// - private static byte[] ReadExactlyFast(Stream fromStream, byte[] intoBuffer, int startAtIndex, int bytesToRead) - { - var index = 0; - while (index < bytesToRead) - { - var read = fromStream.Read(intoBuffer, startAtIndex + index, bytesToRead - index); - if (read == 0) - { - throw new EndOfStreamException - (String.Format("End of stream reached with {0} byte{1} left to read.", - bytesToRead - index, - bytesToRead - index == 1 ? "s" : "")); - } - index += read; - } - return intoBuffer; - } - } -} \ No newline at end of file + public static class StreamExtensions + { + public static long WriteTo(this Stream inStream, Stream outStream) + { + if (inStream is MemoryStream memoryStream) + { + memoryStream.WriteTo(outStream); + return memoryStream.Position; + } + + var data = new byte[4096]; + long total = 0; + int bytesRead; + + while ((bytesRead = inStream.Read(data, 0, data.Length)) > 0) + { + outStream.Write(data, 0, bytesRead); + total += bytesRead; + } + + return total; + } + + public static IEnumerable ReadLines(this Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + using var reader = new StreamReader(stream); + string line; + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } + + /// + /// @jonskeet: Collection of utility methods which operate on streams. + /// r285, February 26th 2009: http://www.yoda.arachsys.com/csharp/miscutil/ + /// + public const int DefaultBufferSize = 8 * 1024; + + /// + /// Reads the given stream up to the end, returning the data as a byte array. + /// + public static byte[] ReadFully(this Stream input) => ReadFully(input, DefaultBufferSize); + + /// + /// Reads the given stream up to the end, returning the data as a byte + /// array, using the given buffer size. + /// + public static byte[] ReadFully(this Stream input, int bufferSize) + { + if (bufferSize < 1) + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + + byte[] buffer = BufferPool.GetBuffer(bufferSize); + try + { + return ReadFully(input, buffer); + } + finally + { + BufferPool.ReleaseBufferToPool(ref buffer); + } + } + + /// + /// Reads the given stream up to the end, returning the data as a byte + /// array, using the given buffer for transferring data. Note that the + /// current contents of the buffer is ignored, so the buffer needn't + /// be cleared beforehand. + /// + public static byte[] ReadFully(this Stream input, byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + // We could do all our own work here, but using MemoryStream is easier + // and likely to be just as efficient. + using var tempStream = MemoryStreamFactory.GetStream(); + CopyTo(input, tempStream, buffer); + // No need to copy the buffer if it's the right size + if (tempStream.Length == tempStream.GetBuffer().Length) + { + return tempStream.GetBuffer(); + } + // Okay, make a copy that's the right size + return tempStream.ToArray(); + } + + /// + /// Reads the given stream up to the end, returning the data as a byte array. + /// + public static Task ReadFullyAsync(this Stream input, CancellationToken token=default) => + ReadFullyAsync(input, DefaultBufferSize, token); + + /// + /// Reads the given stream up to the end, returning the data as a byte + /// array, using the given buffer size. + /// + public static async Task ReadFullyAsync(this Stream input, int bufferSize, CancellationToken token=default) + { + if (bufferSize < 1) + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + + byte[] buffer = BufferPool.GetBuffer(bufferSize); + try + { + return await ReadFullyAsync(input, buffer, token); + } + finally + { + BufferPool.ReleaseBufferToPool(ref buffer); + } + } + + /// + /// Reads the given stream up to the end, returning the data as a byte + /// array, using the given buffer for transferring data. Note that the + /// current contents of the buffer is ignored, so the buffer needn't + /// be cleared beforehand. + /// + public static async Task ReadFullyAsync(this Stream input, byte[] buffer, CancellationToken token=default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + // We could do all our own work here, but using MemoryStream is easier + // and likely to be just as efficient. + using var tempStream = MemoryStreamFactory.GetStream(); + await CopyToAsync(input, tempStream, buffer, token); + // No need to copy the buffer if it's the right size + if (tempStream.Length == tempStream.GetBuffer().Length) + { + return tempStream.GetBuffer(); + } + // Okay, make a copy that's the right size + return tempStream.ToArray(); + } + + /// + /// Reads the given stream up to the end, returning the MemoryStream Buffer as ReadOnlyMemory<byte>. + /// + public static ReadOnlyMemory ReadFullyAsMemory(this Stream input) => + ReadFullyAsMemory(input, DefaultBufferSize); + + /// + /// Reads the given stream up to the end, returning the MemoryStream Buffer as ReadOnlyMemory<byte>. + /// + public static ReadOnlyMemory ReadFullyAsMemory(this Stream input, int bufferSize) + { + byte[] buffer = BufferPool.GetBuffer(bufferSize); + try + { + return ReadFullyAsMemory(input, buffer); + } + finally + { + BufferPool.ReleaseBufferToPool(ref buffer); + } + } + + public static ReadOnlyMemory ReadFullyAsMemory(this Stream input, byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + var ms = new MemoryStream(); + CopyTo(input, ms, buffer); + return ms.GetBufferAsMemory(); + } + + /// + /// Reads the given stream up to the end, returning the MemoryStream Buffer as ReadOnlyMemory<byte>. + /// + public static Task> ReadFullyAsMemoryAsync(this Stream input, CancellationToken token=default) => + ReadFullyAsMemoryAsync(input, DefaultBufferSize, token); + + /// + /// Reads the given stream up to the end, returning the MemoryStream Buffer as ReadOnlyMemory<byte>. + /// + public static async Task> ReadFullyAsMemoryAsync(this Stream input, int bufferSize, CancellationToken token=default) + { + byte[] buffer = BufferPool.GetBuffer(bufferSize); + try + { + return await ReadFullyAsMemoryAsync(input, buffer, token); + } + finally + { + BufferPool.ReleaseBufferToPool(ref buffer); + } + } + + public static async Task> ReadFullyAsMemoryAsync(this Stream input, byte[] buffer, CancellationToken token=default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + var ms = new MemoryStream(); + await CopyToAsync(input, ms, buffer, token); + return ms.GetBufferAsMemory(); + } + + + /// + /// Copies all the data from one stream into another. + /// + public static long CopyTo(this Stream input, Stream output) + { + return CopyTo(input, output, DefaultBufferSize); + } + + /// + /// Copies all the data from one stream into another, using a buffer + /// of the given size. + /// + public static long CopyTo(this Stream input, Stream output, int bufferSize) + { + if (bufferSize < 1) + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + + return CopyTo(input, output, new byte[bufferSize]); + } + + /// + /// Copies all the data from one stream into another, using the given + /// buffer for transferring data. Note that the current contents of + /// the buffer is ignored, so the buffer needn't be cleared beforehand. + /// + public static long CopyTo(this Stream input, Stream output, byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (output == null) + throw new ArgumentNullException(nameof(output)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + long total = 0; + int read; + while ((read = input.Read(buffer, 0, buffer.Length)) > 0) + { + output.Write(buffer, 0, read); + total += read; + } + return total; + } + + /// + /// Copies all the data from one stream into another, using the given + /// buffer for transferring data. Note that the current contents of + /// the buffer is ignored, so the buffer needn't be cleared beforehand. + /// + public static async Task CopyToAsync(this Stream input, Stream output, byte[] buffer, CancellationToken token=default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (output == null) + throw new ArgumentNullException(nameof(output)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + long total = 0; + int read; + while ((read = await input.ReadAsync(buffer, 0, buffer.Length, token)) > 0) + { + await output.WriteAsync(buffer, 0, read, token); + total += read; + } + return total; + } + + /// + /// Reads exactly the given number of bytes from the specified stream. + /// If the end of the stream is reached before the specified amount + /// of data is read, an exception is thrown. + /// + public static byte[] ReadExactly(this Stream input, int bytesToRead) + { + return ReadExactly(input, new byte[bytesToRead]); + } + + /// + /// Reads into a buffer, filling it completely. + /// + public static byte[] ReadExactly(this Stream input, byte[] buffer) + { + return ReadExactly(input, buffer, buffer.Length); + } + + /// + /// Reads exactly the given number of bytes from the specified stream, + /// into the given buffer, starting at position 0 of the array. + /// + public static byte[] ReadExactly(this Stream input, byte[] buffer, int bytesToRead) + { + return ReadExactly(input, buffer, 0, bytesToRead); + } + + /// + /// Reads exactly the given number of bytes from the specified stream, + /// into the given buffer, starting at position 0 of the array. + /// + public static byte[] ReadExactly(this Stream input, byte[] buffer, int startIndex, int bytesToRead) + { + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (startIndex < 0 || startIndex >= buffer.Length) + throw new ArgumentOutOfRangeException(nameof(startIndex)); + + if (bytesToRead < 1 || startIndex + bytesToRead > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(bytesToRead)); + + return ReadExactlyFast(input, buffer, startIndex, bytesToRead); + } + + /// + /// Same as ReadExactly, but without the argument checks. + /// + private static byte[] ReadExactlyFast(Stream fromStream, byte[] intoBuffer, int startAtIndex, int bytesToRead) + { + var index = 0; + while (index < bytesToRead) + { + var read = fromStream.Read(intoBuffer, startAtIndex + index, bytesToRead - index); + if (read == 0) + throw new EndOfStreamException + ($"End of stream reached with {bytesToRead - index} byte{(bytesToRead - index == 1 ? "s" : "")} left to read."); + + index += read; + } + return intoBuffer; + } + + public static string CollapseWhitespace(this string str) + { + if (str == null) + return null; + + var sb = StringBuilderThreadStatic.Allocate(); + var lastChar = (char)0; + for (var i = 0; i < str.Length; i++) + { + var c = str[i]; + if (c < 32) continue; // Skip all these + if (c == 32) + { + if (lastChar == 32) + continue; // Only write one space character + } + sb.Append(c); + lastChar = c; + } + + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + + public static byte[] Combine(this byte[] bytes, params byte[][] withBytes) + { + var combinedLength = bytes.Length + withBytes.Sum(b => b.Length); + var to = new byte[combinedLength]; + + Buffer.BlockCopy(bytes, 0, to, 0, bytes.Length); + var pos = bytes.Length; + + foreach (var b in withBytes) + { + Buffer.BlockCopy(b, 0, to, pos, b.Length); + pos += b.Length; + } + + return to; + } + + public static int AsyncBufferSize = 81920; // CopyToAsync() default value + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task WriteAsync(this Stream stream, ReadOnlyMemory value, CancellationToken token = default) => + MemoryProvider.Instance.WriteAsync(stream, value, token); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task WriteAsync(this Stream stream, byte[] bytes, CancellationToken token = default) => + MemoryProvider.Instance.WriteAsync(stream, bytes, token); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task CopyToAsync(this Stream input, Stream output, CancellationToken token = default) => input.CopyToAsync(output, AsyncBufferSize, token); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task WriteAsync(this Stream stream, string text, CancellationToken token = default) => + MemoryProvider.Instance.WriteAsync(stream, text.AsSpan(), token); + + public static byte[] ToMd5Bytes(this Stream stream) + { +#if NET6_0_OR_GREATER + if (stream is MemoryStream ms) + return System.Security.Cryptography.MD5.HashData(ms.GetBufferAsSpan()); +#endif + if (stream.CanSeek) + { + stream.Position = 0; + } + return System.Security.Cryptography.MD5.Create().ComputeHash(stream); + } + + public static string ToMd5Hash(this Stream stream) => ToMd5Bytes(stream).ToHex(); + + public static string ToMd5Hash(this byte[] bytes) => + System.Security.Cryptography.MD5.Create().ComputeHash(bytes).ToHex(); + + /// + /// Returns bytes in publiclyVisible MemoryStream + /// + public static MemoryStream InMemoryStream(this byte[] bytes) + { + return new MemoryStream(bytes, 0, bytes.Length, writable: true, publiclyVisible: true); + } + + public static string ReadToEnd(this MemoryStream ms) => ReadToEnd(ms, JsConfig.UTF8Encoding); + public static string ReadToEnd(this MemoryStream ms, Encoding encoding) + { + ms.Position = 0; + +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return encoding.GetString(buffer.Array, buffer.Offset, buffer.Count); + } +#else + try + { + return encoding.GetString(ms.GetBuffer(), 0, (int) ms.Length); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + + using var reader = new StreamReader(ms, encoding, true, DefaultBufferSize, leaveOpen: true); + return reader.ReadToEnd(); + } + + public static ReadOnlyMemory GetBufferAsMemory(this MemoryStream ms) + { +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return new ReadOnlyMemory(buffer.Array, buffer.Offset, buffer.Count); + } +#else + try + { + return new ReadOnlyMemory(ms.GetBuffer(), 0, (int) ms.Length); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream in GetBufferAsSpan() wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + return new ReadOnlyMemory(ms.ToArray()); + } + + public static ReadOnlySpan GetBufferAsSpan(this MemoryStream ms) + { +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return new ReadOnlySpan(buffer.Array, buffer.Offset, buffer.Count); + } +#else + try + { + return new ReadOnlySpan(ms.GetBuffer(), 0, (int) ms.Length); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream in GetBufferAsSpan() wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + return new ReadOnlySpan(ms.ToArray()); + } + + public static byte[] GetBufferAsBytes(this MemoryStream ms) + { +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return buffer.Array; + } +#else + try + { + return ms.GetBuffer(); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream in GetBufferAsBytes() wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + return ms.ToArray(); + } + + public static Task ReadToEndAsync(this MemoryStream ms) => ReadToEndAsync(ms, JsConfig.UTF8Encoding); + public static Task ReadToEndAsync(this MemoryStream ms, Encoding encoding) + { + ms.Position = 0; + +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return encoding.GetString(buffer.Array, buffer.Offset, buffer.Count).InTask(); + } +#else + try + { + return encoding.GetString(ms.GetBuffer(), 0, (int) ms.Length).InTask(); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream in ReadToEndAsync() wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + + using var reader = new StreamReader(ms, encoding, true, DefaultBufferSize, leaveOpen: true); + return reader.ReadToEndAsync(); + } + + public static string ReadToEnd(this Stream stream) => ReadToEnd(stream, JsConfig.UTF8Encoding); + public static string ReadToEnd(this Stream stream, Encoding encoding) + { + if (stream is MemoryStream ms) + return ms.ReadToEnd(); + + if (stream.CanSeek) + { + stream.Position = 0; + } + + using var reader = new StreamReader(stream, encoding, true, DefaultBufferSize, leaveOpen:true); + return reader.ReadToEnd(); + } + + public static Task ReadToEndAsync(this Stream stream) => ReadToEndAsync(stream, JsConfig.UTF8Encoding); + public static Task ReadToEndAsync(this Stream stream, Encoding encoding) + { + if (stream is MemoryStream ms) + return ms.ReadToEndAsync(encoding); + + if (stream.CanSeek) + { + stream.Position = 0; + } + + using var reader = new StreamReader(stream, encoding, true, DefaultBufferSize, leaveOpen:true); + return reader.ReadToEndAsync(); + } + + public static Task WriteToAsync(this MemoryStream stream, Stream output, CancellationToken token=default(CancellationToken)) => + WriteToAsync(stream, output, JsConfig.UTF8Encoding, token); + + public static async Task WriteToAsync(this MemoryStream stream, Stream output, Encoding encoding, CancellationToken token) + { +#if NETCORE + if (stream.TryGetBuffer(out var buffer)) + { + await output.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, token).ConfigAwait(); + return; + } +#else + try + { + await output.WriteAsync(stream.GetBuffer(), 0, (int) stream.Length, token).ConfigAwait(); + return; + } + catch (UnauthorizedAccessException) + { + } +#endif + Tracer.Instance.WriteWarning("MemoryStream in WriteToAsync() wasn't created with a publiclyVisible:true byte[] bufffer, falling back to slow impl"); + + var bytes = stream.ToArray(); + await output.WriteAsync(bytes, 0, bytes.Length, token).ConfigAwait(); + } + + public static Task WriteToAsync(this Stream stream, Stream output, CancellationToken token=default(CancellationToken)) => + WriteToAsync(stream, output, JsConfig.UTF8Encoding, token); + + + public static Task WriteToAsync(this Stream stream, Stream output, Encoding encoding, CancellationToken token) + { + if (stream is MemoryStream ms) + return ms.WriteToAsync(output, encoding, token); + + return stream.CopyToAsync(output, token); + } + + public static MemoryStream CopyToNewMemoryStream(this Stream stream) + { + var ms = MemoryStreamFactory.GetStream(); + stream.CopyTo(ms); + ms.Position = 0; + return ms; + } + + public static async Task CopyToNewMemoryStreamAsync(this Stream stream) + { + var ms = MemoryStreamFactory.GetStream(); + await stream.CopyToAsync(ms).ConfigAwait(); + ms.Position = 0; + return ms; + } + } +} diff --git a/src/ServiceStack.Text/StringBuilderCache.cs b/src/ServiceStack.Text/StringBuilderCache.cs new file mode 100644 index 000000000..27d5712ea --- /dev/null +++ b/src/ServiceStack.Text/StringBuilderCache.cs @@ -0,0 +1,99 @@ +using System; +using System.Text; + +namespace ServiceStack.Text +{ + /// + /// Reusable StringBuilder ThreadStatic Cache + /// + public static class StringBuilderCache + { + [ThreadStatic] + static StringBuilder cache; + + public static StringBuilder Allocate() + { + var ret = cache; + if (ret == null) + return new StringBuilder(); + + ret.Length = 0; + cache = null; //don't re-issue cached instance until it's freed + return ret; + } + + public static void Free(StringBuilder sb) + { + cache = sb; + } + + public static string ReturnAndFree(StringBuilder sb) + { + var ret = sb.ToString(); + cache = sb; + return ret; + } + } + + /// + /// Alternative Reusable StringBuilder ThreadStatic Cache + /// + public static class StringBuilderCacheAlt + { + [ThreadStatic] + static StringBuilder cache; + + public static StringBuilder Allocate() + { + var ret = cache; + if (ret == null) + return new StringBuilder(); + + ret.Length = 0; + cache = null; //don't re-issue cached instance until it's freed + return ret; + } + + public static void Free(StringBuilder sb) + { + cache = sb; + } + + public static string ReturnAndFree(StringBuilder sb) + { + var ret = sb.ToString(); + cache = sb; + return ret; + } + } + + //Use separate cache internally to avoid re-allocations and cache misses + internal static class StringBuilderThreadStatic + { + [ThreadStatic] + static StringBuilder cache; + + public static StringBuilder Allocate() + { + var ret = cache; + if (ret == null) + return new StringBuilder(); + + ret.Length = 0; + cache = null; //don't re-issue cached instance until it's freed + return ret; + } + + public static void Free(StringBuilder sb) + { + cache = sb; + } + + public static string ReturnAndFree(StringBuilder sb) + { + var ret = sb.ToString(); + cache = sb; + return ret; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/StringExtensions.cs b/src/ServiceStack.Text/StringExtensions.cs index 0fbfb21dc..79206d0b0 100644 --- a/src/ServiceStack.Text/StringExtensions.cs +++ b/src/ServiceStack.Text/StringExtensions.cs @@ -5,52 +5,27 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Reflection; -using System.Runtime.CompilerServices; +using System.Linq; using System.Text; using System.Text.RegularExpressions; +using ServiceStack.Text; using ServiceStack.Text.Common; using ServiceStack.Text.Support; -#if WINDOWS_PHONE -using System.IO.IsolatedStorage; -using ServiceStack.Text.WP; +using static System.String; -#endif - -namespace ServiceStack.Text +namespace ServiceStack { public static class StringExtensions { - public static T To(this string value) - { - return TypeSerializer.DeserializeFromString(value); - } - - public static T To(this string value, T defaultValue) - { - return string.IsNullOrEmpty(value) ? defaultValue : TypeSerializer.DeserializeFromString(value); - } - - public static T ToOrDefaultValue(this string value) - { - return string.IsNullOrEmpty(value) ? default(T) : TypeSerializer.DeserializeFromString(value); - } - - public static object To(this string value, Type type) - { - return TypeSerializer.DeserializeFromString(value, type); - } - - /// /// Converts from base: 0 - 62 /// @@ -60,44 +35,34 @@ public static object To(this string value, Type type) /// public static string BaseConvert(this string source, int from, int to) { - const string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - var result = ""; - var length = source.Length; - var number = new int[length]; - - for (var i = 0; i < length; i++) + var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + var len = source.Length; + if (len == 0) + throw new Exception($"Parameter: '{source}' is not valid integer (in base {@from})."); + var minus = source[0] == '-' ? "-" : ""; + var src = minus == "" ? source : source.Substring(1); + len = src.Length; + if (len == 0) + throw new Exception($"Parameter: '{source}' is not valid integer (in base {@from})."); + + var d = 0; + for (int i = 0; i < len; i++) // Convert to decimal { - number[i] = chars.IndexOf(source[i]); + int c = chars.IndexOf(src[i]); + if (c >= from) + throw new Exception($"Parameter: '{source}' is not valid integer (in base {@from})."); + d = d * from + c; } + if (to == 10 || d == 0) + return minus + d; - int newlen; - - do + var result = ""; + while (d > 0) // Convert to desired { - var divide = 0; - newlen = 0; - - for (var i = 0; i < length; i++) - { - divide = divide * from + number[i]; - - if (divide >= to) - { - number[newlen++] = divide / to; - divide = divide % to; - } - else if (newlen > 0) - { - number[newlen++] = 0; - } - } - - length = newlen; - result = chars[divide] + result; + result = chars[d % to] + result; + d /= to; } - while (newlen != 0); - - return result; + return minus + result; } public static string EncodeXml(this string value) @@ -107,7 +72,7 @@ public static string EncodeXml(this string value) public static string EncodeJson(this string value) { - return string.Concat + return Concat ("\"", value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "").Replace("\n", "\\n"), "\"" @@ -116,32 +81,37 @@ public static string EncodeJson(this string value) public static string EncodeJsv(this string value) { - if (JsState.QueryStringMode) value = UrlEncode(value); - return string.IsNullOrEmpty(value) || !JsWriter.HasAnyEscapeChars(value) - ? value - : string.Concat - ( - JsWriter.QuoteString, - value.Replace(JsWriter.QuoteString, TypeSerializer.DoubleQuoteString), - JsWriter.QuoteString - ); + if (JsState.QueryStringMode) + { + return UrlEncode(value); + } + return String.IsNullOrEmpty(value) || !JsWriter.HasAnyEscapeChars(value) + ? value + : Concat + ( + JsWriter.QuoteString, + value.Replace(JsWriter.QuoteString, TypeSerializer.DoubleQuoteString), + JsWriter.QuoteString + ); } public static string DecodeJsv(this string value) { - const int startingQuotePos = 1; - const int endingQuotePos = 2; - return string.IsNullOrEmpty(value) || value[0] != JsWriter.QuoteChar - ? value - : value.Substring(startingQuotePos, value.Length - endingQuotePos) - .Replace(TypeSerializer.DoubleQuoteString, JsWriter.QuoteString); + const int startingQuotePos = 1; + const int endingQuotePos = 2; + return String.IsNullOrEmpty(value) || value[0] != JsWriter.QuoteChar + ? value + : value.Substring(startingQuotePos, value.Length - endingQuotePos) + .Replace(TypeSerializer.DoubleQuoteString, JsWriter.QuoteString); } - public static string UrlEncode(this string text) + public static string UrlEncode(this string text, bool upperCase=false) { - if (string.IsNullOrEmpty(text)) return text; + if (string.IsNullOrEmpty(text)) + return text; - var sb = new StringBuilder(); + var sb = StringBuilderThreadStatic.Allocate(); + var fmt = upperCase ? "X2" : "x2"; foreach (var charCode in Encoding.UTF8.GetBytes(text)) { @@ -155,18 +125,22 @@ public static string UrlEncode(this string text) { sb.Append((char)charCode); } + else if(charCode == 32) + { + sb.Append('+'); + } else { - sb.Append('%' + charCode.ToString("x2")); + sb.Append('%' + charCode.ToString(fmt)); } } - return sb.ToString(); + return StringBuilderThreadStatic.ReturnAndFree(sb); } public static string UrlDecode(this string text) { - if (string.IsNullOrEmpty(text)) return null; + if (String.IsNullOrEmpty(text)) return null; var bytes = new List(); @@ -189,45 +163,17 @@ public static string UrlDecode(this string text) bytes.Add((byte)c); } } -#if SILVERLIGHT + byte[] byteArray = bytes.ToArray(); return Encoding.UTF8.GetString(byteArray, 0, byteArray.Length); -#else - return Encoding.UTF8.GetString(bytes.ToArray()); -#endif } -#if !XBOX - public static string HexEscape(this string text, params char[] anyCharOf) - { - if (string.IsNullOrEmpty(text)) return text; - if (anyCharOf == null || anyCharOf.Length == 0) return text; - - var encodeCharMap = new HashSet(anyCharOf); - - var sb = new StringBuilder(); - var textLength = text.Length; - for (var i = 0; i < textLength; i++) - { - var c = text[i]; - if (encodeCharMap.Contains(c)) - { - sb.Append('%' + ((int)c).ToString("x")); - } - else - { - sb.Append(c); - } - } - return sb.ToString(); - } -#endif public static string HexUnescape(this string text, params char[] anyCharOf) { - if (string.IsNullOrEmpty(text)) return null; + if (String.IsNullOrEmpty(text)) return null; if (anyCharOf == null || anyCharOf.Length == 0) return text; - var sb = new StringBuilder(); + var sb = StringBuilderThreadStatic.Allocate(); var textLength = text.Length; for (var i = 0; i < textLength; i++) @@ -245,7 +191,7 @@ public static string HexUnescape(this string text, params char[] anyCharOf) } } - return sb.ToString(); + return StringBuilderThreadStatic.ReturnAndFree(sb); } public static string UrlFormat(this string url, params string[] urlComponents) @@ -257,7 +203,7 @@ public static string UrlFormat(this string url, params string[] urlComponents) encodedUrlComponents[i] = x.UrlEncode(); } - return string.Format(url, encodedUrlComponents); + return Format(url, encodedUrlComponents); } public static string ToRot13(this string value) @@ -278,16 +224,24 @@ public static string ToRot13(this string value) return new string(array); } + private static char[] UrlPathDelims = new[] {'?', '#'}; + + public static string UrlWithTrailingSlash(this string url) + { + var endPos = url?.IndexOfAny(UrlPathDelims) ?? -1; + return endPos >= 0 + ? url.Substring(0, endPos).WithTrailingSlash() + url.Substring(endPos) + : url.WithTrailingSlash(); + } + public static string WithTrailingSlash(this string path) { - if (string.IsNullOrEmpty(path)) - throw new ArgumentNullException("path"); + if (path == null) + throw new ArgumentNullException(nameof(path)); + if (path == "") + return "/"; - if (path[path.Length - 1] != '/') - { - return path + "/"; - } - return path; + return path[path.Length - 1] != '/' ? path + "/" : path; } public static string AppendPath(this string uri, params string[] uriComponents) @@ -297,20 +251,36 @@ public static string AppendPath(this string uri, params string[] uriComponents) public static string AppendUrlPaths(this string uri, params string[] uriComponents) { - var sb = new StringBuilder(uri.WithTrailingSlash()); + var sb = StringBuilderThreadStatic.Allocate(); + sb.Append(uri.WithTrailingSlash()); var i = 0; foreach (var uriComponent in uriComponents) { if (i++ > 0) sb.Append('/'); sb.Append(uriComponent.UrlEncode()); } - return sb.ToString(); + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + + public static string AppendUrlPathsRaw(this string uri, params string[] uriComponents) + { + var sb = StringBuilderThreadStatic.Allocate(); + sb.Append(uri.WithTrailingSlash()); + var i = 0; + foreach (var uriComponent in uriComponents) + { + if (i++ > 0) sb.Append('/'); + sb.Append(uriComponent); + } + return StringBuilderThreadStatic.ReturnAndFree(sb); } public static string FromUtf8Bytes(this byte[] bytes) { return bytes == null ? null - : Encoding.UTF8.GetString(bytes, 0, bytes.Length); + : bytes.Length > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF + ? Encoding.UTF8.GetString(bytes, 3, bytes.Length - 3) + : Encoding.UTF8.GetString(bytes, 0, bytes.Length); } public static byte[] ToUtf8Bytes(this string value) @@ -328,6 +298,11 @@ public static byte[] ToUtf8Bytes(this long longVal) return FastToUtf8Bytes(longVal.ToString()); } + public static byte[] ToUtf8Bytes(this ulong ulongVal) + { + return FastToUtf8Bytes(ulongVal.ToString()); + } + public static byte[] ToUtf8Bytes(this double doubleVal) { var doubleStr = doubleVal.ToString(CultureInfo.InvariantCulture.NumberFormat); @@ -338,6 +313,49 @@ public static byte[] ToUtf8Bytes(this double doubleVal) return FastToUtf8Bytes(doubleStr); } + public static string WithoutBom(this string value) + { + return value.Length > 0 && value[0] == 65279 + ? value.Substring(1) + : value; + } + + // from JWT spec + public static string ToBase64UrlSafe(this byte[] input) + { + var output = Convert.ToBase64String(input); + output = output.LeftPart('='); // Remove any trailing '='s + output = output.Replace('+', '-'); // 62nd char of encoding + output = output.Replace('/', '_'); // 63rd char of encoding + return output; + } + + public static string ToBase64UrlSafe(this MemoryStream ms) + { + var output = Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length); + output = output.LeftPart('='); // Remove any trailing '='s + output = output.Replace('+', '-'); // 62nd char of encoding + output = output.Replace('/', '_'); // 63rd char of encoding + return output; + } + + // from JWT spec + public static byte[] FromBase64UrlSafe(this string input) + { + var output = input; + output = output.Replace('-', '+'); // 62nd char of encoding + output = output.Replace('_', '/'); // 63rd char of encoding + switch (output.Length % 4) // Pad with trailing '='s + { + case 0: break; // No pad chars in this case + case 2: output += "=="; break; // Two pad chars + case 3: output += "="; break; // One pad char + default: throw new Exception("Illegal base64url string!"); + } + var converted = Convert.FromBase64String(output); // Standard base64 decoder + return converted; + } + /// /// Skip the encoding process for 'safe strings' /// @@ -352,9 +370,81 @@ private static byte[] FastToUtf8Bytes(string strVal) return bytes; } + public static string LeftPart(this string strVal, char needle) + { + if (strVal == null) return null; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Substring(0, pos); + } + + public static string LeftPart(this string strVal, string needle) + { + if (strVal == null) return null; + var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase); + return pos == -1 + ? strVal + : strVal.Substring(0, pos); + } + + public static string RightPart(this string strVal, char needle) + { + if (strVal == null) return null; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Substring(pos + 1); + } + + public static string RightPart(this string strVal, string needle) + { + if (strVal == null) return null; + var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase); + return pos == -1 + ? strVal + : strVal.Substring(pos + needle.Length); + } + + public static string LastLeftPart(this string strVal, char needle) + { + if (strVal == null) return null; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Substring(0, pos); + } + + public static string LastLeftPart(this string strVal, string needle) + { + if (strVal == null) return null; + var pos = strVal.LastIndexOf(needle, StringComparison.OrdinalIgnoreCase); + return pos == -1 + ? strVal + : strVal.Substring(0, pos); + } + + public static string LastRightPart(this string strVal, char needle) + { + if (strVal == null) return null; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Substring(pos + 1); + } + + public static string LastRightPart(this string strVal, string needle) + { + if (strVal == null) return null; + var pos = strVal.LastIndexOf(needle, StringComparison.OrdinalIgnoreCase); + return pos == -1 + ? strVal + : strVal.Substring(pos + needle.Length); + } + public static string[] SplitOnFirst(this string strVal, char needle) { - if (strVal == null) return new string[0]; + if (strVal == null) return TypeConstants.EmptyStringArray; var pos = strVal.IndexOf(needle); return pos == -1 ? new[] { strVal } @@ -363,16 +453,16 @@ public static string[] SplitOnFirst(this string strVal, char needle) public static string[] SplitOnFirst(this string strVal, string needle) { - if (strVal == null) return new string[0]; - var pos = strVal.IndexOf(needle); + if (strVal == null) return TypeConstants.EmptyStringArray; + var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase); return pos == -1 ? new[] { strVal } - : new[] { strVal.Substring(0, pos), strVal.Substring(pos + 1) }; + : new[] { strVal.Substring(0, pos), strVal.Substring(pos + needle.Length) }; } public static string[] SplitOnLast(this string strVal, char needle) { - if (strVal == null) return new string[0]; + if (strVal == null) return TypeConstants.EmptyStringArray; var pos = strVal.LastIndexOf(needle); return pos == -1 ? new[] { strVal } @@ -381,35 +471,43 @@ public static string[] SplitOnLast(this string strVal, char needle) public static string[] SplitOnLast(this string strVal, string needle) { - if (strVal == null) return new string[0]; - var pos = strVal.LastIndexOf(needle); + if (strVal == null) return TypeConstants.EmptyStringArray; + var pos = strVal.LastIndexOf(needle, StringComparison.OrdinalIgnoreCase); return pos == -1 ? new[] { strVal } - : new[] { strVal.Substring(0, pos), strVal.Substring(pos + 1) }; + : new[] { strVal.Substring(0, pos), strVal.Substring(pos + needle.Length) }; } public static string WithoutExtension(this string filePath) { - if (string.IsNullOrEmpty(filePath)) return null; + if (String.IsNullOrEmpty(filePath)) + return null; var extPos = filePath.LastIndexOf('.'); if (extPos == -1) return filePath; - var dirPos = filePath.LastIndexOfAny(DirSeps); + var dirPos = filePath.LastIndexOfAny(PclExport.DirSeps); return extPos > dirPos ? filePath.Substring(0, extPos) : filePath; } - private static readonly char DirSep = Path.DirectorySeparatorChar; - private static readonly char AltDirSep = Path.DirectorySeparatorChar == '/' ? '\\' : '/'; - static readonly char[] DirSeps = new[] { '\\', '/' }; + public static string GetExtension(this string filePath) + { + if (String.IsNullOrEmpty(filePath)) + return null; + + var extPos = filePath.LastIndexOf('.'); + return extPos == -1 ? Empty : filePath.Substring(extPos); + } public static string ParentDirectory(this string filePath) { - if (string.IsNullOrEmpty(filePath)) return null; + if (String.IsNullOrEmpty(filePath)) return null; - var dirSep = filePath.IndexOf(DirSep) != -1 - ? DirSep - : filePath.IndexOf(AltDirSep) != -1 ? AltDirSep : (char)0; + var dirSep = filePath.IndexOf(PclExport.Instance.DirSep) != -1 + ? PclExport.Instance.DirSep + : filePath.IndexOf(PclExport.Instance.AltDirSep) != -1 + ? PclExport.Instance.AltDirSep + : (char)0; return dirSep == 0 ? null : filePath.TrimEnd(dirSep).SplitOnLast(dirSep)[0]; } @@ -419,71 +517,146 @@ public static string ToJsv(this T obj) return TypeSerializer.SerializeToString(obj); } + public static string ToJsv(this T obj, Action configure) + { + var config = new Config(); + configure(config); + using (JsConfig.With(config)) + { + return ToJsv(obj); + } + } + + public static string ToSafeJsv(this T obj) + { + return TypeSerializer.HasCircularReferences(obj) + ? obj.ToSafePartialObjectDictionary().ToJsv() + : obj.ToJsv(); + } + public static T FromJsv(this string jsv) { return TypeSerializer.DeserializeFromString(jsv); } - public static string ToJson(this T obj) { - return JsConfig.PreferInterfaces - ? JsonSerializer.SerializeToString(obj, AssemblyUtils.MainInterface()) - : JsonSerializer.SerializeToString(obj); + public static T FromJsvSpan(this ReadOnlySpan jsv) + { + return TypeSerializer.DeserializeFromSpan(jsv); + } + + public static string ToJson(this T obj, Action configure) + { + var config = new Config(); + configure(config); + using (JsConfig.With(config)) + { + return ToJson(obj); + } + } + + public static string ToJson(this T obj) + { + return JsConfig.PreferInterfaces + ? JsonSerializer.SerializeToString(obj, AssemblyUtils.MainInterface()) + : JsonSerializer.SerializeToString(obj); + } + + public static string ToSafeJson(this T obj) + { + return TypeSerializer.HasCircularReferences(obj) + ? obj.ToSafePartialObjectDictionary().ToJson() + : obj.ToJson(); } - public static T FromJson(this string json) + public static T FromJson(this string json) { return JsonSerializer.DeserializeFromString(json); } -#if !XBOX && !SILVERLIGHT && !MONOTOUCH - public static string ToXml(this T obj) + public static T FromJsonSpan(this ReadOnlySpan json) { - return XmlSerializer.SerializeToString(obj); + return JsonSerializer.DeserializeFromSpan(json); } -#endif -#if !XBOX && !SILVERLIGHT && !MONOTOUCH - public static T FromXml(this string json) + public static string ToCsv(this T obj) { - return XmlSerializer.DeserializeFromString(json); + return CsvSerializer.SerializeToString(obj); } -#endif + + public static string ToCsv(this T obj, Action configure) + { + var config = new Config(); + configure(config); + using (JsConfig.With(config)) + { + return ToCsv(obj); + } + } + + public static T FromCsv(this string csv) + { + return CsvSerializer.DeserializeFromString(csv); + } + public static string FormatWith(this string text, params object[] args) { - return string.Format(text, args); + return Format(text, args); } public static string Fmt(this string text, params object[] args) { - return string.Format(text, args); + return Format(text, args); + } + public static string Fmt(this string text, IFormatProvider provider, params object[] args) + { + return Format(provider, text, args); + } + + public static string Fmt(this string text, object arg1) + { + return Format(text, arg1); + } + + public static string Fmt(this string text, object arg1, object arg2) + { + return Format(text, arg1, arg2); + } + + public static string Fmt(this string text, object arg1, object arg2, object arg3) + { + return Format(text, arg1, arg2, arg3); } public static bool StartsWithIgnoreCase(this string text, string startsWith) { return text != null - && text.StartsWith(startsWith, StringComparison.InvariantCultureIgnoreCase); + && text.StartsWith(startsWith, PclExport.Instance.InvariantComparisonIgnoreCase); + } + + public static bool EndsWithIgnoreCase(this string text, string endsWith) + { + return text != null + && text.EndsWith(endsWith, PclExport.Instance.InvariantComparisonIgnoreCase); } public static string ReadAllText(this string filePath) { -#if XBOX && !SILVERLIGHT - using( var fileStream = new FileStream( filePath, FileMode.Open, FileAccess.Read ) ) - { - return new StreamReader( fileStream ).ReadToEnd( ) ; - } + return PclExport.Instance.ReadAllText(filePath); + } + + public static bool FileExists(this string filePath) + { + return PclExport.Instance.FileExists(filePath); + } -#elif WINDOWS_PHONE - using (var isoStore = IsolatedStorageFile.GetUserStoreForApplication()) - { - using (var fileStream = isoStore.OpenFile(filePath, FileMode.Open)) - { - return new StreamReader(fileStream).ReadToEnd(); - } - } -#else - return File.ReadAllText(filePath); -#endif + public static bool DirectoryExists(this string dirPath) + { + return PclExport.Instance.DirectoryExists(dirPath); + } + public static void CreateDirectory(this string dirPath) + { + PclExport.Instance.CreateDirectory(dirPath); } public static int IndexOfAny(this string text, params string[] needles) @@ -493,14 +666,17 @@ public static int IndexOfAny(this string text, params string[] needles) public static int IndexOfAny(this string text, int startIndex, params string[] needles) { - if (text == null) return -1; - var firstPos = -1; - foreach (var needle in needles) + if (text != null) { - var pos = text.IndexOf(needle); - if (firstPos == -1 || pos < firstPos) firstPos = pos; + foreach (var needle in needles) + { + var pos = text.IndexOf(needle, startIndex, StringComparison.Ordinal); + if (pos >= 0 && (firstPos == -1 || pos < firstPos)) + firstPos = pos; + } } + return firstPos; } @@ -511,48 +687,59 @@ public static string ExtractContents(this string fromText, string startAfter, st public static string ExtractContents(this string fromText, string uniqueMarker, string startAfter, string endAt) { - if (string.IsNullOrEmpty(uniqueMarker)) - throw new ArgumentNullException("uniqueMarker"); - if (string.IsNullOrEmpty(startAfter)) - throw new ArgumentNullException("startAfter"); - if (string.IsNullOrEmpty(endAt)) - throw new ArgumentNullException("endAt"); + if (String.IsNullOrEmpty(uniqueMarker)) + throw new ArgumentNullException(nameof(uniqueMarker)); + if (String.IsNullOrEmpty(startAfter)) + throw new ArgumentNullException(nameof(startAfter)); + if (String.IsNullOrEmpty(endAt)) + throw new ArgumentNullException(nameof(endAt)); - if (string.IsNullOrEmpty(fromText)) return null; + if (String.IsNullOrEmpty(fromText)) return null; - var markerPos = fromText.IndexOf(uniqueMarker); + var markerPos = fromText.IndexOf(uniqueMarker, StringComparison.Ordinal); if (markerPos == -1) return null; - var startPos = fromText.IndexOf(startAfter, markerPos); + var startPos = fromText.IndexOf(startAfter, markerPos, StringComparison.Ordinal); if (startPos == -1) return null; startPos += startAfter.Length; - var endPos = fromText.IndexOf(endAt, startPos); + var endPos = fromText.IndexOf(endAt, startPos, StringComparison.Ordinal); if (endPos == -1) endPos = fromText.Length; return fromText.Substring(startPos, endPos - startPos); } -#if XBOX && !SILVERLIGHT - static readonly Regex StripHtmlRegEx = new Regex(@"<(.|\n)*?>", RegexOptions.Compiled); -#else - static readonly Regex StripHtmlRegEx = new Regex(@"<(.|\n)*?>"); -#endif + static readonly Regex StripHtmlRegEx = new Regex(@"<(.|\n)*?>", PclExport.Instance.RegexOptions); + public static string StripHtml(this string html) { - return string.IsNullOrEmpty(html) ? null : StripHtmlRegEx.Replace(html, ""); + return String.IsNullOrEmpty(html) ? null : StripHtmlRegEx.Replace(html, ""); } -#if XBOX && !SILVERLIGHT - static readonly Regex StripBracketsRegEx = new Regex(@"\[(.|\n)*?\]", RegexOptions.Compiled); - static readonly Regex StripBracesRegEx = new Regex(@"\((.|\n)*?\)", RegexOptions.Compiled); -#else - static readonly Regex StripBracketsRegEx = new Regex(@"\[(.|\n)*?\]"); - static readonly Regex StripBracesRegEx = new Regex(@"\((.|\n)*?\)"); -#endif + public static string Quoted(this string text) + { + return text == null || text.IndexOf('"') >= 0 + ? text + : '"' + text + '"'; + } + + public static string StripQuotes(this string text) + { + return string.IsNullOrEmpty(text) || text.Length < 2 + ? text + : (text[0] == '"' && text[text.Length - 1] == '"') || + (text[0] == '\'' && text[text.Length - 1] == '\'') || + (text[0] == '`' && text[text.Length - 1] == '`') + ? text.Substring(1, text.Length - 2) + : text; + } + + static readonly Regex StripBracketsRegEx = new Regex(@"\[(.|\n)*?\]", PclExport.Instance.RegexOptions); + static readonly Regex StripBracesRegEx = new Regex(@"\((.|\n)*?\)", PclExport.Instance.RegexOptions); + public static string StripMarkdownMarkup(this string markdown) { - if (string.IsNullOrEmpty(markdown)) return null; + if (String.IsNullOrEmpty(markdown)) return null; markdown = StripBracketsRegEx.Replace(markdown, ""); markdown = StripBracesRegEx.Replace(markdown, ""); markdown = markdown @@ -568,13 +755,15 @@ public static string StripMarkdownMarkup(this string markdown) private const int LowerCaseOffset = 'a' - 'A'; public static string ToCamelCase(this string value) { - if (string.IsNullOrEmpty(value)) return value; + if (string.IsNullOrEmpty(value)) + return value; var len = value.Length; var newValue = new char[len]; var firstPart = true; - for (var i = 0; i < len; ++i) { + for (var i = 0; i < len; ++i) + { var c0 = value[i]; var c1 = i < len - 1 ? value[i + 1] : 'A'; var c0isUpper = c0 >= 'A' && c0 <= 'Z'; @@ -591,37 +780,43 @@ public static string ToCamelCase(this string value) return new string(newValue); } - private static readonly TextInfo TextInfo = CultureInfo.InvariantCulture.TextInfo; - public static string ToTitleCase(this string value) + public static string ToPascalCase(this string value) { -#if SILVERLIGHT || __MonoCS__ - string[] words = value.Split('_'); + if (string.IsNullOrEmpty(value)) + return value; - for (int i = 0; i <= words.Length - 1; i++) + if (value.IndexOf('_') >= 0) { - if ((!object.ReferenceEquals(words[i], string.Empty))) + var parts = value.Split('_'); + var sb = StringBuilderThreadStatic.Allocate(); + foreach (var part in parts) { - string firstLetter = words[i].Substring(0, 1); - string rest = words[i].Substring(1); - string result = firstLetter.ToUpper() + rest.ToLower(); - words[i] = result; + if (string.IsNullOrEmpty(part)) + continue; + var str = part.ToCamelCase(); + sb.Append(char.ToUpper(str[0]) + str.SafeSubstring(1, str.Length)); } + return StringBuilderThreadStatic.ReturnAndFree(sb); } - return String.Join("", words); -#else - return TextInfo.ToTitleCase(value).Replace("_", string.Empty); -#endif + + var camelCase = value.ToCamelCase(); + return char.ToUpper(camelCase[0]) + camelCase.SafeSubstring(1, camelCase.Length); + } + + public static string ToTitleCase(this string value) + { + return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(value).Replace("_", String.Empty); } public static string ToLowercaseUnderscore(this string value) { - if (string.IsNullOrEmpty(value)) return value; + if (String.IsNullOrEmpty(value)) return value; value = value.ToCamelCase(); - - var sb = new StringBuilder(value.Length); - foreach (var t in value) + + var sb = StringBuilderThreadStatic.Allocate(); + foreach (char t in value) { - if (char.IsLower(t)) + if (char.IsDigit(t) || (char.IsLetter(t) && char.IsLower(t)) || t == '_') { sb.Append(t); } @@ -631,35 +826,502 @@ public static string ToLowercaseUnderscore(this string value) sb.Append(char.ToLower(t)); } } - return sb.ToString(); + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + + public static string ToLowerSafe(this string value) + { + return value?.ToLower(); } - public static string SafeSubstring(this string value, int length) + public static string ToUpperSafe(this string value) { - return string.IsNullOrEmpty(value) - ? string.Empty - : value.Substring(Math.Min(length, value.Length)); + return value?.ToUpper(); + } + + public static string SafeSubstring(this string value, int startIndex) + { + if (String.IsNullOrEmpty(value)) return Empty; + return SafeSubstring(value, startIndex, value.Length); } public static string SafeSubstring(this string value, int startIndex, int length) { - if (string.IsNullOrEmpty(value)) return string.Empty; + if (String.IsNullOrEmpty(value) || length <= 0) return Empty; + if (startIndex < 0) startIndex = 0; if (value.Length >= (startIndex + length)) return value.Substring(startIndex, length); - return value.Length > startIndex ? value.Substring(startIndex) : string.Empty; + return value.Length > startIndex ? value.Substring(startIndex) : Empty; + } + + [Obsolete("typo")] + public static string SubstringWithElipsis(this string value, int startIndex, int length) => SubstringWithEllipsis(value, startIndex, length); + + public static string SubstringWithEllipsis(this string value, int startIndex, int length) + { + var str = value.SafeSubstring(startIndex, length); + return str.Length == length + ? str + "..." + : str; } public static bool IsAnonymousType(this Type type) { if (type == null) - throw new ArgumentNullException("type"); + throw new ArgumentNullException(nameof(type)); + + return PclExport.Instance.IsAnonymousType(type); + } + + public static int CompareIgnoreCase(this string strA, string strB) + { + return Compare(strA, strB, PclExport.Instance.InvariantComparisonIgnoreCase); + } + + public static bool EndsWithInvariant(this string str, string endsWith) + { + return str.EndsWith(endsWith, PclExport.Instance.InvariantComparison); + } + + private static readonly Regex InvalidVarCharsRegex = new(@"[^A-Za-z0-9_]", RegexOptions.Compiled); + private static readonly Regex ValidVarCharsRegex = new(@"^[A-Za-z0-9_]+$", RegexOptions.Compiled); + private static readonly Regex InvalidVarRefCharsRegex = new(@"[^A-Za-z0-9._]", RegexOptions.Compiled); + private static readonly Regex ValidVarRefCharsRegex = new(@"^[A-Za-z0-9._]+$", RegexOptions.Compiled); + + private static readonly Regex SplitCamelCaseRegex = new("([A-Z]|[0-9]+)", RegexOptions.Compiled); + private static readonly Regex HttpRegex = new(@"^http://", + PclExport.Instance.RegexOptions | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + + public static T ToEnum(this string value) + { + return (T)Enum.Parse(typeof(T), value, true); + } + + public static T ToEnumOrDefault(this string value, T defaultValue) + { + if (String.IsNullOrEmpty(value)) return defaultValue; + return (T)Enum.Parse(typeof(T), value, true); + } + + public static string SplitCamelCase(this string value) + { + return SplitCamelCaseRegex.Replace(value, " $1").TrimStart(); + } + + public static string ToInvariantUpper(this char value) + { + return PclExport.Instance.ToInvariantUpper(value); + } + + public static string ToEnglish(this string camelCase) + { + var ucWords = camelCase.SplitCamelCase().ToLower(); + return ucWords[0].ToInvariantUpper() + ucWords.Substring(1); + } + + public static string ToHttps(this string url) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + return HttpRegex.Replace(url.Trim(), "https://"); + } + + public static bool IsEmpty(this string value) + { + return String.IsNullOrEmpty(value); + } + + public static bool IsNullOrEmpty(this string value) + { + return String.IsNullOrEmpty(value); + } + + public static bool EqualsIgnoreCase(this string value, string other) + { + return String.Equals(value, other, StringComparison.OrdinalIgnoreCase); + } + + public static string ReplaceFirst(this string haystack, string needle, string replacement) + { + var pos = haystack.IndexOf(needle, StringComparison.Ordinal); + if (pos < 0) return haystack; + + return haystack.Substring(0, pos) + replacement + haystack.Substring(pos + needle.Length); + } + + [Obsolete("Use built-in string.Replace()")] + public static string ReplaceAll(this string haystack, string needle, string replacement) => + haystack.Replace(needle, replacement); + + public static bool ContainsAny(this string text, params string[] testMatches) + { + foreach (var testMatch in testMatches) + { + if (text.Contains(testMatch)) return true; + } + return false; + } + + public static bool ContainsAny(this string text, string[] testMatches, StringComparison comparisonType) + { + foreach (var testMatch in testMatches) + { + if (text.IndexOf(testMatch, comparisonType) >= 0) return true; + } + return false; + } + + public static bool IsValidVarName(this string name) => ValidVarCharsRegex.IsMatch(name); + public static bool IsValidVarRef(this string name) => ValidVarRefCharsRegex.IsMatch(name); + + public static string SafeVarName(this string text) => !string.IsNullOrEmpty(text) + ? InvalidVarCharsRegex.Replace(text, "_") : null; + + public static string SafeVarRef(this string text) => !string.IsNullOrEmpty(text) + ? InvalidVarRefCharsRegex.Replace(text, "_") : null; + + public static string Join(this List items) + { + return string.Join(JsWriter.ItemSeperatorString, items.ToArray()); + } + + public static string Join(this List items, string delimeter) + { + return string.Join(delimeter, items.ToArray()); + } + + public static string ToParentPath(this string path) + { + var pos = path.LastIndexOf('/'); + if (pos == -1) return "/"; + + var parentPath = path.Substring(0, pos); + return parentPath; + } + + public static string RemoveCharFlags(this string text, bool[] charFlags) + { + if (text == null) return null; + + var copy = text.ToCharArray(); + var nonWsPos = 0; + + for (var i = 0; i < text.Length; i++) + { + var @char = text[i]; + if (@char < charFlags.Length && charFlags[@char]) continue; + copy[nonWsPos++] = @char; + } + + return new string(copy, 0, nonWsPos); + } + + public static string ToNullIfEmpty(this string text) + { + return string.IsNullOrEmpty(text) ? null : text; + } + + private static readonly char[] SystemTypeChars = { '<', '>', '+' }; + + public static bool IsUserType(this Type type) + { + return type.IsClass + && !type.IsSystemType(); + } + + public static bool IsUserEnum(this Type type) + { + return type.IsEnum + && !type.IsSystemType(); + } + + public static bool IsSystemType(this Type type) + { + return type.Namespace == null + || type.Namespace.StartsWith("System") + || type.Name.IndexOfAny(SystemTypeChars) >= 0; + } + + public static bool IsTuple(this Type type) => type.Name.StartsWith("Tuple`"); + + public static bool IsInt(this string text) => !string.IsNullOrEmpty(text) && int.TryParse(text, out _); + + public static int ToInt(this string text) => text == null ? default(int) : int.Parse(text); + + public static int ToInt(this string text, int defaultValue) => int.TryParse(text, out var ret) ? ret : defaultValue; + + public static long ToLong(this string text) => long.Parse(text); + public static long ToInt64(this string text) => long.Parse(text); + + public static long ToLong(this string text, long defaultValue) => long.TryParse(text, out var ret) ? ret : defaultValue; + public static long ToInt64(this string text, long defaultValue) => long.TryParse(text, out var ret) ? ret : defaultValue; + + public static float ToFloat(this string text) => text == null ? default(float) : float.Parse(text); + + public static float ToFloatInvariant(this string text) => text == null ? default(float) : float.Parse(text, CultureInfo.InvariantCulture); + + public static float ToFloat(this string text, float defaultValue) => float.TryParse(text, out var ret) ? ret : defaultValue; + + public static double ToDouble(this string text) => text == null ? default(double) : double.Parse(text); + + public static double ToDoubleInvariant(this string text) => text == null ? default(double) : double.Parse(text, CultureInfo.InvariantCulture); + + public static double ToDouble(this string text, double defaultValue) => double.TryParse(text, out var ret) ? ret : defaultValue; + + public static decimal ToDecimal(this string text) => text == null ? default(decimal) : decimal.Parse(text); + + public static decimal ToDecimalInvariant(this string text) => text == null ? default(decimal) : decimal.Parse(text, CultureInfo.InvariantCulture); + + public static decimal ToDecimal(this string text, decimal defaultValue) => decimal.TryParse(text, out var ret) ? ret : defaultValue; + + public static bool Matches(this string value, string pattern) => value.Glob(pattern); + + public static bool Glob(this string value, string pattern) + { + int pos; + for (pos = 0; pattern.Length != pos; pos++) + { + switch (pattern[pos]) + { + case '?': + break; + + case '*': + for (int i = value.Length; i >= pos; i--) + { + if (Glob(value.Substring(i), pattern.Substring(pos + 1))) + return true; + } + return false; + + default: + if (value.Length == pos || char.ToUpper(pattern[pos]) != char.ToUpper(value[pos])) + { + return false; + } + break; + } + } + + return value.Length == pos; + } + + public static bool GlobPath(this string filePath, string pattern) + { + if (string.IsNullOrEmpty(filePath) || string.IsNullOrEmpty(pattern)) + return false; - // HACK: The only way to detect anonymous types right now. - return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) - && type.IsGenericType && type.Name.Contains("AnonymousType") - && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) - && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; + var sanitizedPath = filePath.Replace('\\','/'); + if (sanitizedPath[0] == '/') + sanitizedPath = sanitizedPath.Substring(1); + var sanitizedPattern = pattern.Replace('\\', '/'); + if (sanitizedPattern[0] == '/') + sanitizedPattern = sanitizedPattern.Substring(1); + + if (sanitizedPattern.IndexOf('*') == -1 && sanitizedPattern.IndexOf('?') == -1) + return sanitizedPath == sanitizedPattern; + + var patternParts = sanitizedPattern.SplitOnLast('/'); + var parts = sanitizedPath.SplitOnLast('/'); + if (parts.Length == 1) + return parts[0].Glob(pattern); + + var dirPart = parts[0]; + var filePart = parts[1]; + if (patternParts.Length == 1) + return filePart.Glob(patternParts[0]); + + var dirPattern = patternParts[0]; + var filePattern = patternParts[1]; + + if (dirPattern.IndexOf("**", StringComparison.Ordinal) >= 0) + { + if (!sanitizedPath.StartsWith(dirPattern.LeftPart("**").TrimEnd('*', '/'))) + return false; + } + else if (dirPattern.IndexOf('*') >= 0 || dirPattern.IndexOf('?') >= 0) + { + var regex = new Regex( + "^" + Regex.Escape(dirPattern).Replace(@"\*", "[^\\/]*").Replace(@"\?", ".") + "$" + ); + if (!regex.IsMatch(dirPart)) + return false; + } + else + { + if (dirPart != dirPattern) + return false; + } + + return filePart.Glob(filePattern); + } + + public static string TrimPrefixes(this string fromString, params string[] prefixes) + { + if (string.IsNullOrEmpty(fromString)) + return fromString; + + foreach (var prefix in prefixes) + { + if (fromString.StartsWith(prefix)) + return fromString.Substring(prefix.Length); + } + + return fromString; + } + + public static string FromAsciiBytes(this byte[] bytes) + { + return bytes == null ? null + : PclExport.Instance.GetAsciiString(bytes); + } + + public static byte[] ToAsciiBytes(this string value) + { + return PclExport.Instance.GetAsciiBytes(value); + } + + public static Dictionary ParseKeyValueText(this string text, string delimiter=" ") + { + var to = new Dictionary(); + if (text == null) return to; + + foreach (var parts in text.ReadLines().Select(line => line.Trim().SplitOnFirst(delimiter))) + { + var key = parts[0].Trim(); + if (key.Length == 0 || key.StartsWith("#")) continue; + to[key] = parts.Length == 2 ? parts[1].Trim() : null; + } + + return to; + } + + public static List> ParseAsKeyValues(this string text, string delimiter=" ") + { + var to = new List>(); + if (text == null) return to; + + foreach (var parts in text.ReadLines().Select(line => line.Trim().SplitOnFirst(delimiter))) + { + var key = parts[0].Trim(); + if (key.Length == 0 || key.StartsWith("#")) continue; + to.Add(new KeyValuePair(key, parts.Length == 2 ? parts[1].Trim() : null)); + } + + return to; + } + + public static IEnumerable ReadLines(this string text) + { + string line; + var reader = new StringReader(text ?? ""); + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } + + public static int CountOccurrencesOf(this string text, char needle) => + text.AsSpan().CountOccurrencesOf(needle); + + public static string NormalizeNewLines(this string text) + { + return text?.Replace("\r\n", "\n").Trim(); + } + +#if !LITE + public static string HexEscape(this string text, params char[] anyCharOf) + { + if (string.IsNullOrEmpty(text)) return text; + if (anyCharOf == null || anyCharOf.Length == 0) return text; + + var encodeCharMap = new HashSet(anyCharOf); + + var sb = StringBuilderThreadStatic.Allocate(); + var textLength = text.Length; + for (var i = 0; i < textLength; i++) + { + var c = text[i]; + if (encodeCharMap.Contains(c)) + { + sb.Append('%' + ((int)c).ToString("x")); + } + else + { + sb.Append(c); + } + } + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + + public static string ToXml(this T obj) + { + return XmlSerializer.SerializeToString(obj); + } + + public static T FromXml(this string json) + { + return XmlSerializer.DeserializeFromString(json); + } +#endif + + public static string ToHex(this byte[] hashBytes, bool upper=false) + { + var len = hashBytes.Length * 2; + var chars = new char[len]; + var i = 0; + var index = 0; + for (i = 0; i < len; i += 2) + { + var b = hashBytes[index++]; + chars[i] = GetHexValue(b / 16, upper); + chars[i + 1] = GetHexValue(b % 16, upper); + } + return new string(chars); + } + + private static char GetHexValue(int i, bool upper) + { + if (i < 0 || i > 15) + throw new ArgumentOutOfRangeException(nameof(i), "must be between 0 and 15"); + + return i < 10 + ? (char) (i + '0') + : (char) (i - 10 + (upper ? 'A' : 'a')); + } + + } +} + +namespace ServiceStack.Text +{ + public static class StringTextExtensions + { + [Obsolete("Use ConvertTo")] + public static T To(this string value) + { + return TypeSerializer.DeserializeFromString(value); + } + + [Obsolete("Use ConvertTo")] + public static T To(this string value, T defaultValue) + { + return String.IsNullOrEmpty(value) ? defaultValue : TypeSerializer.DeserializeFromString(value); + } + + [Obsolete("Use ConvertTo")] + public static T ToOrDefaultValue(this string value) + { + return String.IsNullOrEmpty(value) ? default(T) : TypeSerializer.DeserializeFromString(value); + } + + [Obsolete("Use ConvertTo")] + public static object To(this string value, Type type) + { + return TypeSerializer.DeserializeFromString(value, type); } } -} \ No newline at end of file +} diff --git a/src/ServiceStack.Text/StringSpanExtensions.cs b/src/ServiceStack.Text/StringSpanExtensions.cs new file mode 100644 index 000000000..36a200900 --- /dev/null +++ b/src/ServiceStack.Text/StringSpanExtensions.cs @@ -0,0 +1,686 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Text +{ + /// + /// Helpful extensions on ReadOnlySpan<char> + /// Previous extensions on StringSegment available from: https://gist.github.com/mythz/9825689f0db7464d1d541cb62954614c + /// + public static class StringSpanExtensions + { + /// + /// Returns null if Length == 0, string.Empty if value[0] == NonWidthWhitespace, otherwise returns value.ToString() + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Value(this ReadOnlySpan value) => value.IsEmpty + ? null + : value.Length == 1 && value[0] == TypeConstants.NonWidthWhiteSpace + ? "" + : value.ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static object Value(this object obj) => + obj is string value && value.Length == 1 && value[0] == TypeConstants.NonWidthWhiteSpace + ? "" + : obj; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullOrEmpty(this ReadOnlySpan value) => value.IsEmpty || (value.Length == 1 && value[0] == TypeConstants.NonWidthWhiteSpace); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullOrWhiteSpace(this ReadOnlySpan value) => value.IsNullOrEmpty() || value.IsWhiteSpace(); + + [Obsolete("Use value[index]")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char GetChar(this ReadOnlySpan value, int index) => value[index]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Substring(this ReadOnlySpan value, int pos) => value.Slice(pos, value.Length - pos).ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Substring(this ReadOnlySpan value, int pos, int length) => value.Slice(pos, length).ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CompareIgnoreCase(this ReadOnlySpan value, ReadOnlySpan text) => value.Equals(text, StringComparison.OrdinalIgnoreCase); + + public static ReadOnlySpan FromCsvField(this ReadOnlySpan text) + { + //TODO replace with native Replace() when exists + if (text.IsNullOrEmpty()) + return text; + + var delim = CsvConfig.ItemDelimiterString; + if (delim.Length == 1) + { + if (text[0] != delim[0]) + return text; + } + else if (!text.StartsWith(delim.AsSpan(), StringComparison.Ordinal)) + { + return text; + } + + var ret = text.Slice(CsvConfig.ItemDelimiterString.Length, text.Length - CsvConfig.EscapedItemDelimiterString.Length) + .ToString().Replace(CsvConfig.EscapedItemDelimiterString, CsvConfig.ItemDelimiterString); + + if (ret == string.Empty) + return TypeConstants.EmptyStringSpan; + + return ret.AsSpan(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ParseBoolean(this ReadOnlySpan value) + { + //Lots of kids like to use '1', HTML checkboxes use 'on' as a soft convention + switch (value.Length) + { + case 0: + return false; + case 1: + switch (value[0]) + { + case '1': + case 't': + case 'T': + case 'y': + case 'Y': + return true; + case '0': + case 'f': + case 'F': + case 'n': + case 'N': + return false; + } + break; + case 2: + if (value[0] == 'o' && value[1] == 'n') + return true; + break; + case 3: + if (value[0] == 'o' && value[1] == 'f' && value[1] == 'f') + return false; + break; + } + + return MemoryProvider.Instance.ParseBoolean(value); + } + + public static bool TryParseBoolean(this ReadOnlySpan value, out bool result) => + MemoryProvider.Instance.TryParseBoolean(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseDecimal(this ReadOnlySpan value, out decimal result) => + MemoryProvider.Instance.TryParseDecimal(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseFloat(this ReadOnlySpan value, out float result) => + MemoryProvider.Instance.TryParseFloat(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseDouble(this ReadOnlySpan value, out double result) => + MemoryProvider.Instance.TryParseDouble(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static decimal ParseDecimal(this ReadOnlySpan value) => + MemoryProvider.Instance.ParseDecimal(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static decimal ParseDecimal(this ReadOnlySpan value, bool allowThousands) => + MemoryProvider.Instance.ParseDecimal(value, allowThousands); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ParseFloat(this ReadOnlySpan value) => + MemoryProvider.Instance.ParseFloat(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ParseDouble(this ReadOnlySpan value) => + MemoryProvider.Instance.ParseDouble(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte ParseSByte(this ReadOnlySpan value) => + SignedInteger.ParseSByte(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ParseByte(this ReadOnlySpan value) => + UnsignedInteger.ParseByte(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ParseInt16(this ReadOnlySpan value) => + SignedInteger.ParseInt16(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ParseUInt16(this ReadOnlySpan value) => + UnsignedInteger.ParseUInt16(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ParseInt32(this ReadOnlySpan value) => + SignedInteger.ParseInt32(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ParseUInt32(this ReadOnlySpan value) => + UnsignedInteger.ParseUInt32(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ParseInt64(this ReadOnlySpan value) => + SignedInteger.ParseInt64(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ParseUInt64(this ReadOnlySpan value) => + UnsignedInteger.ParseUInt64(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Guid ParseGuid(this ReadOnlySpan value) => + DefaultMemory.Provider.ParseGuid(value); + + public static object ParseSignedInteger(this ReadOnlySpan value) + { + var longValue = ParseInt64(value); + if (longValue >= int.MinValue && longValue <= int.MaxValue) + return (int)longValue; + return longValue; + } + + public static bool TryReadLine(this ReadOnlySpan text, out ReadOnlySpan line, ref int startIndex) + { + if (startIndex >= text.Length) + { + line = TypeConstants.NullStringSpan; + return false; + } + + text = text.Slice(startIndex); + + var nextLinePos = text.IndexOfAny('\r', '\n'); + if (nextLinePos == -1) + { + var nextLine = text.Slice(0, text.Length); + startIndex += text.Length; + line = nextLine; + return true; + } + else + { + var nextLine = text.Slice(0, nextLinePos); + + startIndex += nextLinePos + 1; + + if (text[nextLinePos] == '\r' && text.Length > nextLinePos + 1 && text[nextLinePos + 1] == '\n') + startIndex += 1; + + line = nextLine; + return true; + } + } + + public static bool TryReadPart(this ReadOnlySpan text, string needle, out ReadOnlySpan part, ref int startIndex) + { + if (startIndex >= text.Length) + { + part = TypeConstants.NullStringSpan; + return false; + } + + text = text.Slice(startIndex); + var nextPartPos = text.IndexOf(needle); + if (nextPartPos == -1) + { + var nextPart = text.Slice(0, text.Length); + startIndex += text.Length; + part = nextPart; + return true; + } + else + { + var nextPart = text.Slice(0, nextPartPos); + startIndex += nextPartPos + needle.Length; + part = nextPart; + return true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan Advance(this ReadOnlySpan text, int to) => text.Slice(to, text.Length - to); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AdvancePastWhitespace(this ReadOnlySpan literal) + { + var i = 0; + while (i < literal.Length && char.IsWhiteSpace(literal[i])) + i++; + + return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length); + } + + public static ReadOnlySpan AdvancePastChar(this ReadOnlySpan literal, char delim) + { + var i = 0; + var c = (char) 0; + while (i < literal.Length && (c = literal[i]) != delim) + i++; + + if (c == delim) + return literal.Slice(i + 1); + + return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length); + } + + [Obsolete("Use Slice()")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan Subsegment(this ReadOnlySpan text, int startPos) => text.Slice(startPos, text.Length - startPos); + + [Obsolete("Use Slice()")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan Subsegment(this ReadOnlySpan text, int startPos, int length) => text.Slice(startPos, length); + + public static ReadOnlySpan LeftPart(this ReadOnlySpan strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlySpan LeftPart(this ReadOnlySpan strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlySpan RightPart(this ReadOnlySpan strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + 1); + } + + public static ReadOnlySpan RightPart(this ReadOnlySpan strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + needle.Length); + } + + public static ReadOnlySpan LastLeftPart(this ReadOnlySpan strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlySpan LastLeftPart(this ReadOnlySpan strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlySpan LastRightPart(this ReadOnlySpan strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + 1); + } + + public static ReadOnlySpan LastRightPart(this ReadOnlySpan strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + needle.Length); + } + + public static void SplitOnFirst(this ReadOnlySpan strVal, char needle, out ReadOnlySpan first, out ReadOnlySpan last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.IndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + 1); + } + } + + public static void SplitOnFirst(this ReadOnlySpan strVal, string needle, out ReadOnlySpan first, out ReadOnlySpan last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.IndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + needle.Length); + } + } + + public static void SplitOnLast(this ReadOnlySpan strVal, char needle, out ReadOnlySpan first, out ReadOnlySpan last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.LastIndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + 1); + } + } + + public static void SplitOnLast(this ReadOnlySpan strVal, string needle, out ReadOnlySpan first, out ReadOnlySpan last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.LastIndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + needle.Length); + } + } + + public static ReadOnlySpan WithoutExtension(this ReadOnlySpan filePath) + { + if (filePath.IsNullOrEmpty()) + return TypeConstants.NullStringSpan; + + var extPos = filePath.LastIndexOf('.'); + if (extPos == -1) return filePath; + + var dirPos = filePath.LastIndexOfAny(PclExport.DirSeps); + return extPos > dirPos ? filePath.Slice(0, extPos) : filePath; + } + + public static ReadOnlySpan GetExtension(this ReadOnlySpan filePath) + { + if (filePath.IsNullOrEmpty()) + return TypeConstants.NullStringSpan; + + var extPos = filePath.LastIndexOf('.'); + return extPos == -1 ? TypeConstants.NullStringSpan : filePath.Slice(extPos); + } + + public static ReadOnlySpan ParentDirectory(this ReadOnlySpan filePath) + { + if (filePath.IsNullOrEmpty()) + return TypeConstants.NullStringSpan; + + var dirSep = filePath.IndexOf(PclExport.Instance.DirSep) != -1 + ? PclExport.Instance.DirSep + : filePath.IndexOf(PclExport.Instance.AltDirSep) != -1 + ? PclExport.Instance.AltDirSep + : (char)0; + + if (dirSep == 0) + return TypeConstants.NullStringSpan; + + MemoryExtensions.TrimEnd(filePath, dirSep).SplitOnLast(dirSep, out var first, out _); + return first; + } + + public static ReadOnlySpan TrimEnd(this ReadOnlySpan value, params char[] trimChars) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + if (trimChars == null || trimChars.Length == 0) + return value.TrimHelper(1); + return value.TrimHelper(trimChars, 1); + } + + private static ReadOnlySpan TrimHelper(this ReadOnlySpan value, int trimType) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + int end = value.Length - 1; + int start = 0; + if (trimType != 1) + { + start = 0; + while (start < value.Length && char.IsWhiteSpace(value[start])) + ++start; + } + if (trimType != 0) + { + end = value.Length - 1; + while (end >= start && char.IsWhiteSpace(value[end])) + --end; + } + return value.CreateTrimmedString(start, end); + } + + private static ReadOnlySpan TrimHelper(this ReadOnlySpan value, char[] trimChars, int trimType) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + int end = value.Length - 1; + int start = 0; + if (trimType != 1) + { + for (start = 0; start < value.Length; ++start) + { + char ch = value[start]; + int index = 0; + while (index < trimChars.Length && (int)trimChars[index] != (int)ch) + ++index; + if (index == trimChars.Length) + break; + } + } + if (trimType != 0) + { + for (end = value.Length - 1; end >= start; --end) + { + char ch = value[end]; + int index = 0; + while (index < trimChars.Length && (int)trimChars[index] != (int)ch) + ++index; + if (index == trimChars.Length) + break; + } + } + return value.CreateTrimmedString(start, end); + } + + private static ReadOnlySpan CreateTrimmedString(this ReadOnlySpan value, int start, int end) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + int length = end - start + 1; + if (length == value.Length) + return value; + if (length == 0) + return TypeConstants.NullStringSpan; + return value.Slice(start, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan SafeSlice(this ReadOnlySpan value, int startIndex) => SafeSlice(value, startIndex, value.Length); + + public static ReadOnlySpan SafeSlice(this ReadOnlySpan value, int startIndex, int length) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + if (startIndex < 0) startIndex = 0; + if (value.Length >= startIndex + length) + return value.Slice(startIndex, length); + + return value.Length > startIndex ? value.Slice(startIndex) : TypeConstants.NullStringSpan; + } + + public static string SubstringWithEllipsis(this ReadOnlySpan value, int startIndex, int length) + { + if (value.IsEmpty) return string.Empty; + var str = value.SafeSlice(startIndex, length); + return str.Length == length + ? str.ToString() + "..." + : str.ToString(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlySpan value, string other) => value.IndexOf(other.AsSpan()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlySpan value, string needle, int start) + { + var pos = value.Slice(start).IndexOf(needle.AsSpan()); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlySpan value, string other) => value.LastIndexOf(other.AsSpan()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlySpan value, string needle, int start) + { + var pos = value.Slice(start).LastIndexOf(needle.AsSpan()); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualTo(this ReadOnlySpan value, string other) => value.Equals(other.AsSpan(), StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualTo(this ReadOnlySpan value, ReadOnlySpan other) => value.Equals(other, StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsOrdinal(this ReadOnlySpan value, string other) => value.Equals(other.AsSpan(), StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlySpan value, string other) => value.StartsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlySpan value, string other, StringComparison comparison) => value.StartsWith(other.AsSpan(), comparison); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWith(this ReadOnlySpan value, string other, StringComparison comparison) => value.EndsWith(other.AsSpan(), comparison); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWith(this ReadOnlySpan value, string other) => value.EndsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsIgnoreCase(this ReadOnlySpan value, ReadOnlySpan other) => value.Equals(other, StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWithIgnoreCase(this ReadOnlySpan value, ReadOnlySpan other) => value.StartsWith(other, StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWithIgnoreCase(this ReadOnlySpan value, ReadOnlySpan other) => value.EndsWith(other, StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task WriteAsync(this Stream stream, ReadOnlySpan value, CancellationToken token = default(CancellationToken)) => + MemoryProvider.Instance.WriteAsync(stream, value, token); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan SafeSubstring(this ReadOnlySpan value, int startIndex) => SafeSubstring(value, startIndex, value.Length); + + public static ReadOnlySpan SafeSubstring(this ReadOnlySpan value, int startIndex, int length) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + if (startIndex < 0) startIndex = 0; + if (value.Length >= (startIndex + length)) + return value.Slice(startIndex, length); + + return value.Length > startIndex ? value.Slice(startIndex) : TypeConstants.NullStringSpan; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static StringBuilder Append(this StringBuilder sb, ReadOnlySpan value) => + MemoryProvider.Instance.Append(sb, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] ParseBase64(this ReadOnlySpan value) => MemoryProvider.Instance.ParseBase64(value); + + public static ReadOnlyMemory ToUtf8(this ReadOnlySpan value) => + MemoryProvider.Instance.ToUtf8(value); + + public static ReadOnlyMemory FromUtf8(this ReadOnlySpan value) => + MemoryProvider.Instance.FromUtf8(value); + + public static byte[] ToUtf8Bytes(this ReadOnlySpan value) => + MemoryProvider.Instance.ToUtf8Bytes(value); + + public static string FromUtf8Bytes(this ReadOnlySpan value) => + MemoryProvider.Instance.FromUtf8Bytes(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List ToStringList(this IEnumerable> from) + { + var to = new List(); + if (from != null) + { + foreach (var item in from) + { + to.Add(item.ToString()); + } + } + return to; + } + + public static int CountOccurrencesOf(this ReadOnlySpan value, char needle) + { + var count = 0; + var length = value.Length; + for (var n = length - 1; n >= 0; n--) + { + if (value[n] == needle) + count++; + } + return count; + } + + public static ReadOnlySpan WithoutBom(this ReadOnlySpan value) + { + return value.Length > 0 && value[0] == 65279 + ? value.Slice(1) + : value; + } + + public static ReadOnlySpan WithoutBom(this ReadOnlySpan value) + { + return value.Length > 3 && value[0] == 0xEF && value[1] == 0xBB && value[2] == 0xBF + ? value.Slice(3) + : value; + } + + } +} diff --git a/src/ServiceStack.Text/StringWriterCache.cs b/src/ServiceStack.Text/StringWriterCache.cs new file mode 100644 index 000000000..535223c48 --- /dev/null +++ b/src/ServiceStack.Text/StringWriterCache.cs @@ -0,0 +1,103 @@ +using System; +using System.Globalization; +using System.IO; + +namespace ServiceStack.Text +{ + /// + /// Reusable StringWriter ThreadStatic Cache + /// + public static class StringWriterCache + { + [ThreadStatic] + static StringWriter cache; + + public static StringWriter Allocate() + { + var ret = cache; + if (ret == null) + return new StringWriter(CultureInfo.InvariantCulture); + + var sb = ret.GetStringBuilder(); + sb.Length = 0; + cache = null; //don't re-issue cached instance until it's freed + return ret; + } + + public static void Free(StringWriter writer) + { + cache = writer; + } + + public static string ReturnAndFree(StringWriter writer) + { + var ret = writer.ToString(); + cache = writer; + return ret; + } + } + + /// + /// Alternative Reusable StringWriter ThreadStatic Cache + /// + public static class StringWriterCacheAlt + { + [ThreadStatic] + static StringWriter cache; + + public static StringWriter Allocate() + { + var ret = cache; + if (ret == null) + return new StringWriter(CultureInfo.InvariantCulture); + + var sb = ret.GetStringBuilder(); + sb.Length = 0; + cache = null; //don't re-issue cached instance until it's freed + return ret; + } + + public static void Free(StringWriter writer) + { + cache = writer; + } + + public static string ReturnAndFree(StringWriter writer) + { + var ret = writer.ToString(); + cache = writer; + return ret; + } + } + + //Use separate cache internally to avoid reallocations and cache misses + internal static class StringWriterThreadStatic + { + [ThreadStatic] + static StringWriter cache; + + public static StringWriter Allocate() + { + var ret = cache; + if (ret == null) + return new StringWriter(CultureInfo.InvariantCulture); + + var sb = ret.GetStringBuilder(); + sb.Length = 0; + cache = null; //don't re-issue cached instance until it's freed + return ret; + } + + public static void Free(StringWriter writer) + { + cache = writer; + } + + public static string ReturnAndFree(StringWriter writer) + { + var ret = writer.ToString(); + cache = writer; + return ret; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Support/DoubleConverter.cs b/src/ServiceStack.Text/Support/DoubleConverter.cs index e84ee80a4..583323e0f 100644 --- a/src/ServiceStack.Text/Support/DoubleConverter.cs +++ b/src/ServiceStack.Text/Support/DoubleConverter.cs @@ -21,9 +21,6 @@ public class DoubleConverter /// A string representation of the double's exact decimal value. public static string ToExactString(double d) { -#if XBOX - return BitConverter.ToString( BitConverter.GetBytes( d ) ) ; -#else if (double.IsPositiveInfinity(d)) return "+Infinity"; if (double.IsNegativeInfinity(d)) @@ -92,7 +89,6 @@ public static string ToExactString(double d) return "-" + ad.ToString(); else return ad.ToString(); -#endif } /// Private class used for manipulating diff --git a/src/ServiceStack.Text/Support/TimeSpanConverter.cs b/src/ServiceStack.Text/Support/TimeSpanConverter.cs new file mode 100644 index 000000000..940a9fcf8 --- /dev/null +++ b/src/ServiceStack.Text/Support/TimeSpanConverter.cs @@ -0,0 +1,127 @@ +using System; +using System.Globalization; +using System.Text; + +namespace ServiceStack.Text.Support +{ + public class TimeSpanConverter + { + private const string MinSerializedValue = "-P10675199DT2H48M5.4775391S"; + private const string MaxSerializedValue = "P10675199DT2H48M5.4775391S"; + + public static string ToXsdDuration(TimeSpan timeSpan) + { + if (timeSpan == TimeSpan.MinValue) + return MinSerializedValue; + if (timeSpan == TimeSpan.MaxValue) + return MaxSerializedValue; + + var sb = StringBuilderThreadStatic.Allocate(); + + sb.Append(timeSpan.Ticks < 0 ? "-P" : "P"); + + double ticks = timeSpan.Ticks; + if (ticks < 0) + ticks = -ticks; + + double totalSeconds = ticks / TimeSpan.TicksPerSecond; + long wholeSeconds = (long) totalSeconds; + long seconds = wholeSeconds; + long sec = (seconds >= 60 ? seconds % 60 : seconds); + long min = (seconds = (seconds / 60)) >= 60 ? seconds % 60 : seconds; + long hours = (seconds = (seconds / 60)) >= 24 ? seconds % 24 : seconds; + long days = seconds / 24; + double remainingSecs = sec + (totalSeconds - wholeSeconds); + + if (days > 0) + sb.Append(days + "D"); + + if (days == 0 || hours + min + sec + remainingSecs > 0) + { + sb.Append("T"); + if (hours > 0) + sb.Append(hours + "H"); + + if (min > 0) + sb.Append(min + "M"); + + if (remainingSecs > 0) + { + var secFmt = string.Format(CultureInfo.InvariantCulture, "{0:0.0000000}", remainingSecs); + secFmt = secFmt.TrimEnd('0').TrimEnd('.'); + sb.Append(secFmt + "S"); + } + else if (sb.Length == 2) //PT + { + sb.Append("0S"); + } + } + + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + + public static TimeSpan FromXsdDuration(string xsdDuration) + { + if (xsdDuration == MinSerializedValue) + return TimeSpan.MinValue; + if (xsdDuration == MaxSerializedValue) + return TimeSpan.MaxValue; + + long days = 0; + long hours = 0; + long minutes = 0; + decimal seconds = 0; + long sign = 1; + + if (xsdDuration.StartsWith("-", StringComparison.Ordinal)) + { + sign = -1; + xsdDuration = xsdDuration.Substring(1); //strip sign + } + + string[] t = xsdDuration.Substring(1).SplitOnFirst('T'); //strip P + + var hasTime = t.Length == 2; + + string[] d = t[0].SplitOnFirst('D'); + if (d.Length == 2) + { + if (long.TryParse(d[0], out var day)) + days = day; + } + if (hasTime) + { + string[] h = t[1].SplitOnFirst('H'); + if (h.Length == 2) + { + if (long.TryParse(h[0], out var hour)) + hours = hour; + } + + string[] m = h[h.Length - 1].SplitOnFirst('M'); + if (m.Length == 2) + { + if (long.TryParse(m[0], out var min)) + minutes = min; + } + + string[] s = m[m.Length - 1].SplitOnFirst('S'); + if (s.Length == 2) + { + if (decimal.TryParse(s[0], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var millis)) + seconds = millis; + } + } + + decimal totalSecs = 0 + + (days * 24 * 60 * 60) + + (hours * 60 * 60) + + (minutes * 60) + + (seconds); + + var interval = (long) (totalSecs * TimeSpan.TicksPerSecond * sign); + + return TimeSpan.FromTicks(interval); + } + } +} diff --git a/src/ServiceStack.Text/Support/TypePair.cs b/src/ServiceStack.Text/Support/TypePair.cs index 114819298..986f41b46 100644 --- a/src/ServiceStack.Text/Support/TypePair.cs +++ b/src/ServiceStack.Text/Support/TypePair.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; diff --git a/src/ServiceStack.Text/SystemTime.cs b/src/ServiceStack.Text/SystemTime.cs index 3c1e6365d..5a0f4c907 100644 --- a/src/ServiceStack.Text/SystemTime.cs +++ b/src/ServiceStack.Text/SystemTime.cs @@ -6,35 +6,35 @@ // Demis Bellot (demis.bellot@gmail.com) // Damian Hickey (dhickey@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; namespace ServiceStack.Text { - public static class SystemTime - { - public static Func UtcDateTimeResolver; + public static class SystemTime + { + public static Func UtcDateTimeResolver; - public static DateTime Now - { - get - { - var temp = UtcDateTimeResolver; - return temp == null ? DateTime.Now : temp().ToLocalTime(); - } - } + public static DateTime Now + { + get + { + var temp = UtcDateTimeResolver; + return temp == null ? DateTime.Now : temp().ToLocalTime(); + } + } - public static DateTime UtcNow - { - get - { - var temp = UtcDateTimeResolver; - return temp == null ? DateTime.UtcNow : temp(); - } - } - } + public static DateTime UtcNow + { + get + { + var temp = UtcDateTimeResolver; + return temp == null ? DateTime.UtcNow : temp(); + } + } + } } diff --git a/src/ServiceStack.Text/TaskExtensions.cs b/src/ServiceStack.Text/TaskExtensions.cs new file mode 100644 index 000000000..eae635c96 --- /dev/null +++ b/src/ServiceStack.Text/TaskExtensions.cs @@ -0,0 +1,96 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack +{ + public static class TaskExtensions + { + public static Task Success(this Task task, Action fn, + bool onUiThread = true, + TaskContinuationOptions taskOptions = TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.OnlyOnRanToCompletion) + { + if (onUiThread) + { + var source = new CancellationToken(); + task.ContinueWith(t => fn(t.Result), source, taskOptions, TaskScheduler.FromCurrentSynchronizationContext()); + } + else + { + task.ContinueWith(t => fn(t.Result), taskOptions); + } + return task; + } + + public static Task Success(this Task task, Action fn, + bool onUiThread = true, + TaskContinuationOptions taskOptions = TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.OnlyOnRanToCompletion) + { + if (onUiThread) + { + var source = new CancellationToken(); + task.ContinueWith(t => fn(), source, taskOptions, TaskScheduler.FromCurrentSynchronizationContext()); + } + else + { + task.ContinueWith(t => fn(), taskOptions); + } + return task; + } + + public static Task Error(this Task task, Action fn, + bool onUiThread = true, + TaskContinuationOptions taskOptions = TaskContinuationOptions.OnlyOnFaulted) + { + if (onUiThread) + { + var source = new CancellationToken(); + task.ContinueWith(t => fn(t.UnwrapIfSingleException()), source, taskOptions, TaskScheduler.FromCurrentSynchronizationContext()); + } + else + { + task.ContinueWith(t => fn(t.UnwrapIfSingleException()), taskOptions); + } + return task; + } + + public static Task Error(this Task task, Action fn, + bool onUiThread = true, + TaskContinuationOptions taskOptions = TaskContinuationOptions.OnlyOnFaulted) + { + if (onUiThread) + { + var source = new CancellationToken(); + task.ContinueWith(t => fn(t.UnwrapIfSingleException()), source, taskOptions, TaskScheduler.FromCurrentSynchronizationContext()); + } + else + { + task.ContinueWith(t => fn(t.UnwrapIfSingleException()), taskOptions); + } + return task; + } + + public static Exception UnwrapIfSingleException(this Task task) + { + return task.Exception.UnwrapIfSingleException(); + } + + public static Exception UnwrapIfSingleException(this Task task) + { + return task.Exception.UnwrapIfSingleException(); + } + + public static Exception UnwrapIfSingleException(this Exception ex) + { + var aex = ex as AggregateException; + if (aex == null) + return ex; + + if (aex.InnerExceptions != null + && aex.InnerExceptions.Count == 1) + return aex.InnerExceptions[0].UnwrapIfSingleException(); + + return aex; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/TaskResult.cs b/src/ServiceStack.Text/TaskResult.cs new file mode 100644 index 000000000..53b524114 --- /dev/null +++ b/src/ServiceStack.Text/TaskResult.cs @@ -0,0 +1,45 @@ +// Copyright (c) ServiceStack, Inc. All Rights Reserved. +// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System.Threading.Tasks; + +namespace ServiceStack +{ + public static class TaskResult + { + public static Task Zero; + public static Task One; + public static readonly Task True; + public static readonly Task False; + public static readonly Task Finished; + public static readonly Task Canceled; + + static TaskResult() + { + Finished = ((object)null).InTask(); + True = true.InTask(); + False = false.InTask(); + Zero = 0.InTask(); + One = 1.InTask(); + + var tcs = new TaskCompletionSource(); + tcs.SetCanceled(); + Canceled = tcs.Task; + } + } + + internal class TaskResult + { + public static readonly Task Canceled; + public static readonly Task Default; + + static TaskResult() + { + Default = ((T)typeof(T).GetDefaultValue()).InTask(); + + var tcs = new TaskCompletionSource(); + tcs.SetCanceled(); + Canceled = tcs.Task; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/TaskUtils.cs b/src/ServiceStack.Text/TaskUtils.cs new file mode 100644 index 000000000..cf5265f22 --- /dev/null +++ b/src/ServiceStack.Text/TaskUtils.cs @@ -0,0 +1,149 @@ +// Copyright (c) ServiceStack, Inc. All Rights Reserved. +// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack +{ + public static class TaskUtils + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task FromResult(T result) => Task.FromResult(result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task InTask(this T result) => Task.FromResult(result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task InTask(this Exception ex) + { + var taskSource = new TaskCompletionSource(); + taskSource.TrySetException(ex); + return taskSource.Task; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsSuccess(this Task task) => !task.IsFaulted && task.IsCompleted; + + public static Task Cast(this Task task) where To : From => task.Then(x => (To)x); + + public static TaskScheduler SafeTaskScheduler() + { + //Unit test runner + if (SynchronizationContext.Current != null) + return TaskScheduler.FromCurrentSynchronizationContext(); + + SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); + return TaskScheduler.FromCurrentSynchronizationContext(); + } + + public static Task Then(this Task task, Func fn) + { + var tcs = new TaskCompletionSource(); + task.ContinueWith(t => + { + if (t.IsFaulted) + tcs.TrySetException(t.Exception.InnerExceptions); + else if (t.IsCanceled) + tcs.TrySetCanceled(); + else + tcs.TrySetResult(fn(t.Result)); + }, TaskContinuationOptions.ExecuteSynchronously); + + return tcs.Task; + } + + public static Task Then(this Task task, Func fn) + { + var tcs = new TaskCompletionSource(); + task.ContinueWith(t => + { + if (t.IsFaulted) + tcs.TrySetException(t.Exception.InnerExceptions); + else if (t.IsCanceled) + tcs.TrySetCanceled(); + else + tcs.TrySetResult(fn(t)); + }, TaskContinuationOptions.ExecuteSynchronously); + + return tcs.Task; + } + + //http://stackoverflow.com/a/13904811/85785 + public static Task EachAsync(this IEnumerable items, Func fn) + { + var tcs = new TaskCompletionSource(); + + var enumerator = items.GetEnumerator(); + var i = 0; + + Action next = null; + next = t => + { + if (t.IsFaulted) + tcs.TrySetException(t.Exception.InnerExceptions); + else if (t.IsCanceled) + tcs.TrySetCanceled(); + else + StartNextIteration(tcs, fn, enumerator, ref i, next); + }; + + StartNextIteration(tcs, fn, enumerator, ref i, next); + + tcs.Task.ContinueWith(_ => enumerator.Dispose(), TaskContinuationOptions.ExecuteSynchronously); + + return tcs.Task; + } + + static void StartNextIteration(TaskCompletionSource tcs, + Func fn, + IEnumerator enumerator, + ref int i, + Action next) + { + bool moveNext; + try + { + moveNext = enumerator.MoveNext(); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + + if (!moveNext) + { + tcs.SetResult(null); + return; + } + + Task iterationTask = null; + try + { + iterationTask = fn(enumerator.Current, i); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + + i++; + + iterationTask?.ContinueWith(next, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + + public static void Sleep(int timeMs) + { + Thread.Sleep(timeMs); + } + + public static void Sleep(TimeSpan time) + { + Thread.Sleep(time); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/TextExtensions.cs b/src/ServiceStack.Text/TextExtensions.cs index 8496d8981..b16ddfbdd 100644 --- a/src/ServiceStack.Text/TextExtensions.cs +++ b/src/ServiceStack.Text/TextExtensions.cs @@ -5,76 +5,85 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; using System.Collections.Generic; -using System.Text; -using ServiceStack.Text.Common; +using ServiceStack.Text; -namespace ServiceStack.Text +namespace ServiceStack { - public static class TextExtensions - { + public static class TextExtensions + { public static string ToCsvField(this string text) { return string.IsNullOrEmpty(text) || !CsvWriter.HasAnyEscapeChars(text) - ? text - : string.Concat - ( - CsvConfig.ItemDelimiterString, - text.Replace(CsvConfig.ItemDelimiterString, CsvConfig.EscapedItemDelimiterString), - CsvConfig.ItemDelimiterString - ); + ? text + : string.Concat + ( + CsvConfig.ItemDelimiterString, + text.Replace(CsvConfig.ItemDelimiterString, CsvConfig.EscapedItemDelimiterString), + CsvConfig.ItemDelimiterString + ); } public static object ToCsvField(this object text) { - return text == null || !JsWriter.HasAnyEscapeChars(text.ToString()) - ? text - : string.Concat - ( - JsWriter.QuoteString, - text.ToString().Replace(JsWriter.QuoteString, TypeSerializer.DoubleQuoteString), - JsWriter.QuoteString - ); + var textSerialized = string.Empty; + if (text is string) + { + textSerialized = text.ToString(); + } + else + { + textSerialized = TypeSerializer.SerializeToString(text).StripQuotes(); + } + + return textSerialized.IsNullOrEmpty() || !CsvWriter.HasAnyEscapeChars(textSerialized) + ? textSerialized + : string.Concat + ( + CsvConfig.ItemDelimiterString, + textSerialized.Replace(CsvConfig.ItemDelimiterString, CsvConfig.EscapedItemDelimiterString), + CsvConfig.ItemDelimiterString + ); } - public static string FromCsvField(this string text) - { - return string.IsNullOrEmpty(text) || !text.StartsWith(CsvConfig.ItemDelimiterString) - ? text - : text.Substring(CsvConfig.ItemDelimiterString.Length, text.Length - CsvConfig.EscapedItemDelimiterString.Length) - .Replace(CsvConfig.EscapedItemDelimiterString, CsvConfig.ItemDelimiterString); - } + public static string FromCsvField(this string text) + { + return string.IsNullOrEmpty(text) || !text.StartsWith(CsvConfig.ItemDelimiterString, StringComparison.Ordinal) + ? text + : text.Substring(CsvConfig.ItemDelimiterString.Length, text.Length - CsvConfig.EscapedItemDelimiterString.Length) + .Replace(CsvConfig.EscapedItemDelimiterString, CsvConfig.ItemDelimiterString); + } - public static List FromCsvFields(this IEnumerable texts) - { - var safeTexts = new List(); - foreach (var text in texts) - { - safeTexts.Add(FromCsvField(text)); - } - return safeTexts; - } + public static List FromCsvFields(this IEnumerable texts) + { + var safeTexts = new List(); + foreach (var text in texts) + { + safeTexts.Add(FromCsvField(text)); + } + return safeTexts; + } - public static string[] FromCsvFields(params string[] texts) - { - var textsLen = texts.Length; - var safeTexts = new string[textsLen]; - for (var i = 0; i < textsLen; i++) - { - safeTexts[i] = FromCsvField(texts[i]); - } - return safeTexts; - } + public static string[] FromCsvFields(params string[] texts) + { + var textsLen = texts.Length; + var safeTexts = new string[textsLen]; + for (var i = 0; i < textsLen; i++) + { + safeTexts[i] = FromCsvField(texts[i]); + } + return safeTexts; + } - public static string SerializeToString(this T value) - { - return JsonSerializer.SerializeToString(value); - } - } + public static string SerializeToString(this T value) + { + return JsonSerializer.SerializeToString(value); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Tracer.cs b/src/ServiceStack.Text/Tracer.cs index 180276ec6..e9da684ea 100644 --- a/src/ServiceStack.Text/Tracer.cs +++ b/src/ServiceStack.Text/Tracer.cs @@ -2,64 +2,84 @@ namespace ServiceStack.Text { - public class Tracer - { - public static ITracer Instance = new NullTracer(); + public class Tracer + { + public static ITracer Instance = new NullTracer(); - public class NullTracer : ITracer - { - public void WriteDebug(string error) { } + public class NullTracer : ITracer + { + public void WriteDebug(string error) { } + + public void WriteDebug(string format, params object[] args) { } - public void WriteDebug(string format, params object[] args) { } - public void WriteWarning(string warning) { } - public void WriteWarning(string format, params object[] args) { } + public void WriteWarning(string format, params object[] args) { } + + public void WriteError(Exception ex) + { + if (JsConfig.ThrowOnError) + throw ex; + } + + public void WriteError(string error) + { + if (JsConfig.ThrowOnError) + throw new Exception(error); + } - public void WriteError(Exception ex) { } + public void WriteError(string format, params object[] args) + { + if (JsConfig.ThrowOnError) + throw new Exception(string.Format(format, args)); + } + } - public void WriteError(string error) { } + public class ConsoleTracer : ITracer + { + public void WriteDebug(string error) + { + PclExport.Instance.WriteLine(error); + } - public void WriteError(string format, params object[] args) { } + public void WriteDebug(string format, params object[] args) + { + PclExport.Instance.WriteLine(format, args); + } - } + public void WriteWarning(string warning) + { + PclExport.Instance.WriteLine(warning); + } - public class ConsoleTracer : ITracer - { - public void WriteDebug(string error) - { - Console.WriteLine(error); - } + public void WriteWarning(string format, params object[] args) + { + PclExport.Instance.WriteLine(format, args); + } - public void WriteDebug(string format, params object[] args) - { - Console.WriteLine(format, args); - } + public void WriteError(Exception ex) + { + PclExport.Instance.WriteLine(ex.ToString()); + } - public void WriteWarning(string warning) - { - Console.WriteLine(warning); - } + public void WriteError(string error) + { + PclExport.Instance.WriteLine(error); + } - public void WriteWarning(string format, params object[] args) - { - Console.WriteLine(format, args); + public void WriteError(string format, params object[] args) + { + PclExport.Instance.WriteLine(format, args); } + } + } - public void WriteError(Exception ex) - { - Console.WriteLine(ex); - } - - public void WriteError(string error) - { - Console.WriteLine(error); - } - - public void WriteError(string format, params object[] args) - { - Console.WriteLine(format, args); - } - } - } + public static class TracerExceptions + { + public static T Trace(this T ex) where T : Exception + { + Tracer.Instance.WriteError(ex); + return ex; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/TranslateListWithElements.cs b/src/ServiceStack.Text/TranslateListWithElements.cs index a7ac5f4a8..b3613133d 100644 --- a/src/ServiceStack.Text/TranslateListWithElements.cs +++ b/src/ServiceStack.Text/TranslateListWithElements.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; @@ -15,220 +15,242 @@ using System.Collections.Generic; using System.Reflection; using System.Threading; +using System.Linq; using ServiceStack.Text.Common; namespace ServiceStack.Text { - public static class TranslateListWithElements - { + public static class TranslateListWithElements + { private static Dictionary TranslateICollectionCache = new Dictionary(); - public static object TranslateToGenericICollectionCache(object from, Type toInstanceOfType, Type elementType) - { - ConvertInstanceDelegate translateToFn; - if (TranslateICollectionCache.TryGetValue(toInstanceOfType, out translateToFn)) + public static object TranslateToGenericICollectionCache(object from, Type toInstanceOfType, Type elementType) + { + if (TranslateICollectionCache.TryGetValue(toInstanceOfType, out var translateToFn)) return translateToFn(from, toInstanceOfType); var genericType = typeof(TranslateListWithElements<>).MakeGenericType(elementType); - var mi = genericType.GetMethod("LateBoundTranslateToGenericICollection", BindingFlags.Static | BindingFlags.Public); - translateToFn = (ConvertInstanceDelegate)Delegate.CreateDelegate(typeof(ConvertInstanceDelegate), mi); + var mi = genericType.GetStaticMethod("LateBoundTranslateToGenericICollection"); + translateToFn = (ConvertInstanceDelegate)mi.MakeDelegate(typeof(ConvertInstanceDelegate)); Dictionary snapshot, newCache; do { snapshot = TranslateICollectionCache; - newCache = new Dictionary(TranslateICollectionCache); - newCache[elementType] = translateToFn; + newCache = new Dictionary(TranslateICollectionCache) { + [elementType] = translateToFn + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref TranslateICollectionCache, newCache, snapshot), snapshot)); - return translateToFn(from, toInstanceOfType); - } + return translateToFn(from, toInstanceOfType); + } private static Dictionary TranslateConvertibleICollectionCache = new Dictionary(); - public static object TranslateToConvertibleGenericICollectionCache( - object from, Type toInstanceOfType, Type fromElementType) - { - var typeKey = new ConvertibleTypeKey(toInstanceOfType, fromElementType); - ConvertInstanceDelegate translateToFn; - if (TranslateConvertibleICollectionCache.TryGetValue(typeKey, out translateToFn)) return translateToFn(from, toInstanceOfType); + public static object TranslateToConvertibleGenericICollectionCache( + object from, Type toInstanceOfType, Type fromElementType) + { + var typeKey = new ConvertibleTypeKey(toInstanceOfType, fromElementType); + if (TranslateConvertibleICollectionCache.TryGetValue(typeKey, out var translateToFn)) return translateToFn(from, toInstanceOfType); - var toElementType = toInstanceOfType.GetGenericType().GetGenericArguments()[0]; + var toElementType = toInstanceOfType.FirstGenericType().GetGenericArguments()[0]; var genericType = typeof(TranslateListWithConvertibleElements<,>).MakeGenericType(fromElementType, toElementType); - var mi = genericType.GetMethod("LateBoundTranslateToGenericICollection", BindingFlags.Static | BindingFlags.Public); - translateToFn = (ConvertInstanceDelegate)Delegate.CreateDelegate(typeof(ConvertInstanceDelegate), mi); + var mi = genericType.GetStaticMethod("LateBoundTranslateToGenericICollection"); + translateToFn = (ConvertInstanceDelegate)mi.MakeDelegate(typeof(ConvertInstanceDelegate)); Dictionary snapshot, newCache; do { snapshot = TranslateConvertibleICollectionCache; - newCache = new Dictionary(TranslateConvertibleICollectionCache); - newCache[typeKey] = translateToFn; + newCache = new Dictionary(TranslateConvertibleICollectionCache) { + [typeKey] = translateToFn + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref TranslateConvertibleICollectionCache, newCache, snapshot), snapshot)); - + return translateToFn(from, toInstanceOfType); - } - - public static object TryTranslateToGenericICollection(Type fromPropertyType, Type toPropertyType, object fromValue) - { - var args = typeof(ICollection<>).GetGenericArgumentsIfBothHaveSameGenericDefinitionTypeAndArguments( - fromPropertyType, toPropertyType); - - if (args != null) - { - return TranslateToGenericICollectionCache( - fromValue, toPropertyType, args[0]); - } - - var varArgs = typeof(ICollection<>).GetGenericArgumentsIfBothHaveConvertibleGenericDefinitionTypeAndArguments( - fromPropertyType, toPropertyType); - - if (varArgs != null) - { - return TranslateToConvertibleGenericICollectionCache( - fromValue, toPropertyType, varArgs.Args1[0]); - } - - return null; - } - - } - - public class ConvertibleTypeKey - { - public Type ToInstanceType { get; set; } - public Type FromElemenetType { get; set; } - - public ConvertibleTypeKey() - { - } - - public ConvertibleTypeKey(Type toInstanceType, Type fromElemenetType) - { - ToInstanceType = toInstanceType; - FromElemenetType = fromElemenetType; - } - - public bool Equals(ConvertibleTypeKey other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other.ToInstanceType, ToInstanceType) && Equals(other.FromElemenetType, FromElemenetType); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(ConvertibleTypeKey)) return false; - return Equals((ConvertibleTypeKey)obj); - } - - public override int GetHashCode() - { - unchecked - { - return ((ToInstanceType != null ? ToInstanceType.GetHashCode() : 0) * 397) - ^ (FromElemenetType != null ? FromElemenetType.GetHashCode() : 0); - } - } - } - - public class TranslateListWithElements - { - public static object CreateInstance(Type toInstanceOfType) - { - if (toInstanceOfType.IsGenericType) - { - if (toInstanceOfType.HasAnyTypeDefinitionsOf( - typeof(ICollection<>), typeof(IList<>))) - { - return ReflectionExtensions.CreateInstance(typeof(List)); - } - } - - return ReflectionExtensions.CreateInstance(toInstanceOfType); - } - - public static IList TranslateToIList(IList fromList, Type toInstanceOfType) - { - var to = (IList)ReflectionExtensions.CreateInstance(toInstanceOfType); - foreach (var item in fromList) - { - to.Add(item); - } - return to; - } - - public static object LateBoundTranslateToGenericICollection( - object fromList, Type toInstanceOfType) - { - if (fromList == null) return null; //AOT - - return TranslateToGenericICollection( - (ICollection)fromList, toInstanceOfType); - } - - public static ICollection TranslateToGenericICollection( - ICollection fromList, Type toInstanceOfType) - { - var to = (ICollection)CreateInstance(toInstanceOfType); - foreach (var item in fromList) - { - to.Add(item); - } - return to; - } - } - - public class TranslateListWithConvertibleElements - { - private static readonly Func ConvertFn; - - static TranslateListWithConvertibleElements() - { - ConvertFn = GetConvertFn(); - } - - public static object LateBoundTranslateToGenericICollection( - object fromList, Type toInstanceOfType) - { - return TranslateToGenericICollection( - (ICollection)fromList, toInstanceOfType); - } - - public static ICollection TranslateToGenericICollection( - ICollection fromList, Type toInstanceOfType) - { - if (fromList == null) return null; //AOT - - var to = (ICollection)TranslateListWithElements.CreateInstance(toInstanceOfType); - - foreach (var item in fromList) - { - var toItem = ConvertFn(item); - to.Add(toItem); - } - return to; - } - - private static Func GetConvertFn() - { - if (typeof(TTo) == typeof(string)) - { - return x => (TTo)(object)TypeSerializer.SerializeToString(x); - } - if (typeof(TFrom) == typeof(string)) - { - return x => TypeSerializer.DeserializeFromString((string)(object)x); - } - return x => TypeSerializer.DeserializeFromString(TypeSerializer.SerializeToString(x)); - } - } + } + + public static object TryTranslateCollections(Type fromPropertyType, Type toPropertyType, object fromValue) + { + var args = typeof(IEnumerable<>).GetGenericArgumentsIfBothHaveSameGenericDefinitionTypeAndArguments( + fromPropertyType, toPropertyType); + + if (args != null) + { + return TranslateToGenericICollectionCache( + fromValue, toPropertyType, args[0]); + } + + var varArgs = typeof(IEnumerable<>).GetGenericArgumentsIfBothHaveConvertibleGenericDefinitionTypeAndArguments( + fromPropertyType, toPropertyType); + + if (varArgs != null) + { + return TranslateToConvertibleGenericICollectionCache( + fromValue, toPropertyType, varArgs.Args1[0]); + } + + var fromElType = fromPropertyType.GetCollectionType(); + var toElType = toPropertyType.GetCollectionType(); + + if (fromElType == null || toElType == null) + return null; + + return TranslateToGenericICollectionCache(fromValue, toPropertyType, toElType); + } + } + + public class ConvertibleTypeKey + { + public Type ToInstanceType { get; set; } + public Type FromElementType { get; set; } + + public ConvertibleTypeKey() + { + } + + public ConvertibleTypeKey(Type toInstanceType, Type fromElementType) + { + ToInstanceType = toInstanceType; + FromElementType = fromElementType; + } + + public bool Equals(ConvertibleTypeKey other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other.ToInstanceType == ToInstanceType && other.FromElementType == FromElementType; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(ConvertibleTypeKey)) return false; + return Equals((ConvertibleTypeKey)obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((ToInstanceType != null ? ToInstanceType.GetHashCode() : 0) * 397) + ^ (FromElementType != null ? FromElementType.GetHashCode() : 0); + } + } + } + + public class TranslateListWithElements + { + public static object CreateInstance(Type toInstanceOfType) + { + if (toInstanceOfType.IsGenericType) + { + if (toInstanceOfType.HasAnyTypeDefinitionsOf( + typeof(ICollection<>), typeof(IList<>))) + { + return typeof(List).CreateInstance(); + } + } + + return toInstanceOfType.CreateInstance(); + } + + public static IList TranslateToIList(IList fromList, Type toInstanceOfType) + { + var to = (IList)toInstanceOfType.CreateInstance(); + foreach (var item in fromList) + { + to.Add(item); + } + return to; + } + + public static object LateBoundTranslateToGenericICollection( + object fromList, Type toInstanceOfType) + { + if (fromList == null) + return null; //AOT + + if (toInstanceOfType.IsArray) + { + var result = TranslateToGenericICollection( + (IEnumerable)fromList, typeof(List)); + return result.ToArray(); + } + + return TranslateToGenericICollection( + (IEnumerable)fromList, toInstanceOfType); + } + + public static ICollection TranslateToGenericICollection( + IEnumerable fromList, Type toInstanceOfType) + { + var to = (ICollection)CreateInstance(toInstanceOfType); + foreach (var item in fromList) + { + if (item is IEnumerable> dictionary) + { + var convertedItem = dictionary.FromObjectDictionary(); + to.Add(convertedItem); + } + else + { + to.Add((T)item); + } + } + return to; + } + } + + public class TranslateListWithConvertibleElements + { + private static readonly Func ConvertFn; + + static TranslateListWithConvertibleElements() + { + ConvertFn = GetConvertFn(); + } + + public static object LateBoundTranslateToGenericICollection( + object fromList, Type toInstanceOfType) + { + return TranslateToGenericICollection( + (ICollection)fromList, toInstanceOfType); + } + + public static ICollection TranslateToGenericICollection( + ICollection fromList, Type toInstanceOfType) + { + if (fromList == null) return null; //AOT + + var to = (ICollection)TranslateListWithElements.CreateInstance(toInstanceOfType); + + foreach (var item in fromList) + { + var toItem = ConvertFn(item); + to.Add(toItem); + } + return to; + } + + private static Func GetConvertFn() + { + if (typeof(TTo) == typeof(string)) + { + return x => (TTo)(object)TypeSerializer.SerializeToString(x); + } + if (typeof(TFrom) == typeof(string)) + { + return x => TypeSerializer.DeserializeFromString((string)(object)x); + } + return x => TypeSerializer.DeserializeFromString(TypeSerializer.SerializeToString(x)); + } + } } diff --git a/src/ServiceStack.Text/TypeConfig.cs b/src/ServiceStack.Text/TypeConfig.cs index c6ced4e17..57585be3e 100644 --- a/src/ServiceStack.Text/TypeConfig.cs +++ b/src/ServiceStack.Text/TypeConfig.cs @@ -1,53 +1,122 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; namespace ServiceStack.Text { - internal class TypeConfig - { - internal readonly Type Type; - internal bool EnableAnonymousFieldSetterses; - internal PropertyInfo[] Properties; - - internal TypeConfig(Type type) - { - Type = type; - EnableAnonymousFieldSetterses = false; - Properties = new PropertyInfo[0]; - } - } - - public static class TypeConfig - { - private static readonly TypeConfig config; - - public static PropertyInfo[] Properties - { - get { return config.Properties; } - set { config.Properties = value; } - } - - public static bool EnableAnonymousFieldSetters - { - get { return config.EnableAnonymousFieldSetterses; } - set { config.EnableAnonymousFieldSetterses = value; } - } - - static TypeConfig() - { - config = new TypeConfig(typeof(T)); - - var excludedProperties = JsConfig.ExcludePropertyNames ?? new string[0]; - var properties = excludedProperties.Any() - ? config.Type.GetSerializableProperties().Where(x => !excludedProperties.Contains(x.Name)) - : config.Type.GetSerializableProperties(); - Properties = properties.Where(x => x.GetIndexParameters().Length == 0).ToArray(); - } - - internal static TypeConfig GetState() - { - return config; - } - } + internal class TypeConfig + { + internal readonly Type Type; + internal bool EnableAnonymousFieldSetters; + internal PropertyInfo[] Properties; + internal FieldInfo[] Fields; + internal Func OnDeserializing; + internal bool IsUserType { get; set; } + internal Func TextCaseResolver; + internal TextCase? TextCase + { + get + { + var result = TextCaseResolver?.Invoke(); + return result is null or Text.TextCase.Default ? null : result; + } + } + + internal TypeConfig(Type type) + { + Type = type; + EnableAnonymousFieldSetters = false; + Properties = TypeConstants.EmptyPropertyInfoArray; + Fields = TypeConstants.EmptyFieldInfoArray; + + JsConfig.AddUniqueType(Type); + } + } + + public static class TypeConfig + { + internal static TypeConfig config; + + static TypeConfig Config => config ??= Create(); + + public static PropertyInfo[] Properties + { + get => Config.Properties; + set => Config.Properties = value; + } + + public static FieldInfo[] Fields + { + get => Config.Fields; + set => Config.Fields = value; + } + + public static bool EnableAnonymousFieldSetters + { + get => Config.EnableAnonymousFieldSetters; + set => Config.EnableAnonymousFieldSetters = value; + } + + public static bool IsUserType + { + get => Config.IsUserType; + set => Config.IsUserType = value; + } + + static TypeConfig() + { + Init(); + } + + internal static void Init() + { + if (config == null) + { + Create(); + } + } + + public static Func OnDeserializing + { + get => config.OnDeserializing; + set => config.OnDeserializing = value; + } + + static TypeConfig Create() + { + config = new TypeConfig(typeof(T)) { + TextCaseResolver = () => JsConfig.TextCase + }; + + var excludedProperties = JsConfig.ExcludePropertyNames ?? TypeConstants.EmptyStringArray; + + var properties = excludedProperties.Length > 0 + ? config.Type.GetSerializableProperties().Where(x => !excludedProperties.Contains(x.Name)) + : config.Type.GetSerializableProperties(); + Properties = properties.Where(x => x.GetIndexParameters().Length == 0).ToArray(); + + Fields = config.Type.GetSerializableFields().ToArray(); + + if (!JsConfig.HasDeserializingFn) + OnDeserializing = ReflectionExtensions.GetOnDeserializing(); + else + config.OnDeserializing = (instance, memberName, value) => JsConfig.OnDeserializingFn((T)instance, memberName, value); + + IsUserType = !typeof(T).IsValueType && typeof(T).Namespace != "System"; + + return config; + } + + public static void Reset() + { + config = null; + } + + internal static TypeConfig GetState() + { + return Config; + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/TypeConstants.cs b/src/ServiceStack.Text/TypeConstants.cs new file mode 100644 index 000000000..a2d989981 --- /dev/null +++ b/src/ServiceStack.Text/TypeConstants.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; + +namespace ServiceStack +{ + public static class TypeConstants + { + static TypeConstants() + { + ZeroTask = InTask(0); + TrueTask = InTask(true); + FalseTask = InTask(false); + EmptyTask = InTask((object)null); + } + + private static Task InTask(this T result) + { + var tcs = new TaskCompletionSource(); + tcs.SetResult(result); + return tcs.Task; + } + + public static readonly Task ZeroTask; + public static readonly Task TrueTask; + public static readonly Task FalseTask; + public static readonly Task EmptyTask; + + public static readonly object EmptyObject = new object(); + + public const char NonWidthWhiteSpace = (char)0x200B; //Use zero-width space marker to capture empty string + public static char[] NonWidthWhiteSpaceChars = { (char)0x200B }; + + public static ReadOnlySpan NullStringSpan => default; + public static ReadOnlySpan EmptyStringSpan => new ReadOnlySpan(NonWidthWhiteSpaceChars); + + public static ReadOnlyMemory NullStringMemory => default; + public static ReadOnlyMemory EmptyStringMemory => "".AsMemory(); + + public static readonly string[] EmptyStringArray = new string[0]; + public static readonly long[] EmptyLongArray = new long[0]; + public static readonly int[] EmptyIntArray = new int[0]; + public static readonly char[] EmptyCharArray = new char[0]; + public static readonly bool[] EmptyBoolArray = new bool[0]; + public static readonly byte[] EmptyByteArray = new byte[0]; + public static readonly object[] EmptyObjectArray = new object[0]; + public static readonly Type[] EmptyTypeArray = new Type[0]; + public static readonly FieldInfo[] EmptyFieldInfoArray = new FieldInfo[0]; + public static readonly PropertyInfo[] EmptyPropertyInfoArray = new PropertyInfo[0]; + + public static readonly byte[][] EmptyByteArrayArray = new byte[0][]; + + public static readonly Dictionary EmptyStringDictionary = new Dictionary(0); + public static readonly Dictionary EmptyObjectDictionary = new Dictionary(); + + public static readonly List EmptyStringList = new List(0); + public static readonly List EmptyLongList = new List(0); + public static readonly List EmptyIntList = new List(0); + public static readonly List EmptyCharList = new List(0); + public static readonly List EmptyBoolList = new List(0); + public static readonly List EmptyByteList = new List(0); + public static readonly List EmptyObjectList = new List(0); + public static readonly List EmptyTypeList = new List(0); + public static readonly List EmptyFieldInfoList = new List(0); + public static readonly List EmptyPropertyInfoList = new List(0); + } + + public static class TypeConstants + { + public static readonly T[] EmptyArray = new T[0]; + public static readonly List EmptyList = new List(0); + public static readonly HashSet EmptyHashSet = new HashSet(); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/TypeFields.cs b/src/ServiceStack.Text/TypeFields.cs new file mode 100644 index 000000000..6a82b52f4 --- /dev/null +++ b/src/ServiceStack.Text/TypeFields.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using ServiceStack.Text; + +namespace ServiceStack +{ + public class FieldAccessor + { + public FieldAccessor( + FieldInfo fieldInfo, + GetMemberDelegate publicGetter, + SetMemberDelegate publicSetter, + SetMemberRefDelegate publicSetterRef) + { + FieldInfo = fieldInfo; + PublicGetter = publicGetter; + PublicSetter = publicSetter; + PublicSetterRef = publicSetterRef; + } + + public FieldInfo FieldInfo { get; } + + public GetMemberDelegate PublicGetter { get; } + + public SetMemberDelegate PublicSetter { get; } + + public SetMemberRefDelegate PublicSetterRef { get; } + } + + public class TypeFields : TypeFields + { + public static readonly TypeFields Instance = new TypeFields(); + + static TypeFields() + { + Instance.Type = typeof(T); + Instance.PublicFieldInfos = typeof(T).GetPublicFields(); + foreach (var fi in Instance.PublicFieldInfos) + { + try + { + var fnRef = fi.SetExpressionRef(); + Instance.FieldsMap[fi.Name] = new FieldAccessor( + fi, + ReflectionOptimizer.Instance.CreateGetter(fi), + ReflectionOptimizer.Instance.CreateSetter(fi), + delegate (ref object instance, object arg) + { + var valueInstance = (T)instance; + fnRef(ref valueInstance, arg); + instance = valueInstance; + }); + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + } + } + } + + public new static FieldAccessor GetAccessor(string propertyName) + { + return Instance.FieldsMap.TryGetValue(propertyName, out FieldAccessor info) + ? info + : null; + } + } + + public abstract class TypeFields + { + static Dictionary CacheMap = new Dictionary(); + + public static Type FactoryType = typeof(TypeFields<>); + + public static TypeFields Get(Type type) + { + if (CacheMap.TryGetValue(type, out TypeFields value)) + return value; + + var genericType = FactoryType.MakeGenericType(type); + var instanceFi = genericType.GetPublicStaticField("Instance"); + var instance = (TypeFields)instanceFi.GetValue(null); + + Dictionary snapshot, newCache; + do + { + snapshot = CacheMap; + newCache = new Dictionary(CacheMap) + { + [type] = instance + }; + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref CacheMap, newCache, snapshot), snapshot)); + + return instance; + } + + public FieldAccessor GetAccessor(string propertyName) + { + return FieldsMap.TryGetValue(propertyName, out FieldAccessor info) + ? info + : null; + } + + public Type Type { get; protected set; } + + public readonly Dictionary FieldsMap = + new Dictionary(PclExport.Instance.InvariantComparerIgnoreCase); + + public FieldInfo[] PublicFieldInfos { get; protected set; } + + public virtual FieldInfo GetPublicField(string name) + { + foreach (var fi in PublicFieldInfos) + { + if (fi.Name == name) + return fi; + } + return null; + } + + public virtual GetMemberDelegate GetPublicGetter(FieldInfo fi) => GetPublicGetter(fi?.Name); + + public virtual GetMemberDelegate GetPublicGetter(string name) + { + if (name == null) + return null; + + return FieldsMap.TryGetValue(name, out FieldAccessor info) + ? info.PublicGetter + : null; + } + + public virtual SetMemberDelegate GetPublicSetter(FieldInfo fi) => GetPublicSetter(fi?.Name); + + public virtual SetMemberDelegate GetPublicSetter(string name) + { + if (name == null) + return null; + + return FieldsMap.TryGetValue(name, out FieldAccessor info) + ? info.PublicSetter + : null; + } + + public virtual SetMemberRefDelegate GetPublicSetterRef(string name) + { + if (name == null) + return null; + + return FieldsMap.TryGetValue(name, out FieldAccessor info) + ? info.PublicSetterRef + : null; + } + } + + public static class FieldInvoker + { + public static GetMemberDelegate CreateGetter(this FieldInfo fieldInfo) => + ReflectionOptimizer.Instance.CreateGetter(fieldInfo); + + public static GetMemberDelegate CreateGetter(this FieldInfo fieldInfo) => + ReflectionOptimizer.Instance.CreateGetter(fieldInfo); + + public static SetMemberDelegate CreateSetter(this FieldInfo fieldInfo) => + ReflectionOptimizer.Instance.CreateSetter(fieldInfo); + + public static SetMemberDelegate CreateSetter(this FieldInfo fieldInfo) => + ReflectionOptimizer.Instance.CreateSetter(fieldInfo); + + public static SetMemberRefDelegate SetExpressionRef(this FieldInfo fieldInfo) => + ReflectionOptimizer.Instance.CreateSetterRef(fieldInfo); + } +} diff --git a/src/ServiceStack.Text/TypeProperties.cs b/src/ServiceStack.Text/TypeProperties.cs new file mode 100644 index 000000000..1fc0fdbfd --- /dev/null +++ b/src/ServiceStack.Text/TypeProperties.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using ServiceStack.Text; + +namespace ServiceStack +{ + public class PropertyAccessor + { + public PropertyAccessor( + PropertyInfo propertyInfo, + GetMemberDelegate publicGetter, + SetMemberDelegate publicSetter) + { + PropertyInfo = propertyInfo; + PublicGetter = publicGetter; + PublicSetter = publicSetter; + } + + public PropertyInfo PropertyInfo { get; } + + public GetMemberDelegate PublicGetter { get; } + + public SetMemberDelegate PublicSetter { get; } + } + + public class TypeProperties : TypeProperties + { + public static readonly TypeProperties Instance = new TypeProperties(); + + static TypeProperties() + { + Instance.Type = typeof(T); + Instance.PublicPropertyInfos = typeof(T).GetPublicProperties(); + foreach (var pi in Instance.PublicPropertyInfos) + { + try + { + Instance.PropertyMap[pi.Name] = new PropertyAccessor( + pi, + ReflectionOptimizer.Instance.CreateGetter(pi), + ReflectionOptimizer.Instance.CreateSetter(pi) + ); + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + } + } + } + + public new static PropertyAccessor GetAccessor(string propertyName) + { + return Instance.PropertyMap.TryGetValue(propertyName, out PropertyAccessor info) + ? info + : null; + } + } + + public abstract class TypeProperties + { + static Dictionary CacheMap = new Dictionary(); + + public static readonly Type FactoryType = typeof(TypeProperties<>); + + public static TypeProperties Get(Type type) + { + if (CacheMap.TryGetValue(type, out TypeProperties value)) + return value; + + var genericType = FactoryType.MakeGenericType(type); + var instanceFi = genericType.GetPublicStaticField("Instance"); + var instance = (TypeProperties)instanceFi.GetValue(null); + + Dictionary snapshot, newCache; + do + { + snapshot = CacheMap; + newCache = new Dictionary(CacheMap) + { + [type] = instance + }; + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref CacheMap, newCache, snapshot), snapshot)); + + return instance; + } + + public PropertyAccessor GetAccessor(string propertyName) + { + return PropertyMap.TryGetValue(propertyName, out PropertyAccessor info) + ? info + : null; + } + + public Type Type { get; protected set; } + + public readonly Dictionary PropertyMap = + new Dictionary(PclExport.Instance.InvariantComparerIgnoreCase); + + public PropertyInfo[] PublicPropertyInfos { get; protected set; } + + public PropertyInfo GetPublicProperty(string name) + { + foreach (var pi in PublicPropertyInfos) + { + if (pi.Name == name) + return pi; + } + return null; + } + + public GetMemberDelegate GetPublicGetter(PropertyInfo pi) => GetPublicGetter(pi?.Name); + + public GetMemberDelegate GetPublicGetter(string name) + { + if (name == null) + return null; + + return PropertyMap.TryGetValue(name, out PropertyAccessor info) + ? info.PublicGetter + : null; + } + + public SetMemberDelegate GetPublicSetter(PropertyInfo pi) => GetPublicSetter(pi?.Name); + + public SetMemberDelegate GetPublicSetter(string name) + { + if (name == null) + return null; + + return PropertyMap.TryGetValue(name, out PropertyAccessor info) + ? info.PublicSetter + : null; + } + } + + public static class PropertyInvoker + { + public static GetMemberDelegate CreateGetter(this PropertyInfo propertyInfo) => + ReflectionOptimizer.Instance.CreateGetter(propertyInfo); + + public static GetMemberDelegate CreateGetter(this PropertyInfo propertyInfo) => + ReflectionOptimizer.Instance.CreateGetter(propertyInfo); + + public static SetMemberDelegate CreateSetter(this PropertyInfo propertyInfo) => + ReflectionOptimizer.Instance.CreateSetter(propertyInfo); + + public static SetMemberDelegate CreateSetter(this PropertyInfo propertyInfo) => + ReflectionOptimizer.Instance.CreateSetter(propertyInfo); + } +} + diff --git a/src/ServiceStack.Text/TypeSerializer.Generic.cs b/src/ServiceStack.Text/TypeSerializer.Generic.cs index 583a01f30..4de9cc6ef 100644 --- a/src/ServiceStack.Text/TypeSerializer.Generic.cs +++ b/src/ServiceStack.Text/TypeSerializer.Generic.cs @@ -5,9 +5,9 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; @@ -17,52 +17,49 @@ namespace ServiceStack.Text { - public class TypeSerializer : ITypeSerializer - { - public bool CanCreateFromString(Type type) - { - return JsvReader.GetParseFn(type) != null; - } + public class TypeSerializer : ITypeSerializer + { + public bool CanCreateFromString(Type type) + { + return JsvReader.GetParseFn(type) != null; + } - /// - /// Parses the specified value. - /// - /// The value. - /// - public T DeserializeFromString(string value) - { - if (string.IsNullOrEmpty(value)) return default(T); - return (T)JsvReader.Parse(value); - } + /// + /// Parses the specified value. + /// + /// The value. + /// + public T DeserializeFromString(string value) + { + if (string.IsNullOrEmpty(value)) return default(T); + return (T)JsvReader.Parse(value); + } - public T DeserializeFromReader(TextReader reader) - { - return DeserializeFromString(reader.ReadToEnd()); - } + public T DeserializeFromReader(TextReader reader) + { + return DeserializeFromString(reader.ReadToEnd()); + } - public string SerializeToString(T value) - { - if (value == null) return null; - if (typeof(T) == typeof(string)) return value as string; + public string SerializeToString(T value) + { + if (value == null) return null; + if (typeof(T) == typeof(string)) return value as string; - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb)) - { - JsvWriter.WriteObject(writer, value); - } - return sb.ToString(); - } + var writer = StringWriterThreadStatic.Allocate(); + JsvWriter.WriteObject(writer, value); + return StringWriterThreadStatic.ReturnAndFree(writer); + } - public void SerializeToWriter(T value, TextWriter writer) - { - if (value == null) return; - if (typeof(T) == typeof(string)) - { - writer.Write(value); - return; - } + public void SerializeToWriter(T value, TextWriter writer) + { + if (value == null) return; + if (typeof(T) == typeof(string)) + { + writer.Write(value); + return; + } - JsvWriter.WriteObject(writer, value); - } - } + JsvWriter.WriteObject(writer, value); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/TypeSerializer.cs b/src/ServiceStack.Text/TypeSerializer.cs index 0e855e5d2..a06fdcd11 100644 --- a/src/ServiceStack.Text/TypeSerializer.cs +++ b/src/ServiceStack.Text/TypeSerializer.cs @@ -5,197 +5,273 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2012 ServiceStack Ltd. +// Copyright 2012 ServiceStack, Inc. All Rights Reserved. // -// Licensed under the same terms of ServiceStack: new BSD license. +// Licensed under the same terms of ServiceStack. // using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; using System.IO; +using System.Linq; using System.Text; +using System.Threading.Tasks; using ServiceStack.Text.Common; using ServiceStack.Text.Jsv; +using ServiceStack.Text.Pools; namespace ServiceStack.Text { - /// - /// Creates an instance of a Type from a string value - /// - public static class TypeSerializer - { - private static readonly UTF8Encoding UTF8EncodingWithoutBom = new UTF8Encoding(false); - - public const string DoubleQuoteString = "\"\""; - - /// - /// Determines whether the specified type is convertible from string. - /// - /// The type. - /// - /// true if the specified type is convertible from string; otherwise, false. - /// - public static bool CanCreateFromString(Type type) - { - return JsvReader.GetParseFn(type) != null; - } - - /// - /// Parses the specified value. - /// - /// The value. - /// - public static T DeserializeFromString(string value) - { - if (string.IsNullOrEmpty(value)) return default(T); - return (T)JsvReader.Parse(value); - } - - public static T DeserializeFromReader(TextReader reader) - { - return DeserializeFromString(reader.ReadToEnd()); - } - - /// - /// Parses the specified type. - /// - /// The type. - /// The value. - /// - public static object DeserializeFromString(string value, Type type) - { - return value == null - ? null - : JsvReader.GetParseFn(type)(value); - } - - public static object DeserializeFromReader(TextReader reader, Type type) - { - return DeserializeFromString(reader.ReadToEnd(), type); - } - - public static string SerializeToString(T value) - { - if (value == null) return null; - if (typeof(T) == typeof(string)) return value as string; - if (typeof(T) == typeof(object) || typeof(T).IsAbstract || typeof(T).IsInterface) - { - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; + /// + /// Creates an instance of a Type from a string value + /// + public static class TypeSerializer + { + static TypeSerializer() + { + JsConfig.InitStatics(); + } + + [Obsolete("Use JsConfig.UTF8Encoding")] + public static UTF8Encoding UTF8Encoding + { + get => JsConfig.UTF8Encoding; + set => JsConfig.UTF8Encoding = value; + } + + public static Action OnSerialize { get; set; } + + public const string DoubleQuoteString = "\"\""; + + /// + /// Determines whether the specified type is convertible from string. + /// + /// The type. + /// + /// true if the specified type is convertible from string; otherwise, false. + /// + public static bool CanCreateFromString(Type type) + { + return JsvReader.GetParseFn(type) != null; + } + + /// + /// Parses the specified value. + /// + /// The value. + /// + public static T DeserializeFromString(string value) + { + if (string.IsNullOrEmpty(value)) return default(T); + return (T)JsvReader.Parse(value); + } + + public static T DeserializeFromSpan(ReadOnlySpan value) + { + if (value.IsEmpty) return default(T); + return (T)JsvReader.Parse(value); + } + + public static T DeserializeFromReader(TextReader reader) + { + return DeserializeFromString(reader.ReadToEnd()); + } + + /// + /// Parses the specified type. + /// + /// The type. + /// The value. + /// + public static object DeserializeFromString(string value, Type type) + { + return value == null + ? null + : JsvReader.GetParseFn(type)(value); + } + + public static object DeserializeFromSpan(Type type, ReadOnlySpan value) + { + return value.IsEmpty + ? null + : JsvReader.GetParseSpanFn(type)(value); + } + + public static object DeserializeFromReader(TextReader reader, Type type) + { + return DeserializeFromString(reader.ReadToEnd(), type); + } + + public static string SerializeToString(T value) + { + if (value == null || value is Delegate) return null; + if (typeof(T) == typeof(object)) + { + return SerializeToString(value, value.GetType()); + } + if (typeof(T).IsAbstract || typeof(T).IsInterface) + { + var prevState = JsState.IsWritingDynamic; + JsState.IsWritingDynamic = true; var result = SerializeToString(value, value.GetType()); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; + JsState.IsWritingDynamic = prevState; return result; } - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture)) - { - JsvWriter.WriteObject(writer, value); - } - return sb.ToString(); - } - - public static string SerializeToString(object value, Type type) - { - if (value == null) return null; - if (type == typeof(string)) return value as string; - - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture)) - { - JsvWriter.GetWriteFn(type)(writer, value); - } - return sb.ToString(); - } - - public static void SerializeToWriter(T value, TextWriter writer) - { - if (value == null) return; - if (typeof(T) == typeof(string)) - { - writer.Write(value); - return; - } - if (typeof(T) == typeof(object)) - { - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; + var writer = StringWriterThreadStatic.Allocate(); + JsvWriter.WriteRootObject(writer, value); + return StringWriterThreadStatic.ReturnAndFree(writer); + } + + public static string SerializeToString(object value, Type type) + { + if (value == null) return null; + if (value is string str) + return str.EncodeJsv(); + + OnSerialize?.Invoke(value); + var writer = StringWriterThreadStatic.Allocate(); + JsvWriter.GetWriteFn(type)(writer, value); + return StringWriterThreadStatic.ReturnAndFree(writer); + } + + public static void SerializeToWriter(T value, TextWriter writer) + { + if (value == null) return; + if (value is string str) + { + writer.Write(str.EncodeJsv()); + } + else if (typeof(T) == typeof(object)) + { SerializeToWriter(value, value.GetType(), writer); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; + } + else if (typeof(T).IsAbstract || typeof(T).IsInterface) + { + var prevState = JsState.IsWritingDynamic; + JsState.IsWritingDynamic = false; + SerializeToWriter(value, value.GetType(), writer); + JsState.IsWritingDynamic = prevState; + } + else + { + JsvWriter.WriteRootObject(writer, value); + } + } + + public static void SerializeToWriter(object value, Type type, TextWriter writer) + { + if (value == null) return; + if (value is string str) + { + writer.Write(str.EncodeJsv()); return; - } - - JsvWriter.WriteObject(writer, value); - } - - public static void SerializeToWriter(object value, Type type, TextWriter writer) - { - if (value == null) return; - if (type == typeof(string)) - { - writer.Write(value); - return; - } - - JsvWriter.GetWriteFn(type)(writer, value); - } - - public static void SerializeToStream(T value, Stream stream) - { - if (value == null) return; - if (typeof(T) == typeof(object)) - { - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; + } + + OnSerialize?.Invoke(value); + JsvWriter.GetWriteFn(type)(writer, value); + } + + public static void SerializeToStream(T value, Stream stream) + { + if (value == null) return; + if (typeof(T) == typeof(object)) + { SerializeToStream(value, value.GetType(), stream); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; - return; - } - - var writer = new StreamWriter(stream, UTF8EncodingWithoutBom); - JsvWriter.WriteObject(writer, value); - writer.Flush(); - } - - public static void SerializeToStream(object value, Type type, Stream stream) - { - var writer = new StreamWriter(stream, UTF8EncodingWithoutBom); - JsvWriter.GetWriteFn(type)(writer, value); - writer.Flush(); - } - - public static T Clone(T value) - { - var serializedValue = SerializeToString(value); - var cloneObj = DeserializeFromString(serializedValue); - return cloneObj; - } - - public static T DeserializeFromStream(Stream stream) - { - using (var reader = new StreamReader(stream, UTF8EncodingWithoutBom)) - { - return DeserializeFromString(reader.ReadToEnd()); - } - } - - public static object DeserializeFromStream(Type type, Stream stream) - { - using (var reader = new StreamReader(stream, UTF8EncodingWithoutBom)) - { - return DeserializeFromString(reader.ReadToEnd(), type); - } - } - - /// - /// Useful extension method to get the Dictionary[string,string] representation of any POCO type. - /// - /// - public static Dictionary ToStringDictionary(this T obj) - where T : class - { - var jsv = SerializeToString(obj); - var map = DeserializeFromString>(jsv); - return map; - } + } + else if (typeof(T).IsAbstract || typeof(T).IsInterface) + { + var prevState = JsState.IsWritingDynamic; + JsState.IsWritingDynamic = false; + SerializeToStream(value, value.GetType(), stream); + JsState.IsWritingDynamic = prevState; + } + else + { + var writer = new StreamWriter(stream, JsConfig.UTF8Encoding); + JsvWriter.WriteRootObject(writer, value); + writer.Flush(); + } + } + + public static void SerializeToStream(object value, Type type, Stream stream) + { + OnSerialize?.Invoke(value); + var writer = new StreamWriter(stream, JsConfig.UTF8Encoding); + JsvWriter.GetWriteFn(type)(writer, value); + writer.Flush(); + } + + public static T Clone(T value) + { + var serializedValue = SerializeToString(value); + var cloneObj = DeserializeFromString(serializedValue); + return cloneObj; + } + + public static T DeserializeFromStream(Stream stream) + { + return (T)MemoryProvider.Instance.Deserialize(stream, typeof(T), DeserializeFromSpan); + } + + public static object DeserializeFromStream(Type type, Stream stream) + { + return MemoryProvider.Instance.Deserialize(stream, type, DeserializeFromSpan); + } + + public static Task DeserializeFromStreamAsync(Type type, Stream stream) + { + return MemoryProvider.Instance.DeserializeAsync(stream, type, DeserializeFromSpan); + } + + public static async Task DeserializeFromStreamAsync(Stream stream) + { + var obj = await MemoryProvider.Instance.DeserializeAsync(stream, typeof(T), DeserializeFromSpan).ConfigAwait(); + return (T)obj; + } + + /// + /// Useful extension method to get the Dictionary[string,string] representation of any POCO type. + /// + /// + public static Dictionary ToStringDictionary(this object obj) + { + if (obj == null) + return new Dictionary(); + + if (obj is Dictionary strDictionary) + return strDictionary; + + if (obj is IEnumerable> kvpStrings) + { + var to = new Dictionary(); + foreach (var kvp in kvpStrings) + { + to[kvp.Key] = kvp.Value; + } + return to; + } + + if (obj is IEnumerable> kvps) + return PlatformExtensions.ToStringDictionary(kvps); + + if (obj is NameValueCollection nvc) + { + var to = new Dictionary(); + for (var i = 0; i < nvc.Count; i++) + { + to[nvc.GetKey(i)] = nvc.Get(i); + } + return to; + } + + var jsv = SerializeToString(obj); + var map = DeserializeFromString>(jsv); + return map; + } /// /// Recursively prints the contents of any POCO object in a human-friendly, readable format @@ -211,7 +287,7 @@ public static string Dump(this T instance) /// public static void PrintDump(this T instance) { - Console.WriteLine(SerializeAndFormat(instance)); + PclExport.Instance.WriteLine(SerializeAndFormat(instance)); } /// @@ -220,16 +296,211 @@ public static void PrintDump(this T instance) public static void Print(this string text, params object[] args) { if (args.Length > 0) - Console.WriteLine(text, args); + PclExport.Instance.WriteLine(text, args); else - Console.WriteLine(text); + PclExport.Instance.WriteLine(text); + } + + public static void Print(this int intValue) + { + PclExport.Instance.WriteLine(intValue.ToString(CultureInfo.InvariantCulture)); } - public static string SerializeAndFormat(this T instance) - { - var dtoStr = SerializeToString(instance); - var formatStr = JsvFormatter.Format(dtoStr); - return formatStr; - } - } + public static void Print(this long longValue) + { + PclExport.Instance.WriteLine(longValue.ToString(CultureInfo.InvariantCulture)); + } + + public static string SerializeAndFormat(this T instance) + { + if (instance is Delegate fn) + return Dump(fn); + + var dtoStr = !HasCircularReferences(instance) + ? SerializeToString(instance) + : SerializeToString(instance.ToSafePartialObjectDictionary()); + var formatStr = JsvFormatter.Format(dtoStr); + return formatStr; + } + + public static string Dump(this Delegate fn) + { + var method = fn.GetType().GetMethod("Invoke"); + var sb = StringBuilderThreadStatic.Allocate(); + foreach (var param in method.GetParameters()) + { + if (sb.Length > 0) + sb.Append(", "); + + sb.AppendFormat("{0} {1}", param.ParameterType.Name, param.Name); + } + + var methodName = fn.Method.Name; + var info = $"{method.ReturnType.Name} {methodName}({StringBuilderThreadStatic.ReturnAndFree(sb)})"; + return info; + } + + public static bool HasCircularReferences(object value) + { + return HasCircularReferences(value, null); + } + + private static bool HasCircularReferences(object value, Stack parentValues) + { + var type = value?.GetType(); + + if (type == null || !type.IsClass || value is string || value is Type) + return false; + + if (parentValues == null) + { + parentValues = new Stack(); + parentValues.Push(value); + } + + bool CheckValue(object key) + { + if (parentValues.Contains(key)) + return true; + + parentValues.Push(key); + + if (HasCircularReferences(key, parentValues)) + return true; + + parentValues.Pop(); + return false; + } + + if (value is IEnumerable valueEnumerable) + { + foreach (var item in valueEnumerable) + { + if (item == null) + continue; + + var itemType = item.GetType(); + if (itemType.IsGenericType && itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + var props = TypeProperties.Get(itemType); + var key = props.GetPublicGetter("Key")(item); + + if (CheckValue(key)) + return true; + + var val = props.GetPublicGetter("Value")(item); + + if (CheckValue(val)) + return true; + } + + if (CheckValue(item)) + return true; + } + } + else + { + var props = type.GetSerializableProperties(); + + foreach (var pi in props) + { + if (pi.GetIndexParameters().Length > 0) + continue; + + var mi = pi.GetGetMethod(nonPublic:false); + var pValue = mi != null ? mi.Invoke(value, null) : null; + if (pValue == null) + continue; + + if (CheckValue(pValue)) + return true; + } + } + + return false; + } + + private static void times(int count, Action fn) + { + for (var i = 0; i < count; i++) fn(); + } + + private const string Indent = " "; + public static string IndentJson(this string json) + { + var indent = 0; + var quoted = false; + var sb = StringBuilderThreadStatic.Allocate(); + + for (var i = 0; i < json.Length; i++) + { + var ch = json[i]; + switch (ch) + { + case '{': + case '[': + sb.Append(ch); + if (!quoted) + { + sb.AppendLine(); + times(++indent, () => sb.Append(Indent)); + } + break; + case '}': + case ']': + if (!quoted) + { + sb.AppendLine(); + times(--indent, () => sb.Append(Indent)); + } + sb.Append(ch); + break; + case '"': + sb.Append(ch); + var escaped = false; + var index = i; + while (index > 0 && json[--index] == '\\') + escaped = !escaped; + if (!escaped) + quoted = !quoted; + break; + case ',': + sb.Append(ch); + if (!quoted) + { + sb.AppendLine(); + times(indent, () => sb.Append(Indent)); + } + break; + case ':': + sb.Append(ch); + if (!quoted) + sb.Append(" "); + break; + default: + sb.Append(ch); + break; + } + } + return StringBuilderThreadStatic.ReturnAndFree(sb); + } + } + + public class JsvStringSerializer : IStringSerializer + { + public To DeserializeFromString(string serializedText) + { + return TypeSerializer.DeserializeFromString(serializedText); + } + + public object DeserializeFromString(string serializedText, Type type) + { + return TypeSerializer.DeserializeFromString(serializedText, type); + } + + public string SerializeToString(TFrom @from) + { + return TypeSerializer.SerializeToString(@from); + } + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/WebRequestExtensions.cs b/src/ServiceStack.Text/WebRequestExtensions.cs deleted file mode 100644 index a15786afe..000000000 --- a/src/ServiceStack.Text/WebRequestExtensions.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.IO; -using System.Net; - -namespace ServiceStack.Text -{ - public static class WebRequestExtensions - { - public static string GetJsonFromUrl(this string url, Action responseFilter = null) - { - return url.GetStringFromUrl("application/json", responseFilter); - } - - public static string GetStringFromUrl(this string url, string acceptContentType = "*/*", Action responseFilter = null) - { - var webReq = (HttpWebRequest)WebRequest.Create(url); - webReq.Accept = acceptContentType; - webReq.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate"); - webReq.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; - using (var webRes = webReq.GetResponse()) - using (var stream = webRes.GetResponseStream()) - using (var reader = new StreamReader(stream)) - { - if (responseFilter != null) - { - responseFilter((HttpWebResponse)webRes); - } - return reader.ReadToEnd(); - } - } - - public static bool Is404(this Exception ex) - { - return HasStatus(ex as WebException, HttpStatusCode.NotFound); - } - - public static HttpStatusCode? GetResponseStatus(this string url) - { - try - { - var webReq = (HttpWebRequest)WebRequest.Create(url); - using (var webRes = webReq.GetResponse()) - { - var httpRes = webRes as HttpWebResponse; - return httpRes != null ? httpRes.StatusCode : (HttpStatusCode?)null; - } - } - catch (Exception ex) - { - return ex.GetStatus(); - } - } - - public static HttpStatusCode? GetStatus(this Exception ex) - { - return GetStatus(ex as WebException); - } - - public static HttpStatusCode? GetStatus(this WebException webEx) - { - if (webEx == null) return null; - var httpRes = webEx.Response as HttpWebResponse; - return httpRes != null ? httpRes.StatusCode : (HttpStatusCode?)null; - } - - public static bool HasStatus(this WebException webEx, HttpStatusCode statusCode) - { - return GetStatus(webEx) == statusCode; - } - } -} \ No newline at end of file diff --git a/src/ServiceStack.Text/XmlSerializer.cs b/src/ServiceStack.Text/XmlSerializer.cs index 2ed5cd457..2e60c7c31 100644 --- a/src/ServiceStack.Text/XmlSerializer.cs +++ b/src/ServiceStack.Text/XmlSerializer.cs @@ -1,8 +1,4 @@ - -#if !XBOX360 && !SILVERLIGHT && !WINDOWS_PHONE && !MONOTOUCH -using System.IO.Compression; -#endif - +#if !LITE using System; using System.IO; using System.Runtime.Serialization; @@ -11,45 +7,33 @@ namespace ServiceStack.Text { -#if !XBOX public class XmlSerializer { - private readonly XmlDictionaryReaderQuotas quotas; - private static readonly XmlWriterSettings XSettings = new XmlWriterSettings(); + public static readonly XmlWriterSettings XmlWriterSettings = new XmlWriterSettings(); + public static readonly XmlReaderSettings XmlReaderSettings = new XmlReaderSettings(); - public static XmlSerializer Instance - = new XmlSerializer( -#if !SILVERLIGHT && !WINDOWS_PHONE && !MONOTOUCH - new XmlDictionaryReaderQuotas { MaxStringContentLength = 1024 * 1024, } -#endif -); + public static XmlSerializer Instance = new XmlSerializer(); - public XmlSerializer(XmlDictionaryReaderQuotas quotas=null, bool omitXmlDeclaration = false) + public XmlSerializer(bool omitXmlDeclaration = false, int maxCharsInDocument = 1024 * 1024) { - this.quotas = quotas; - XSettings.Encoding = new UTF8Encoding(false); - XSettings.OmitXmlDeclaration = omitXmlDeclaration; + XmlWriterSettings.Encoding = PclExport.Instance.GetUTF8Encoding(false); + XmlWriterSettings.OmitXmlDeclaration = omitXmlDeclaration; + XmlReaderSettings.MaxCharactersInDocument = maxCharsInDocument; + + //Prevent XML bombs by default: https://msdn.microsoft.com/en-us/magazine/ee335713.aspx + XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit; } - private static object Deserialize(string xml, Type type, XmlDictionaryReaderQuotas quotas) + private static object Deserialize(string xml, Type type) { try { -#if WINDOWS_PHONE - StringReader stringReader = new StringReader(xml); - using (var reader = XmlDictionaryReader.Create(stringReader)) - { - var serializer = new DataContractSerializer(type); - return serializer.ReadObject(reader); - } -#else - var bytes = Encoding.UTF8.GetBytes(xml); - using (var reader = XmlDictionaryReader.CreateTextReader(bytes, quotas)) + var stringReader = new StringReader(xml); + using (var reader = XmlReader.Create(stringReader, XmlReaderSettings)) { var serializer = new DataContractSerializer(type); return serializer.ReadObject(reader); } -#endif } catch (Exception ex) { @@ -59,13 +43,13 @@ private static object Deserialize(string xml, Type type, XmlDictionaryReaderQuot public static object DeserializeFromString(string xml, Type type) { - return Deserialize(xml, type, Instance.quotas); + return Deserialize(xml, type); } public static T DeserializeFromString(string xml) { var type = typeof(T); - return (T)Deserialize(xml, type, Instance.quotas); + return (T)Deserialize(xml, type); } public static T DeserializeFromReader(TextReader reader) @@ -90,22 +74,20 @@ public static string SerializeToString(T from) { try { - using (var ms = new MemoryStream()) + using (var ms = MemoryStreamFactory.GetStream()) { - using (var xw = XmlWriter.Create(ms, XSettings)) + using (var xw = XmlWriter.Create(ms, XmlWriterSettings)) { var serializer = new DataContractSerializer(from.GetType()); serializer.WriteObject(xw, from); xw.Flush(); - ms.Seek(0, SeekOrigin.Begin); - var reader = new StreamReader(ms); - return reader.ReadToEnd(); + return ms.ReadToEnd(); } } } catch (Exception ex) { - throw new SerializationException(string.Format("Error serializing object of type {0}", from.GetType().FullName), ex); + throw new SerializationException($"Error serializing object of type {@from.GetType().FullName}", ex); } } @@ -113,11 +95,7 @@ public static void SerializeToWriter(T value, TextWriter writer) { try { -#if !SILVERLIGHT - using (var xw = new XmlTextWriter(writer)) -#else - using (var xw = XmlWriter.Create(writer)) -#endif + using (var xw = XmlWriter.Create(writer, XmlWriterSettings)) { var serializer = new DataContractSerializer(value.GetType()); serializer.WriteObject(xw, value); @@ -125,47 +103,19 @@ public static void SerializeToWriter(T value, TextWriter writer) } catch (Exception ex) { - throw new SerializationException(string.Format("Error serializing object of type {0}", value.GetType().FullName), ex); + throw new SerializationException($"Error serializing object of type {value.GetType().FullName}", ex); } } public static void SerializeToStream(object obj, Stream stream) { -#if !SILVERLIGHT - using (var xw = new XmlTextWriter(stream, Encoding.UTF8)) -#else - using (var xw = XmlWriter.Create(stream)) -#endif + if (obj == null) return; + using (var xw = XmlWriter.Create(stream, XmlWriterSettings)) { var serializer = new DataContractSerializer(obj.GetType()); serializer.WriteObject(xw, obj); } } - - -#if !SILVERLIGHT && !MONOTOUCH - public static void CompressToStream(TXmlDto from, Stream stream) - { - using (var deflateStream = new DeflateStream(stream, CompressionMode.Compress)) - using (var xw = new XmlTextWriter(deflateStream, Encoding.UTF8)) - { - var serializer = new DataContractSerializer(from.GetType()); - serializer.WriteObject(xw, from); - xw.Flush(); - } - } - - public static byte[] Compress(TXmlDto from) - { - using (var ms = new MemoryStream()) - { - CompressToStream(from, ms); - - return ms.ToArray(); - } - } -#endif - } -#endif } +#endif \ No newline at end of file diff --git a/src/UpgradeLog.htm b/src/UpgradeLog.htm new file mode 100644 index 000000000..a5d93a65d Binary files /dev/null and b/src/UpgradeLog.htm differ diff --git a/src/_TeamCity.ServiceStack.Text/ProjectModel/projectModel.dat b/src/_TeamCity.ServiceStack.Text/ProjectModel/projectModel.dat deleted file mode 100644 index da8f8e714..000000000 Binary files a/src/_TeamCity.ServiceStack.Text/ProjectModel/projectModel.dat and /dev/null differ diff --git a/src/servicestack.snk b/src/servicestack.snk new file mode 100644 index 000000000..dade7cea3 Binary files /dev/null and b/src/servicestack.snk differ diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 000000000..7eb7717ea --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,29 @@ + + + + 6.0.3 + latest + false + + + + DEBUG + + + + $(DefineConstants);NETFX;NET472 + + + + $(DefineConstants);NETCORE;NETSTANDARD2_0 + + + + $(DefineConstants);NET6_0;NET6_0_OR_GREATER + + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT + + + diff --git a/tests/NetCore.Console.Tests/NetCore.Console.Tests.xproj b/tests/NetCore.Console.Tests/NetCore.Console.Tests.xproj new file mode 100644 index 000000000..0f9a52d96 --- /dev/null +++ b/tests/NetCore.Console.Tests/NetCore.Console.Tests.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 2c558857-b489-44bf-945a-cf8e0f286f3b + NetCore.Console.Tests + .\obj + .\bin\ + v4.6 + + + + 2.0 + + + diff --git a/tests/NetCore.Console.Tests/Program.cs b/tests/NetCore.Console.Tests/Program.cs new file mode 100644 index 000000000..7ae25b2a3 --- /dev/null +++ b/tests/NetCore.Console.Tests/Program.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ServiceStack; + +namespace NetCore.Console.Tests +{ + public class Program + { + public static void Main(string[] args) + { + var obj = new { name = "Hello ServiceStack!" }; + var json = obj.ToJson(); + System.Console.WriteLine(json); + } + } +} diff --git a/tests/NetCore.Console.Tests/Properties/AssemblyInfo.cs b/tests/NetCore.Console.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..775c35063 --- /dev/null +++ b/tests/NetCore.Console.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NetCore.Console.Tests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2c558857-b489-44bf-945a-cf8e0f286f3b")] diff --git a/tests/NetCore.Console.Tests/project.json b/tests/NetCore.Console.Tests/project.json new file mode 100644 index 000000000..d8f870346 --- /dev/null +++ b/tests/NetCore.Console.Tests/project.json @@ -0,0 +1,24 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "debugType": "portable", + "emitEntryPoint": true + }, + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.1", + "type": "platform" + }, + "ServiceStack.Text": "4.5.4" + }, + "frameworks": { + "netcoreapp1.1": { + "dependencies": { + }, + "imports": [ + "dotnet5.6", + "portable-net45+win8" + ] + } + } +} diff --git a/tests/Northwind.Common/ComplexModel/ArrayDtoWithOrders.cs b/tests/Northwind.Common/ComplexModel/ArrayDtoWithOrders.cs new file mode 100644 index 000000000..5dcb09ec7 --- /dev/null +++ b/tests/Northwind.Common/ComplexModel/ArrayDtoWithOrders.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.Serialization; +using Northwind.Common.ServiceModel; +using ProtoBuf; + +namespace Northwind.Common.ComplexModel +{ + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + public class ArrayDtoWithOrders + { + public ArrayDtoWithOrders() + { + Orders = new OrderDto[0]; + } + + [DataMember] + public Guid Id { get; set; } + + [DataMember] + public CustomerDto Customer { get; set; } + + [DataMember] + public SupplierDto Supplier { get; set; } + + [DataMember] + public OrderDto[] Orders { get; set; } + + public override bool Equals(object obj) + { + var other = obj as ArrayDtoWithOrders; + if (other == null) return false; + + return this.Id == other.Id + && this.Customer.Equals(other.Customer) + && this.Supplier.Equals(other.Supplier) + && this.Orders.Length == other.Orders.Length; + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ComplexModel/CustomerOrderArrayDto.cs b/tests/Northwind.Common/ComplexModel/CustomerOrderArrayDto.cs new file mode 100644 index 000000000..24be31244 --- /dev/null +++ b/tests/Northwind.Common/ComplexModel/CustomerOrderArrayDto.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Northwind.Common.ServiceModel; +using ProtoBuf; + +namespace Northwind.Common.ComplexModel +{ + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + public class CustomerOrderArrayDto + { + public CustomerOrderArrayDto() + { + this.Orders = new FullOrderDto[0]; + } + + [ProtoMember(1)] + public CustomerDto Customer { get; set; } + + [ProtoMember(2)] + public FullOrderDto[] Orders { get; set; } + + public override bool Equals(object obj) + { + var other = obj as CustomerOrderArrayDto; + if (other == null) return false; + + var i = 0; + return this.Customer.Equals(other.Customer) + && this.Orders.All(x => x.Equals(other.Orders[i++])); + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ComplexModel/CustomerOrderListDto.cs b/tests/Northwind.Common/ComplexModel/CustomerOrderListDto.cs new file mode 100644 index 000000000..67fa68eb4 --- /dev/null +++ b/tests/Northwind.Common/ComplexModel/CustomerOrderListDto.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Northwind.Common.ServiceModel; +using ProtoBuf; + +namespace Northwind.Common.ComplexModel +{ + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + public class CustomerOrderListDto + { + public CustomerOrderListDto() + { + this.Orders = new List(); + } + + [DataMember] + public CustomerDto Customer { get; set; } + + [DataMember] + public List Orders { get; set; } + + public override bool Equals(object obj) + { + var other = obj as CustomerOrderListDto; + if (other == null) return false; + + var i = 0; + return this.Customer.Equals(other.Customer) + && this.Orders.All(x => x.Equals(other.Orders[i++])); + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ComplexModel/DtoFactory.cs b/tests/Northwind.Common/ComplexModel/DtoFactory.cs new file mode 100644 index 000000000..0b1402ca4 --- /dev/null +++ b/tests/Northwind.Common/ComplexModel/DtoFactory.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using Northwind.Common.ServiceModel; + +namespace Northwind.Common.ComplexModel +{ + public class DtoFactory + { + public static CustomerDto CustomerDto + { + get + { + return NorthwindDtoFactory.Customer( + 1.ToString("x"), "Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", + "Berlin", null, "12209", "Germany", "030-0074321", "030-0076545", null); + } + } + + public static SupplierDto SupplierDto + { + get + { + return NorthwindDtoFactory.Supplier( + 1, "Exotic Liquids", "Charlotte Cooper", "Purchasing Manager", "49 Gilbert St.", "London", null, + "EC1 4SD", "UK", "(171) 555-2222", null, null); + } + } + + public static OrderDto OrderDto + { + get + { + return NorthwindDtoFactory.Order( + 1, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"); + } + } + + public static MultiOrderProperties MultiOrderProperties + { + get + { + return new MultiOrderProperties { + Orders1 = NorthwindDtoFactory.Order( + 1, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + Orders2 = NorthwindDtoFactory.Order( + 2, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + Orders3 = NorthwindDtoFactory.Order( + 3, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + Orders4 = NorthwindDtoFactory.Order( + 4, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + }; + } + } + + public static MultiCustomerProperties MultiCustomerProperties + { + get + { + return new MultiCustomerProperties { + Customer1 = NorthwindDtoFactory.Customer( + 1.ToString("x"), "Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", + "Berlin", null, "12209", "Germany", "030-0074321", "030-0076545", null), + Customer2 = NorthwindDtoFactory.Customer( + 1.ToString("x"), "Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", + "Berlin", null, "12209", "Germany", "030-0074321", "030-0076545", null), + Customer3 = NorthwindDtoFactory.Customer( + 1.ToString("x"), "Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", + "Berlin", null, "12209", "Germany", "030-0074321", "030-0076545", null), + }; + } + } + + public static MultiDtoWithOrders MultiDtoWithOrders + { + get + { + return new MultiDtoWithOrders { + Id = Guid.NewGuid(), + Customer = NorthwindDtoFactory.Customer( + 1.ToString("x"), "Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", + "Berlin", null, "12209", "Germany", "030-0074321", "030-0076545", null), + Supplier = NorthwindDtoFactory.Supplier( + 1, "Exotic Liquids", "Charlotte Cooper", "Purchasing Manager", "49 Gilbert St.", "London", null, + "EC1 4SD", "UK", "(171) 555-2222", null, null), + Orders = new List { + NorthwindDtoFactory.Order( + 1, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + NorthwindDtoFactory.Order( + 2, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + NorthwindDtoFactory.Order( + 3, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + NorthwindDtoFactory.Order( + 4, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + }, + }; + } + } + + public static ArrayDtoWithOrders ArrayDtoWithOrders + { + get + { + return new ArrayDtoWithOrders { + Id = Guid.NewGuid(), + Customer = NorthwindDtoFactory.Customer( + 1.ToString("x"), "Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", + "Berlin", null, "12209", "Germany", "030-0074321", "030-0076545", null), + Supplier = NorthwindDtoFactory.Supplier( + 1, "Exotic Liquids", "Charlotte Cooper", "Purchasing Manager", "49 Gilbert St.", "London", null, + "EC1 4SD", "UK", "(171) 555-2222", null, null), + Orders = new[] { + NorthwindDtoFactory.Order( + 1, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + NorthwindDtoFactory.Order( + 2, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + NorthwindDtoFactory.Order( + 3, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + NorthwindDtoFactory.Order( + 4, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + }, + }; + } + } + + public static CustomerOrderListDto CustomerOrderListDto + { + get + { + return new CustomerOrderListDto { + Customer = NorthwindDtoFactory.Customer( + 1.ToString("x"), "Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", + "Berlin", null, "12209", "Germany", "030-0074321", "030-0076545", null), + Orders = new List { + new FullOrderDto + { + Order = NorthwindDtoFactory.Order( + 1, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + OrderDetails = new List + { + NorthwindDtoFactory.OrderDetail(1, 1, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(1, 2, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(1, 3, 10.00m, 1, 0), + } + }, + new FullOrderDto + { + Order = NorthwindDtoFactory.Order( + 2, "VINET", 5, new DateTime(1997, 7, 4), new DateTime(1997, 1, 8), new DateTime(1997, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + OrderDetails = new List + { + NorthwindDtoFactory.OrderDetail(2, 1, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(2, 2, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(2, 3, 10.00m, 1, 0), + } + }, + new FullOrderDto + { + Order = NorthwindDtoFactory.Order( + 3, "VINET", 5, new DateTime(1998, 7, 4), new DateTime(1998, 1, 8), new DateTime(1998, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + OrderDetails = new List + { + NorthwindDtoFactory.OrderDetail(3, 1, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(3, 2, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(3, 3, 10.00m, 1, 0), + } + }, + } + }; + } + } + + + public static CustomerOrderArrayDto CustomerOrderArrayDto + { + get + { + return new CustomerOrderArrayDto { + Customer = NorthwindDtoFactory.Customer( + 1.ToString("x"), "Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", + "Berlin", null, "12209", "Germany", "030-0074321", "030-0076545", null), + Orders = new[] { + new FullOrderDto + { + Order = NorthwindDtoFactory.Order( + 1, "VINET", 5, new DateTime(1996, 7, 4), new DateTime(1996, 1, 8), new DateTime(1996, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + OrderDetails = new List + { + NorthwindDtoFactory.OrderDetail(1, 1, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(1, 2, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(1, 3, 10.00m, 1, 0), + } + }, + new FullOrderDto + { + Order = NorthwindDtoFactory.Order( + 2, "VINET", 5, new DateTime(1997, 7, 4), new DateTime(1997, 1, 8), new DateTime(1997, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + OrderDetails = new List + { + NorthwindDtoFactory.OrderDetail(2, 1, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(2, 2, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(2, 3, 10.00m, 1, 0), + } + }, + new FullOrderDto + { + Order = NorthwindDtoFactory.Order( + 3, "VINET", 5, new DateTime(1998, 7, 4), new DateTime(1998, 1, 8), new DateTime(1998, 7, 16), + 3, 32.38m, "Vins et alcools Chevalier", "59 rue de l'Abbaye", "Reims", null, "51100", "France"), + OrderDetails = new List + { + NorthwindDtoFactory.OrderDetail(3, 1, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(3, 2, 10.00m, 1, 0), + NorthwindDtoFactory.OrderDetail(3, 3, 10.00m, 1, 0), + } + }, + } + }; + } + } + + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ComplexModel/FullOrderDto.cs b/tests/Northwind.Common/ComplexModel/FullOrderDto.cs new file mode 100644 index 000000000..f6569aef0 --- /dev/null +++ b/tests/Northwind.Common/ComplexModel/FullOrderDto.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Northwind.Common.ServiceModel; +using ProtoBuf; + +namespace Northwind.Common.ComplexModel +{ + [DataContract] + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + public class FullOrderDto + { + public FullOrderDto() + { + this.OrderDetails = new List(); + } + + [DataMember] + public OrderDto Order { get; set; } + + [DataMember] + public List OrderDetails { get; set; } + + public override bool Equals(object obj) + { + var other = obj as FullOrderDto; + if (other == null) return false; + + var i = 0; + return this.Order.Equals(other.Order) + && this.OrderDetails.All(x => x.Equals(other.OrderDetails[i++])); + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ComplexModel/MultiCustomerProperties.cs b/tests/Northwind.Common/ComplexModel/MultiCustomerProperties.cs new file mode 100644 index 000000000..82c859b80 --- /dev/null +++ b/tests/Northwind.Common/ComplexModel/MultiCustomerProperties.cs @@ -0,0 +1,30 @@ +using System.Runtime.Serialization; +using Northwind.Common.ServiceModel; +using ProtoBuf; + +namespace Northwind.Common.ComplexModel +{ + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + public class MultiCustomerProperties + { + [DataMember] + public CustomerDto Customer1 { get; set; } + + [DataMember] + public CustomerDto Customer2 { get; set; } + + [DataMember] + public CustomerDto Customer3 { get; set; } + + public override bool Equals(object obj) + { + var other = obj as MultiCustomerProperties; + if (other == null) return false; + + return this.Customer1.Equals(other.Customer1) + && this.Customer2.Equals(other.Customer2) + && this.Customer3.Equals(other.Customer3); + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ComplexModel/MultiDto.cs b/tests/Northwind.Common/ComplexModel/MultiDto.cs new file mode 100644 index 000000000..0cfe1380f --- /dev/null +++ b/tests/Northwind.Common/ComplexModel/MultiDto.cs @@ -0,0 +1,24 @@ +using System; +using Northwind.Common.ServiceModel; +using ProtoBuf; + +namespace Northwind.Common.ComplexModel +{ + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + public class MultiDto + { + public Guid Id { get; set; } + public CustomerDto Customer { get; set; } + public SupplierDto Supplier { get; set; } + + public override bool Equals(object obj) + { + var other = obj as MultiDto; + if (other == null) return false; + + return this.Id == other.Id + && this.Customer.Equals(other.Customer) + && this.Supplier.Equals(other.Supplier); + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ComplexModel/MultiDtoWithOrders.cs b/tests/Northwind.Common/ComplexModel/MultiDtoWithOrders.cs new file mode 100644 index 000000000..f2dbbdf30 --- /dev/null +++ b/tests/Northwind.Common/ComplexModel/MultiDtoWithOrders.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Northwind.Common.ServiceModel; +using ProtoBuf; + +namespace Northwind.Common.ComplexModel +{ + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + public class MultiDtoWithOrders + { + public MultiDtoWithOrders() + { + Orders = new List(); + } + + [DataMember] + public Guid Id { get; set; } + + [DataMember] + public CustomerDto Customer { get; set; } + + [DataMember] + public SupplierDto Supplier { get; set; } + + [DataMember] + public List Orders { get; set; } + + public override bool Equals(object obj) + { + var other = obj as MultiDtoWithOrders; + if (other == null) return false; + + return this.Id == other.Id + && this.Customer.Equals(other.Customer) + && this.Supplier.Equals(other.Supplier) + && this.Orders.Count == other.Orders.Count; + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ComplexModel/MultiOrderProperties.cs b/tests/Northwind.Common/ComplexModel/MultiOrderProperties.cs new file mode 100644 index 000000000..e175bec46 --- /dev/null +++ b/tests/Northwind.Common/ComplexModel/MultiOrderProperties.cs @@ -0,0 +1,34 @@ +using System.Runtime.Serialization; +using Northwind.Common.ServiceModel; +using ProtoBuf; + +namespace Northwind.Common.ComplexModel +{ + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + public class MultiOrderProperties + { + [DataMember] + public OrderDto Orders1 { get; set; } + + [DataMember] + public OrderDto Orders2 { get; set; } + + [DataMember] + public OrderDto Orders3 { get; set; } + + [DataMember] + public OrderDto Orders4 { get; set; } + + public override bool Equals(object obj) + { + var other = obj as MultiOrderProperties; + if (other == null) return false; + + return this.Orders1.Equals(other.Orders1) + && this.Orders2.Equals(other.Orders2) + && this.Orders3.Equals(other.Orders3) + && this.Orders4.Equals(other.Orders4); + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/DataModel/Northwind.models.cs b/tests/Northwind.Common/DataModel/Northwind.models.cs new file mode 100644 index 000000000..17404087e --- /dev/null +++ b/tests/Northwind.Common/DataModel/Northwind.models.cs @@ -0,0 +1,759 @@ +using System; +using ServiceStack.DataAnnotations; +using ServiceStack.Model; + +namespace Northwind.Common.DataModel +{ + [Alias("Employees")] + public class Employee + : IHasIntId, IEquatable + { + [AutoIncrement] + [Alias("EmployeeID")] + public int Id { get; set; } + + [Index] + [Required] + [StringLength(20)] + public string LastName { get; set; } + + [Required] + [StringLength(10)] + public string FirstName { get; set; } + + [StringLength(30)] + public string Title { get; set; } + + [StringLength(25)] + public string TitleOfCourtesy { get; set; } + + public DateTime? BirthDate { get; set; } + + public DateTime? HireDate { get; set; } + + [StringLength(60)] + public string Address { get; set; } + + [StringLength(15)] + public string City { get; set; } + + [StringLength(15)] + public string Region { get; set; } + + [Index] + [StringLength(10)] + public string PostalCode { get; set; } + + [StringLength(15)] + public string Country { get; set; } + + [StringLength(24)] + public string HomePhone { get; set; } + + [StringLength(4)] + public string Extension { get; set; } + + public byte[] Photo { get; set; } + + [StringLength(8000)] + public string Notes { get; set; } + + [References(typeof(Employee))] + public int? ReportsTo { get; set; } + + [StringLength(255)] + public string PhotoPath { get; set; } + + public bool Equals(Employee other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(LastName, other.LastName) && string.Equals(FirstName, other.FirstName) && string.Equals(Title, other.Title) && string.Equals(TitleOfCourtesy, other.TitleOfCourtesy) && BirthDate.Equals(other.BirthDate) && HireDate.Equals(other.HireDate) && string.Equals(Address, other.Address) && string.Equals(City, other.City) && string.Equals(Region, other.Region) && string.Equals(PostalCode, other.PostalCode) && string.Equals(Country, other.Country) && string.Equals(HomePhone, other.HomePhone) && string.Equals(Extension, other.Extension) && Equals(Photo, other.Photo) && string.Equals(Notes, other.Notes) && ReportsTo == other.ReportsTo && string.Equals(PhotoPath, other.PhotoPath); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Employee)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode * 397) ^ (LastName != null ? LastName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (FirstName != null ? FirstName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Title != null ? Title.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (TitleOfCourtesy != null ? TitleOfCourtesy.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ BirthDate.GetHashCode(); + hashCode = (hashCode * 397) ^ HireDate.GetHashCode(); + hashCode = (hashCode * 397) ^ (Address != null ? Address.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (City != null ? City.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Region != null ? Region.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (PostalCode != null ? PostalCode.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Country != null ? Country.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (HomePhone != null ? HomePhone.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Extension != null ? Extension.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Photo != null ? Photo.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Notes != null ? Notes.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ ReportsTo.GetHashCode(); + hashCode = (hashCode * 397) ^ (PhotoPath != null ? PhotoPath.GetHashCode() : 0); + return hashCode; + } + } + } + + [Alias("Categories")] + public class Category + : IHasIntId, IEquatable + { + [Alias("CategoryID")] + public int Id { get; set; } + + [Index] + [Required] + [StringLength(15)] + public string CategoryName { get; set; } + + [StringLength(100)] + public string Description { get; set; } + + public byte[] Picture { get; set; } + + public bool Equals(Category other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(CategoryName, other.CategoryName) && string.Equals(Description, other.Description) && Equals(Picture, other.Picture); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Category)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode * 397) ^ (CategoryName != null ? CategoryName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Description != null ? Description.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Picture != null ? Picture.GetHashCode() : 0); + return hashCode; + } + } + } + + [Alias("Customers")] + public class Customer + : IHasStringId, IEquatable + { + [Required] + [StringLength(5)] + [Alias("CustomerID")] + public string Id { get; set; } + + [Index] + [Required] + [StringLength(40)] + public string CompanyName { get; set; } + + [StringLength(30)] + public string ContactName { get; set; } + + [StringLength(30)] + public string ContactTitle { get; set; } + + [StringLength(60)] + public string Address { get; set; } + + [Index] + [StringLength(15)] + public string City { get; set; } + + [Index] + [StringLength(15)] + public string Region { get; set; } + + [Index] + [StringLength(10)] + public string PostalCode { get; set; } + + [StringLength(15)] + public string Country { get; set; } + + [StringLength(24)] + public string Phone { get; set; } + + [StringLength(24)] + public string Fax { get; set; } + + public byte[] Picture { get; set; } + + public bool Equals(Customer other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Id, other.Id) && string.Equals(CompanyName, other.CompanyName) && string.Equals(ContactName, other.ContactName) && string.Equals(ContactTitle, other.ContactTitle) && string.Equals(Address, other.Address) && string.Equals(City, other.City) && string.Equals(Region, other.Region) && string.Equals(PostalCode, other.PostalCode) && string.Equals(Country, other.Country) && string.Equals(Phone, other.Phone) && string.Equals(Fax, other.Fax) && Equals(Picture, other.Picture); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Customer)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Id != null ? Id.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (CompanyName != null ? CompanyName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (ContactName != null ? ContactName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (ContactTitle != null ? ContactTitle.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Address != null ? Address.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (City != null ? City.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Region != null ? Region.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (PostalCode != null ? PostalCode.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Country != null ? Country.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Phone != null ? Phone.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Fax != null ? Fax.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Picture != null ? Picture.GetHashCode() : 0); + return hashCode; + } + } + } + + [Alias("Shippers")] + public class Shipper + : IHasIntId, IEquatable + { + [AutoIncrement] + [Alias("ShipperID")] + public int Id { get; set; } + + [Required] + [Index(Unique = true)] + [StringLength(40)] + public string CompanyName { get; set; } + + [StringLength(24)] + public string Phone { get; set; } + + public bool Equals(Shipper other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(CompanyName, other.CompanyName) && string.Equals(Phone, other.Phone); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Shipper)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode * 397) ^ (CompanyName != null ? CompanyName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Phone != null ? Phone.GetHashCode() : 0); + return hashCode; + } + } + } + + [Alias("Suppliers")] + public class Supplier + : IHasIntId, IEquatable + { + [AutoIncrement] + [Alias("SupplierID")] + public int Id { get; set; } + + [Index] + [Required] + [StringLength(40)] + public string CompanyName { get; set; } + + [StringLength(30)] + public string ContactName { get; set; } + + [StringLength(30)] + public string ContactTitle { get; set; } + + [StringLength(60)] + public string Address { get; set; } + + [StringLength(15)] + public string City { get; set; } + + [StringLength(15)] + public string Region { get; set; } + + [Index] + [StringLength(10)] + public string PostalCode { get; set; } + + [StringLength(15)] + public string Country { get; set; } + + [StringLength(24)] + public string Phone { get; set; } + + [StringLength(24)] + public string Fax { get; set; } + + public string HomePage { get; set; } + + public bool Equals(Supplier other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(CompanyName, other.CompanyName) && string.Equals(ContactName, other.ContactName) && string.Equals(ContactTitle, other.ContactTitle) && string.Equals(Address, other.Address) && string.Equals(City, other.City) && string.Equals(Region, other.Region) && string.Equals(PostalCode, other.PostalCode) && string.Equals(Country, other.Country) && string.Equals(Phone, other.Phone) && string.Equals(Fax, other.Fax) && string.Equals(HomePage, other.HomePage); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Supplier)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode * 397) ^ (CompanyName != null ? CompanyName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (ContactName != null ? ContactName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (ContactTitle != null ? ContactTitle.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Address != null ? Address.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (City != null ? City.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Region != null ? Region.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (PostalCode != null ? PostalCode.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Country != null ? Country.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Phone != null ? Phone.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Fax != null ? Fax.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (HomePage != null ? HomePage.GetHashCode() : 0); + return hashCode; + } + } + } + + [Alias("Orders")] + public class Order + : IHasIntId, IEquatable + { + //[AutoIncrement] + [Alias("OrderID")] + public int Id { get; set; } + + [Index] + [References(typeof(Customer))] + [Alias("CustomerID")] + [StringLength(5)] + public string CustomerId { get; set; } + + [Index] + [References(typeof(Employee))] + [Alias("EmployeeID")] + public int EmployeeId { get; set; } + + [Index] + public DateTime? OrderDate { get; set; } + + public DateTime? RequiredDate { get; set; } + + [Index] + public DateTime? ShippedDate { get; set; } + + [Index] + [References(typeof(Shipper))] + public int? ShipVia { get; set; } + + public decimal Freight { get; set; } + + [StringLength(40)] + public string ShipName { get; set; } + + [StringLength(60)] + public string ShipAddress { get; set; } + + [StringLength(15)] + public string ShipCity { get; set; } + + [StringLength(15)] + public string ShipRegion { get; set; } + + [Index] + [StringLength(10)] + public string ShipPostalCode { get; set; } + + [StringLength(15)] + public string ShipCountry { get; set; } + + public bool Equals(Order other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(CustomerId, other.CustomerId) && EmployeeId == other.EmployeeId && OrderDate.Equals(other.OrderDate) && RequiredDate.Equals(other.RequiredDate) && ShippedDate.Equals(other.ShippedDate) && ShipVia == other.ShipVia && Freight == other.Freight && string.Equals(ShipName, other.ShipName) && string.Equals(ShipAddress, other.ShipAddress) && string.Equals(ShipCity, other.ShipCity) && string.Equals(ShipRegion, other.ShipRegion) && string.Equals(ShipPostalCode, other.ShipPostalCode) && string.Equals(ShipCountry, other.ShipCountry); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Order) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode*397) ^ (CustomerId != null ? CustomerId.GetHashCode() : 0); + hashCode = (hashCode*397) ^ EmployeeId; + hashCode = (hashCode*397) ^ OrderDate.GetHashCode(); + hashCode = (hashCode*397) ^ RequiredDate.GetHashCode(); + hashCode = (hashCode*397) ^ ShippedDate.GetHashCode(); + hashCode = (hashCode*397) ^ ShipVia.GetHashCode(); + hashCode = (hashCode*397) ^ Freight.GetHashCode(); + hashCode = (hashCode*397) ^ (ShipName != null ? ShipName.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipAddress != null ? ShipAddress.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipCity != null ? ShipCity.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipRegion != null ? ShipRegion.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipPostalCode != null ? ShipPostalCode.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipCountry != null ? ShipCountry.GetHashCode() : 0); + return hashCode; + } + } + } + + [Alias("Products")] + public class Product + : IHasIntId, IEquatable + { + [AutoIncrement] + [Alias("ProductID")] + public int Id { get; set; } + + [Index] + [Required] + [StringLength(40)] + public string ProductName { get; set; } + + [Index] + [Alias("SupplierID")] + [References(typeof(Supplier))] + public int SupplierId { get; set; } + + [Index] + [Alias("CategoryID")] + [References(typeof(Category))] + public int CategoryId { get; set; } + + [StringLength(20)] + public string QuantityPerUnit { get; set; } + + [Range(0, double.MaxValue)] + public decimal UnitPrice { get; set; } + + [Range(0, double.MaxValue)] + public short UnitsInStock { get; set; } + + [Range(0, double.MaxValue)] + public short UnitsOnOrder { get; set; } + + [Range(0, double.MaxValue)] + public short ReorderLevel { get; set; } + + public bool Discontinued { get; set; } + + public bool Equals(Product other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(ProductName, other.ProductName) && SupplierId == other.SupplierId && CategoryId == other.CategoryId && string.Equals(QuantityPerUnit, other.QuantityPerUnit) && UnitPrice == other.UnitPrice && UnitsInStock == other.UnitsInStock && UnitsOnOrder == other.UnitsOnOrder && ReorderLevel == other.ReorderLevel && Discontinued == other.Discontinued; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Product) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode*397) ^ (ProductName != null ? ProductName.GetHashCode() : 0); + hashCode = (hashCode*397) ^ SupplierId; + hashCode = (hashCode*397) ^ CategoryId; + hashCode = (hashCode*397) ^ (QuantityPerUnit != null ? QuantityPerUnit.GetHashCode() : 0); + hashCode = (hashCode*397) ^ UnitPrice.GetHashCode(); + hashCode = (hashCode*397) ^ UnitsInStock.GetHashCode(); + hashCode = (hashCode*397) ^ UnitsOnOrder.GetHashCode(); + hashCode = (hashCode*397) ^ ReorderLevel.GetHashCode(); + hashCode = (hashCode*397) ^ Discontinued.GetHashCode(); + return hashCode; + } + } + } + + [Alias("Order Details")] + public class OrderDetail + : IHasStringId, IEquatable + { + public string Id => this.OrderId + "/" + this.ProductId; + + [Index] + [Alias("OrderID")] + [References(typeof(Order))] + public int OrderId { get; set; } + + [Index] + [Alias("ProductID")] + [References(typeof(Product))] + public int ProductId { get; set; } + + [Range(0, double.MaxValue)] + public decimal UnitPrice { get; set; } + + [Range(0, double.MaxValue)] + public short Quantity { get; set; } + + [Range(0, double.MaxValue)] + public double Discount { get; set; } + + public bool Equals(OrderDetail other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return OrderId == other.OrderId && ProductId == other.ProductId && UnitPrice == other.UnitPrice && Quantity == other.Quantity && Discount.Equals(other.Discount); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((OrderDetail) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = OrderId; + hashCode = (hashCode*397) ^ ProductId; + hashCode = (hashCode*397) ^ UnitPrice.GetHashCode(); + hashCode = (hashCode*397) ^ Quantity.GetHashCode(); + hashCode = (hashCode*397) ^ Discount.GetHashCode(); + return hashCode; + } + } + } + + public class CustomerCustomerDemo + : IHasStringId, IEquatable + { + [StringLength(5)] + [Alias("CustomerID")] + public string Id { get; set; } + + [StringLength(10)] + [Alias("CustomerTypeID")] + public string CustomerTypeId { get; set; } + + public bool Equals(CustomerCustomerDemo other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Id, other.Id) && string.Equals(CustomerTypeId, other.CustomerTypeId); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CustomerCustomerDemo) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Id != null ? Id.GetHashCode() : 0)*397) ^ (CustomerTypeId != null ? CustomerTypeId.GetHashCode() : 0); + } + } + } + + [Alias("CustomerDemographics")] + public class CustomerDemographic + : IHasStringId, IEquatable + { + [StringLength(10)] + [Alias("CustomerTypeID")] + public string Id { get; set; } + + public string CustomerDesc { get; set; } + + public bool Equals(CustomerDemographic other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Id, other.Id) && string.Equals(CustomerDesc, other.CustomerDesc); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CustomerDemographic) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Id != null ? Id.GetHashCode() : 0)*397) ^ (CustomerDesc != null ? CustomerDesc.GetHashCode() : 0); + } + } + } + + public class Region + : IHasIntId, IEquatable + { + [Alias("RegionID")] + public int Id { get; set; } + + [Required] + [StringLength(50)] + public string RegionDescription { get; set; } + + public bool Equals(Region other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(RegionDescription, other.RegionDescription); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Region) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Id*397) ^ (RegionDescription != null ? RegionDescription.GetHashCode() : 0); + } + } + } + + [Alias("Territories")] + public class Territory + : IHasStringId, IEquatable + { + [StringLength(20)] + [Alias("TerritoryID")] + public string Id { get; set; } + + [Required] + [StringLength(50)] + public string TerritoryDescription { get; set; } + + [Alias("RegionID")] + [References(typeof(Region))] + public int RegionId { get; set; } + + public bool Equals(Territory other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Id, other.Id) && string.Equals(TerritoryDescription, other.TerritoryDescription) && RegionId == other.RegionId; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Territory) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Id != null ? Id.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (TerritoryDescription != null ? TerritoryDescription.GetHashCode() : 0); + hashCode = (hashCode*397) ^ RegionId; + return hashCode; + } + } + } + + [Alias("EmployeeTerritories")] + public class EmployeeTerritory + : IHasStringId, IEquatable + { + public string Id { get { return this.EmployeeId + "/" + this.TerritoryId; } } + + [Alias("EmployeeID")] + public int EmployeeId { get; set; } + + [Required] + [StringLength(20)] + [Alias("TerritoryID")] + public string TerritoryId { get; set; } + + public bool Equals(EmployeeTerritory other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return EmployeeId == other.EmployeeId && string.Equals(TerritoryId, other.TerritoryId); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((EmployeeTerritory) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (EmployeeId*397) ^ (TerritoryId != null ? TerritoryId.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/DataModel/NorthwindData.cs b/tests/Northwind.Common/DataModel/NorthwindData.cs new file mode 100644 index 000000000..1d00afa4a --- /dev/null +++ b/tests/Northwind.Common/DataModel/NorthwindData.cs @@ -0,0 +1,3622 @@ +using System; +using System.Collections.Generic; + +namespace Northwind.Common.DataModel +{ + public static class NorthwindData + { + private static bool LoadImages; + + static byte[] GetImage(int id) + { + return LoadImages + ? NorthwindResources.GetImage(id) + : null; + } + + public static void LoadData(bool loadImages) + { + LoadImages = loadImages; + + Func getImage; + + getImage = GetImage; + Categories = new List { + NorthwindFactory.Category(1, "Beverages", "Soft drinks, coffees, teas, beers, and ales", getImage(1)), + NorthwindFactory.Category(2, "Condiments","Sweet and savory sauces, relishes, spreads, and seasonings",getImage(2)), + NorthwindFactory.Category(3, "Confections","Desserts, candies, and sweet breads",getImage(3)), + NorthwindFactory.Category(4, "Dairy Products","Cheeses",getImage(4)), + NorthwindFactory.Category(5, "Grains/Cereals","Breads, crackers, pasta, and cereal",getImage(5)), + NorthwindFactory.Category(6, "Meat/Poultry","Prepared meats",getImage(6)), + NorthwindFactory.Category(7, "Produce","Dried fruit and bean curd",getImage(7)), + NorthwindFactory.Category(8, "Seafood","Seaweed and fish",getImage(8)), + }; + + Customers = new List { + NorthwindFactory.Customer("ALFKI","Alfreds Futterkiste","Maria Anders","Sales Representative","Obere Str. 57","Berlin",null,"12209","Germany","030-0074321","030-0076545", null), + NorthwindFactory.Customer("ANATR","Ana Trujillo Emparedados y helados","Ana Trujillo","Owner","Avda. de la Constitucin 2222","Mxico D.F.",null,"05021","Mexico","(5) 555-4729","(5) 555-3745", null), + NorthwindFactory.Customer("ANTON","Antonio Moreno Taquera","Antonio Moreno","Owner","Mataderos 2312","Mxico D.F.",null,"05023","Mexico","(5) 555-3932",null, null), + NorthwindFactory.Customer("AROUT","Around the Horn","Thomas Hardy","Sales Representative","120 Hanover Sq.","London",null,"WA1 1DP","UK","(171) 555-7788","(171) 555-6750", null), + NorthwindFactory.Customer("BERGS","Berglunds snabbkp","Christina Berglund","Order Administrator","Berguvsvgen 8","Lule",null,"S-958 22","Sweden","0921-12 34 65","0921-12 34 67", null), + NorthwindFactory.Customer("BLAUS","Blauer See Delikatessen","Hanna Moos","Sales Representative","Forsterstr. 57","Mannheim",null,"68306","Germany","0621-08460","0621-08924", null), + NorthwindFactory.Customer("BLONP","Blondesddsl pre et fils","Frdrique Citeaux","Marketing Manager","24, place Klber","Strasbourg",null,"67000","France","88.60.15.31","88.60.15.32", null), + NorthwindFactory.Customer("BOLID","Blido Comidas preparadas","Martn Sommer","Owner","C/ Araquil, 67","Madrid",null,"28023","Spain","(91) 555 22 82","(91) 555 91 99", null), + NorthwindFactory.Customer("BONAP","Bon app","Laurence Lebihan","Owner","12, rue des Bouchers","Marseille",null,"13008","France","91.24.45.40","91.24.45.41", null), + NorthwindFactory.Customer("BOTTM","Bottom-Dollar Markets","Elizabeth Lincoln","Accounting Manager","23 Tsawassen Blvd.","Tsawassen","BC","T2F 8M4","Canada","(604) 555-4729","(604) 555-3745", null), + + NorthwindFactory.Customer("BSBEV","B's Beverages","Victoria Ashworth","Sales Representative","Fauntleroy Circus","London",null,"EC2 5NT","UK","(171) 555-1212",null, null), + NorthwindFactory.Customer("CACTU","Cactus Comidas para llevar","Patricio Simpson","Sales Agent","Cerrito 333","Buenos Aires",null,"1010","Argentina","(1) 135-5555","(1) 135-4892", null), + NorthwindFactory.Customer("CENTC","Centro comercial Moctezuma","Francisco Chang","Marketing Manager","Sierras de Granada 9993","Mxico D.F.",null,"05022","Mexico","(5) 555-3392","(5) 555-7293", null), + NorthwindFactory.Customer("CHOPS","Chop-suey Chinese","Yang Wang","Owner","Hauptstr. 29","Bern",null,"3012","Switzerland","0452-076545",null, null), + NorthwindFactory.Customer("COMMI","Comrcio Mineiro","Pedro Afonso","Sales Associate","Av. dos Lusadas, 23","Sao Paulo","SP","05432-043","Brazil","(11) 555-7647",null, null), + NorthwindFactory.Customer("CONSH","Consolidated Holdings","Elizabeth Brown","Sales Representative","Berkeley Gardens 12 Brewery","London",null,"WX1 6LT","UK","(171) 555-2282","(171) 555-9199", null), + NorthwindFactory.Customer("DRACD","Drachenblut Delikatessen","Sven Ottlieb","Order Administrator","Walserweg 21","Aachen",null,"52066","Germany","0241-039123","0241-059428", null), + NorthwindFactory.Customer("DUMON","Du monde entier","Janine Labrune","Owner","67, rue des Cinquante Otages","Nantes",null,"44000","France","40.67.88.88","40.67.89.89", null), + NorthwindFactory.Customer("EASTC","Eastern Connection","Ann Devon","Sales Agent","35 King George","London",null,"WX3 6FW","UK","(171) 555-0297","(171) 555-3373", null), + NorthwindFactory.Customer("ERNSH","Ernst Handel","Roland Mendel","Sales Manager","Kirchgasse 6","Graz",null,"8010","Austria","7675-3425","7675-3426", null), + + NorthwindFactory.Customer("FAMIA","Familia Arquibaldo","Aria Cruz","Marketing Assistant","Rua Ors, 92","Sao Paulo","SP","05442-030","Brazil","(11) 555-9857",null, null), + NorthwindFactory.Customer("FISSA","FISSA Fabrica Inter. Salchichas S.A.","Diego Roel","Accounting Manager","C/ Moralzarzal, 86","Madrid",null,"28034","Spain","(91) 555 94 44","(91) 555 55 93", null), + NorthwindFactory.Customer("FOLIG","Folies gourmandes","Martine Ranc","Assistant Sales Agent","184, chausse de Tournai","Lille",null,"59000","France","20.16.10.16","20.16.10.17", null), + NorthwindFactory.Customer("FOLKO","Folk och f HB","Maria Larsson","Owner","kergatan 24","Brcke",null,"S-844 67","Sweden","0695-34 67 21",null, null), + NorthwindFactory.Customer("FRANK","Frankenversand","Peter Franken","Marketing Manager","Berliner Platz 43","Mnchen",null,"80805","Germany","089-0877310","089-0877451", null), + NorthwindFactory.Customer("FRANR","France restauration","Carine Schmitt","Marketing Manager","54, rue Royale","Nantes",null,"44000","France","40.32.21.21","40.32.21.20", null), + NorthwindFactory.Customer("FRANS","Franchi S.p.A.","Paolo Accorti","Sales Representative","Via Monte Bianco 34","Torino",null,"10100","Italy","011-4988260","011-4988261", null), + NorthwindFactory.Customer("FURIB","Furia Bacalhau e Frutos do Mar","Lino Rodriguez","Sales Manager","Jardim das rosas n. 32","Lisboa",null,"1675","Portugal","(1) 354-2534","(1) 354-2535", null), + NorthwindFactory.Customer("GALED","Galera del gastrnomo","Eduardo Saavedra","Marketing Manager","Rambla de Catalua, 23","Barcelona",null,"08022","Spain","(93) 203 4560","(93) 203 4561", null), + NorthwindFactory.Customer("GODOS","Godos Cocina Tpica","Jos Pedro Freyre","Sales Manager","C/ Romero, 33","Sevilla",null,"41101","Spain","(95) 555 82 82",null, null), + + NorthwindFactory.Customer("GOURL","Gourmet Lanchonetes","Andr Fonseca","Sales Associate","Av. Brasil, 442","Campinas","SP","04876-786","Brazil","(11) 555-9482",null, null), + NorthwindFactory.Customer("GREAL","Great Lakes Food Market","Howard Snyder","Marketing Manager","2732 Baker Blvd.","Eugene","OR","97403","USA","(503) 555-7555",null, null), + NorthwindFactory.Customer("GROSR","GROSELLA-Restaurante","Manuel Pereira","Owner","5 Ave. Los Palos Grandes","Caracas","DF","1081","Venezuela","(2) 283-2951","(2) 283-3397", null), + NorthwindFactory.Customer("HANAR","Hanari Carnes","Mario Pontes","Accounting Manager","Rua do Pao, 67","Rio de Janeiro","RJ","05454-876","Brazil","(21) 555-0091","(21) 555-8765", null), + NorthwindFactory.Customer("HILAA","HILARION-Abastos","Carlos Hernndez","Sales Representative","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal","Tchira","5022","Venezuela","(5) 555-1340","(5) 555-1948", null), + NorthwindFactory.Customer("HUNGC","Hungry Coyote Import Store","Yoshi Latimer","Sales Representative","City Center Plaza 516 Main St.","Elgin","OR","97827","USA","(503) 555-6874","(503) 555-2376", null), + NorthwindFactory.Customer("HUNGO","Hungry Owl All-Night Grocers","Patricia McKenna","Sales Associate","8 Johnstown Road","Cork","Co. Cork",null,"Ireland","2967 542","2967 3333", null), + NorthwindFactory.Customer("ISLAT","Island Trading","Helen Bennett","Marketing Manager","Garden House Crowther Way","Cowes","Isle of Wight","PO31 7PJ","UK","(198) 555-8888",null, null), + NorthwindFactory.Customer("KOENE","Kniglich Essen","Philip Cramer","Sales Associate","Maubelstr. 90","Brandenburg",null,"14776","Germany","0555-09876",null, null), + NorthwindFactory.Customer("LACOR","La corne d'abondance","Daniel Tonini","Sales Representative","67, avenue de l'Europe","Versailles",null,"78000","France","30.59.84.10","30.59.85.11", null), + + NorthwindFactory.Customer("LAMAI","La maison d'Asie","Annette Roulet","Sales Manager","1 rue Alsace-Lorraine","Toulouse",null,"31000","France","61.77.61.10","61.77.61.11", null), + NorthwindFactory.Customer("LAUGB","Laughing Bacchus Wine Cellars","Yoshi Tannamuri","Marketing Assistant","1900 Oak St.","Vancouver","BC","V3F 2K1","Canada","(604) 555-3392","(604) 555-7293", null), + NorthwindFactory.Customer("LAZYK","Lazy K Kountry Store","John Steel","Marketing Manager","12 Orchestra Terrace","Walla Walla","WA","99362","USA","(509) 555-7969","(509) 555-6221", null), + NorthwindFactory.Customer("LEHMS","Lehmanns Marktstand","Renate Messner","Sales Representative","Magazinweg 7","Frankfurt a.M.",null,"60528","Germany","069-0245984","069-0245874", null), + NorthwindFactory.Customer("LETSS","Let's Stop N Shop","Jaime Yorres","Owner","87 Polk St. Suite 5","San Francisco","CA","94117","USA","(415) 555-5938",null, null), + NorthwindFactory.Customer("LILAS","LILA-Supermercado","Carlos Gonzlez","Accounting Manager","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto","Lara","3508","Venezuela","(9) 331-6954","(9) 331-7256", null), + NorthwindFactory.Customer("LINOD","LINO-Delicateses","Felipe Izquierdo","Owner","Ave. 5 de Mayo Porlamar","I. de Margarita","Nueva Esparta","4980","Venezuela","(8) 34-56-12","(8) 34-93-93", null), + NorthwindFactory.Customer("LONEP","Lonesome Pine Restaurant","Fran Wilson","Sales Manager","89 Chiaroscuro Rd.","Portland","OR","97219","USA","(503) 555-9573","(503) 555-9646", null), + NorthwindFactory.Customer("MAGAA","Magazzini Alimentari Riuniti","Giovanni Rovelli","Marketing Manager","Via Ludovico il Moro 22","Bergamo",null,"24100","Italy","035-640230","035-640231", null), + NorthwindFactory.Customer("MAISD","Maison Dewey","Catherine Dewey","Sales Agent","Rue Joseph-Bens 532","Bruxelles",null,"B-1180","Belgium","(02) 201 24 67","(02) 201 24 68", null), + + NorthwindFactory.Customer("MEREP","Mre Paillarde","Jean Fresnire","Marketing Assistant","43 rue St. Laurent","Montral","Qubec","H1J 1C3","Canada","(514) 555-8054","(514) 555-8055", null), + NorthwindFactory.Customer("MORGK","Morgenstern Gesundkost","Alexander Feuer","Marketing Assistant","Heerstr. 22","Leipzig",null,"04179","Germany","0342-023176",null, null), + NorthwindFactory.Customer("NORTS","North/South","Simon Crowther","Sales Associate","South House 300 Queensbridge","London",null,"SW7 1RZ","UK","(171) 555-7733","(171) 555-2530", null), + NorthwindFactory.Customer("OCEAN","Ocano Atlntico Ltda.","Yvonne Moncada","Sales Agent","Ing. Gustavo Moncada 8585 Piso 20-A","Buenos Aires",null,"1010","Argentina","(1) 135-5333","(1) 135-5535", null), + NorthwindFactory.Customer("OLDWO","Old World Delicatessen","Rene Phillips","Sales Representative","2743 Bering St.","Anchorage","AK","99508","USA","(907) 555-7584","(907) 555-2880", null), + NorthwindFactory.Customer("OTTIK","Ottilies Kseladen","Henriette Pfalzheim","Owner","Mehrheimerstr. 369","Kln",null,"50739","Germany","0221-0644327","0221-0765721", null), + NorthwindFactory.Customer("PARIS","Paris spcialits","Marie Bertrand","Owner","265, boulevard Charonne","Paris",null,"75012","France","(1) 42.34.22.66","(1) 42.34.22.77", null), + NorthwindFactory.Customer("PERIC","Pericles Comidas clsicas","Guillermo Fernndez","Sales Representative","Calle Dr. Jorge Cash 321","Mxico D.F.",null,"05033","Mexico","(5) 552-3745","(5) 545-3745", null), + NorthwindFactory.Customer("PICCO","Piccolo und mehr","Georg Pipps","Sales Manager","Geislweg 14","Salzburg",null,"5020","Austria","6562-9722","6562-9723", null), + NorthwindFactory.Customer("PRINI","Princesa Isabel Vinhos","Isabel de Castro","Sales Representative","Estrada da sade n. 58","Lisboa",null,"1756","Portugal","(1) 356-5634",null, null), + + NorthwindFactory.Customer("QUEDE","Que Delcia","Bernardo Batista","Accounting Manager","Rua da Panificadora, 12","Rio de Janeiro","RJ","02389-673","Brazil","(21) 555-4252","(21) 555-4545", null), + NorthwindFactory.Customer("QUEEN","Queen Cozinha","Lcia Carvalho","Marketing Assistant","Alameda dos Canrios, 891","Sao Paulo","SP","05487-020","Brazil","(11) 555-1189",null, null), + NorthwindFactory.Customer("QUICK","QUICK-Stop","Horst Kloss","Accounting Manager","Taucherstrae 10","Cunewalde",null,"01307","Germany","0372-035188",null, null), + NorthwindFactory.Customer("RANCH","Rancho grande","Sergio Gutirrez","Sales Representative","Av. del Libertador 900","Buenos Aires",null,"1010","Argentina","(1) 123-5555","(1) 123-5556", null), + NorthwindFactory.Customer("RATTC","Rattlesnake Canyon Grocery","Paula Wilson","Assistant Sales Representative","2817 Milton Dr.","Albuquerque","NM","87110","USA","(505) 555-5939","(505) 555-3620", null), + NorthwindFactory.Customer("REGGC","Reggiani Caseifici","Maurizio Moroni","Sales Associate","Strada Provinciale 124","Reggio Emilia",null,"42100","Italy","0522-556721","0522-556722", null), + NorthwindFactory.Customer("RICAR","Ricardo Adocicados","Janete Limeira","Assistant Sales Agent","Av. Copacabana, 267","Rio de Janeiro","RJ","02389-890","Brazil","(21) 555-3412",null, null), + NorthwindFactory.Customer("RICSU","Richter Supermarkt","Michael Holz","Sales Manager","Grenzacherweg 237","Genve",null,"1203","Switzerland","0897-034214",null, null), + NorthwindFactory.Customer("ROMEY","Romero y tomillo","Alejandra Camino","Accounting Manager","Gran Va, 1","Madrid",null,"28001","Spain","(91) 745 6200","(91) 745 6210", null), + NorthwindFactory.Customer("SANTG","Sant Gourmet","Jonas Bergulfsen","Owner","Erling Skakkes gate 78","Stavern",null,"4110","Norway","07-98 92 35","07-98 92 47", null), + + NorthwindFactory.Customer("SAVEA","Save-a-lot Markets","Jose Pavarotti","Sales Representative","187 Suffolk Ln.","Boise","ID","83720","USA","(208) 555-8097",null, null), + NorthwindFactory.Customer("SEVES","Seven Seas Imports","Hari Kumar","Sales Manager","90 Wadhurst Rd.","London",null,"OX15 4NB","UK","(171) 555-1717","(171) 555-5646", null), + NorthwindFactory.Customer("SIMOB","Simons bistro","Jytte Petersen","Owner","Vinbltet 34","Kobenhavn",null,"1734","Denmark","31 12 34 56","31 13 35 57", null), + NorthwindFactory.Customer("SPECD","Spcialits du monde","Dominique Perrier","Marketing Manager","25, rue Lauriston","Paris",null,"75016","France","(1) 47.55.60.10","(1) 47.55.60.20", null), + NorthwindFactory.Customer("SPLIR","Split Rail Beer & Ale","Art Braunschweiger","Sales Manager","P.O. Box 555","Lander","WY","82520","USA","(307) 555-4680","(307) 555-6525", null), + NorthwindFactory.Customer("SUPRD","Suprmes dlices","Pascale Cartrain","Accounting Manager","Boulevard Tirou, 255","Charleroi",null,"B-6000","Belgium","(071) 23 67 22 20","(071) 23 67 22 21", null), + NorthwindFactory.Customer("THEBI","The Big Cheese","Liz Nixon","Marketing Manager","89 Jefferson Way Suite 2","Portland","OR","97201","USA","(503) 555-3612",null, null), + NorthwindFactory.Customer("THECR","The Cracker Box","Liu Wong","Marketing Assistant","55 Grizzly Peak Rd.","Butte","MT","59801","USA","(406) 555-5834","(406) 555-8083", null), + NorthwindFactory.Customer("TOMSP","Toms Spezialitten","Karin Josephs","Marketing Manager","Luisenstr. 48","Mnster",null,"44087","Germany","0251-031259","0251-035695", null), + NorthwindFactory.Customer("TORTU","Tortuga Restaurante","Miguel Angel Paolino","Owner","Avda. Azteca 123","Mxico D.F.",null,"05033","Mexico","(5) 555-2933",null, null), + + NorthwindFactory.Customer("TRADH","Tradio Hipermercados","Anabela Domingues","Sales Representative","Av. Ins de Castro, 414","Sao Paulo","SP","05634-030","Brazil","(11) 555-2167","(11) 555-2168", null), + NorthwindFactory.Customer("TRAIH","Trail's Head Gourmet Provisioners","Helvetius Nagy","Sales Associate","722 DaVinci Blvd.","Kirkland","WA","98034","USA","(206) 555-8257","(206) 555-2174", null), + NorthwindFactory.Customer("VAFFE","Vaffeljernet","Palle Ibsen","Sales Manager","Smagsloget 45","rhus",null,"8200","Denmark","86 21 32 43","86 22 33 44", null), + NorthwindFactory.Customer("VICTE","Victuailles en stock","Mary Saveley","Sales Agent","2, rue du Commerce","Lyon",null,"69004","France","78.32.54.86","78.32.54.87", null), + NorthwindFactory.Customer("VINET","Vins et alcools Chevalier","Paul Henriot","Accounting Manager","59 rue de l'Abbaye","Reims",null,"51100","France","26.47.15.10","26.47.15.11", null), + NorthwindFactory.Customer("WANDK","Die Wandernde Kuh","Rita Mller","Sales Representative","Adenauerallee 900","Stuttgart",null,"70563","Germany","0711-020361","0711-035428", null), + NorthwindFactory.Customer("WARTH","Wartian Herkku","Pirkko Koskitalo","Accounting Manager","Torikatu 38","Oulu",null,"90110","Finland","981-443655","981-443655", null), + NorthwindFactory.Customer("WELLI","Wellington Importadora","Paula Parente","Sales Manager","Rua do Mercado, 12","Resende","SP","08737-363","Brazil","(14) 555-8122",null, null), + NorthwindFactory.Customer("WHITC","White Clover Markets","Karl Jablonski","Owner","305 - 14th Ave. S. Suite 3B","Seattle","WA","98128","USA","(206) 555-4112","(206) 555-4115", null), + NorthwindFactory.Customer("WILMK","Wilman Kala","Matti Karttunen","Owner/Marketing Assistant","Keskuskatu 45","Helsinki",null,"21240","Finland","90-224 8858","90-224 8858", null), + + NorthwindFactory.Customer("WOLZA","Wolski Zajazd","Zbyszek Piestrzeniewicz","Owner","ul. Filtrowa 68","Warszawa",null,"01-012","Poland","(26) 642-7012","(26) 642-7012", null), + }; + + getImage = GetImage; + Employees = new List { + NorthwindFactory.Employee(1,"Fuller","Andrew","Vice President, Sales","Dr.",ToDateTime("02/19/1952"),ToDateTime("08/14/1992"),"908 W. Capital Way","Tacoma","WA","98401","USA","(206) 555-9482","3457",getImage(1),"Andrew received his BTS commercial in 1974 and a Ph.D. in international marketing from the University of Dallas in 1981. He is fluent in French and Italian and reads German. He joined the company as a sales representative, was promoted to sales manager in January 1992 and to vice president of sales in March 1993. Andrew is a member of the Sales Management Roundtable, the Seattle Chamber of Commerce, and the Pacific Rim Importers Association.",null,"http://accweb/emmployees/fuller.bmp"), + NorthwindFactory.Employee(2,"Davolio","Nancy","Sales Representative","Ms.",ToDateTime("12/08/1948"),ToDateTime("05/01/1992"),"507 - 20th Ave. E. Apt. 2A","Seattle","WA","98122","USA","(206) 555-9857","5467",getImage(2),"Education includes a BA in psychology from Colorado State University in 1970. She also completed 'The Art of the Cold Call.' Nancy is a member of Toastmasters International.",1,"http://accweb/emmployees/davolio.bmp"), + NorthwindFactory.Employee(3,"Leverling","Janet","Sales Representative","Ms.",ToDateTime("08/30/1963"),ToDateTime("04/01/1992"),"722 Moss Bay Blvd.","Kirkland","WA","98033","USA","(206) 555-3412","3355",getImage(3),"Janet has a BS degree in chemistry from Boston College (1984). She has also completed a certificate program in food retailing management. Janet was hired as a sales associate in 1991 and promoted to sales representative in February 1992.",1,"http://accweb/emmployees/leverling.bmp"), + NorthwindFactory.Employee(4,"Peacock","Margaret","Sales Representative","Mrs.",ToDateTime("09/19/1937"),ToDateTime("05/03/1993"),"4110 Old Redmond Rd.","Redmond","WA","98052","USA","(206) 555-8122","5176",getImage(4),"Margaret holds a BA in English literature from Concordia College (1958) and an MA from the American Institute of Culinary Arts (1966). She was assigned to the London office temporarily from July through November 1992.",1,"http://accweb/emmployees/peacock.bmp"), + NorthwindFactory.Employee(5,"Buchanan","Steven","Sales Manager","Mr.",ToDateTime("03/04/1955"),ToDateTime("10/17/1993"),"14 Garrett Hill","London",null,"SW1 8JR","UK","(71) 555-4848","3453",getImage(5),"Steven Buchanan graduated from St. Andrews University, Scotland, with a BSC degree in 1976. Upon joining the company as a sales representative in 1992, he spent 6 months in an orientation program at the Seattle office and then returned to his permanent post in London. He was promoted to sales manager in March 1993. Mr. Buchanan has completed the courses 'Successful Telemarketing' and 'International Sales Management.' He is fluent in French.",1,"http://accweb/emmployees/buchanan.bmp"), + NorthwindFactory.Employee(6,"Suyama","Michael","Sales Representative","Mr.",ToDateTime("07/02/1963"),ToDateTime("10/17/1993"),"Coventry House Miner Rd.","London",null,"EC2 7JR","UK","(71) 555-7773","428",getImage(6),"Michael is a graduate of Sussex University (MA, economics, 1983) and the University of California at Los Angeles (MBA, marketing, 1986). He has also taken the courses 'Multi-Cultural Selling' and 'Time Management for the Sales Professional.' He is fluent in Japanese and can read and write French, Portuguese, and Spanish.",5,"http://accweb/emmployees/davolio.bmp"), + NorthwindFactory.Employee(7,"King","Robert","Sales Representative","Mr.",ToDateTime("05/29/1960"),ToDateTime("01/02/1994"),"Edgeham Hollow Winchester Way","London",null,"RG1 9SP","UK","(71) 555-5598","465",getImage(7),"Robert King served in the Peace Corps and traveled extensively before completing his degree in English at the University of Michigan in 1992, the year he joined the company. After completing a course entitled 'Selling in Europe,' he was transferred to the London office in March 1993.",5,"http://accweb/emmployees/davolio.bmp"), + NorthwindFactory.Employee(8,"Callahan","Laura","Inside Sales Coordinator","Ms.",ToDateTime("01/09/1958"),ToDateTime("03/05/1994"),"4726 - 11th Ave. N.E.","Seattle","WA","98105","USA","(206) 555-1189","2344",getImage(8),"Laura received a BA in psychology from the University of Washington. She has also completed a course in business French. She reads and writes French.",1,"http://accweb/emmployees/davolio.bmp"), + NorthwindFactory.Employee(9,"Dodsworth","Anne","Sales Representative","Ms.",ToDateTime("01/27/1966"),ToDateTime("11/15/1994"),"7 Houndstooth Rd.","London",null,"WG2 7LT","UK","(71) 555-4444","452",getImage(9),"Anne has a BA degree in English from St. Lawrence College. She is fluent in French and German.",5,"http://accweb/emmployees/davolio.bmp"), + }; + + OrderDetails = new List { + NorthwindFactory.OrderDetail(10248,11,14,12,0), + NorthwindFactory.OrderDetail(10248,42,9.8m,10,0), + NorthwindFactory.OrderDetail(10248,72,34.8m,5,0), + NorthwindFactory.OrderDetail(10249,14,18.6m,9,0), + NorthwindFactory.OrderDetail(10249,51,42.4m,40,0), + NorthwindFactory.OrderDetail(10250,41,7.7m,10,0), + NorthwindFactory.OrderDetail(10250,51,42.4m,35,0.15), + NorthwindFactory.OrderDetail(10250,65,16.8m,15,0.15), + NorthwindFactory.OrderDetail(10251,22,16.8m,6,0.05), + NorthwindFactory.OrderDetail(10251,57,15.6m,15,0.05), + + NorthwindFactory.OrderDetail(10251,65,16.8m,20,0), + NorthwindFactory.OrderDetail(10252,20,64.8m,40,0.05), + NorthwindFactory.OrderDetail(10252,33,2,25,0.05), + NorthwindFactory.OrderDetail(10252,60,27.2m,40,0), + NorthwindFactory.OrderDetail(10253,31,10,20,0), + NorthwindFactory.OrderDetail(10253,39,14.4m,42,0), + NorthwindFactory.OrderDetail(10253,49,16,40,0), + NorthwindFactory.OrderDetail(10254,24,3.6m,15,0.15), + NorthwindFactory.OrderDetail(10254,55,19.2m,21,0.15), + NorthwindFactory.OrderDetail(10254,74,8,21,0), + + NorthwindFactory.OrderDetail(10255,2,15.2m,20,0), + NorthwindFactory.OrderDetail(10255,16,13.9m,35,0), + NorthwindFactory.OrderDetail(10255,36,15.2m,25,0), + NorthwindFactory.OrderDetail(10255,59,44,30,0), + NorthwindFactory.OrderDetail(10256,53,26.2m,15,0), + NorthwindFactory.OrderDetail(10256,77,10.4m,12,0), + NorthwindFactory.OrderDetail(10257,27,35.1m,25,0), + NorthwindFactory.OrderDetail(10257,39,14.4m,6,0), + NorthwindFactory.OrderDetail(10257,77,10.4m,15,0), + NorthwindFactory.OrderDetail(10258,2,15.2m,50,0.2), + + NorthwindFactory.OrderDetail(10258,5,17,65,0.2), + NorthwindFactory.OrderDetail(10258,32,25.6m,6,0.2), + NorthwindFactory.OrderDetail(10259,21,8,10,0), + NorthwindFactory.OrderDetail(10259,37,20.8m,1,0), + NorthwindFactory.OrderDetail(10260,41,7.7m,16,0.25), + NorthwindFactory.OrderDetail(10260,57,15.6m,50,0), + NorthwindFactory.OrderDetail(10260,62,39.4m,15,0.25), + NorthwindFactory.OrderDetail(10260,70,12,21,0.25), + NorthwindFactory.OrderDetail(10261,21,8,20,0), + NorthwindFactory.OrderDetail(10261,35,14.4m,20,0), + + NorthwindFactory.OrderDetail(10262,5,17,12,0.2), + NorthwindFactory.OrderDetail(10262,7,24,15,0), + NorthwindFactory.OrderDetail(10262,56,30.4m,2,0), + NorthwindFactory.OrderDetail(10263,16,13.9m,60,0.25), + NorthwindFactory.OrderDetail(10263,24,3.6m,28,0), + NorthwindFactory.OrderDetail(10263,30,20.7m,60,0.25), + NorthwindFactory.OrderDetail(10263,74,8,36,0.25), + NorthwindFactory.OrderDetail(10264,2,15.2m,35,0), + NorthwindFactory.OrderDetail(10264,41,7.7m,25,0.15), + NorthwindFactory.OrderDetail(10265,17,31.2m,30,0), + + NorthwindFactory.OrderDetail(10265,70,12,20,0), + NorthwindFactory.OrderDetail(10266,12,30.4m,12,0.05), + NorthwindFactory.OrderDetail(10267,40,14.7m,50,0), + NorthwindFactory.OrderDetail(10267,59,44,70,0.15), + NorthwindFactory.OrderDetail(10267,76,14.4m,15,0.15), + NorthwindFactory.OrderDetail(10268,29,99,10,0), + NorthwindFactory.OrderDetail(10268,72,27.8m,4,0), + NorthwindFactory.OrderDetail(10269,33,2,60,0.05), + NorthwindFactory.OrderDetail(10269,72,27.8m,20,0.05), + NorthwindFactory.OrderDetail(10270,36,15.2m,30,0), + + NorthwindFactory.OrderDetail(10270,43,36.8m,25,0), + NorthwindFactory.OrderDetail(10271,33,2,24,0), + NorthwindFactory.OrderDetail(10272,20,64.8m,6,0), + NorthwindFactory.OrderDetail(10272,31,10,40,0), + NorthwindFactory.OrderDetail(10272,72,27.8m,24,0), + NorthwindFactory.OrderDetail(10273,10,24.8m,24,0.05), + NorthwindFactory.OrderDetail(10273,31,10,15,0.05), + NorthwindFactory.OrderDetail(10273,33,2,20,0), + NorthwindFactory.OrderDetail(10273,40,14.7m,60,0.05), + NorthwindFactory.OrderDetail(10273,76,14.4m,33,0.05), + + NorthwindFactory.OrderDetail(10274,71,17.2m,20,0), + NorthwindFactory.OrderDetail(10274,72,27.8m,7,0), + NorthwindFactory.OrderDetail(10275,24,3.6m,12,0.05), + NorthwindFactory.OrderDetail(10275,59,44,6,0.05), + NorthwindFactory.OrderDetail(10276,10,24.8m,15,0), + NorthwindFactory.OrderDetail(10276,13,4.8m,10,0), + NorthwindFactory.OrderDetail(10277,28,36.4m,20,0), + NorthwindFactory.OrderDetail(10277,62,39.4m,12,0), + NorthwindFactory.OrderDetail(10278,44,15.5m,16,0), + NorthwindFactory.OrderDetail(10278,59,44,15,0), + + NorthwindFactory.OrderDetail(10278,63,35.1m,8,0), + NorthwindFactory.OrderDetail(10278,73,12,25,0), + NorthwindFactory.OrderDetail(10279,17,31.2m,15,0.25), + NorthwindFactory.OrderDetail(10280,24,3.6m,12,0), + NorthwindFactory.OrderDetail(10280,55,19.2m,20,0), + NorthwindFactory.OrderDetail(10280,75,6.2m,30,0), + NorthwindFactory.OrderDetail(10281,19,7.3m,1,0), + NorthwindFactory.OrderDetail(10281,24,3.6m,6,0), + NorthwindFactory.OrderDetail(10281,35,14.4m,4,0), + NorthwindFactory.OrderDetail(10282,30,20.7m,6,0), + + NorthwindFactory.OrderDetail(10282,57,15.6m,2,0), + NorthwindFactory.OrderDetail(10283,15,12.4m,20,0), + NorthwindFactory.OrderDetail(10283,19,7.3m,18,0), + NorthwindFactory.OrderDetail(10283,60,27.2m,35,0), + NorthwindFactory.OrderDetail(10283,72,27.8m,3,0), + NorthwindFactory.OrderDetail(10284,27,35.1m,15,0.25), + NorthwindFactory.OrderDetail(10284,44,15.5m,21,0), + NorthwindFactory.OrderDetail(10284,60,27.2m,20,0.25), + NorthwindFactory.OrderDetail(10284,67,11.2m,5,0.25), + NorthwindFactory.OrderDetail(10285,1,14.4m,45,0.2), + + NorthwindFactory.OrderDetail(10285,40,14.7m,40,0.2), + NorthwindFactory.OrderDetail(10285,53,26.2m,36,0.2), + NorthwindFactory.OrderDetail(10286,35,14.4m,100,0), + NorthwindFactory.OrderDetail(10286,62,39.4m,40,0), + NorthwindFactory.OrderDetail(10287,16,13.9m,40,0.15), + NorthwindFactory.OrderDetail(10287,34,11.2m,20,0), + NorthwindFactory.OrderDetail(10287,46,9.6m,15,0.15), + NorthwindFactory.OrderDetail(10288,54,5.9m,10,0.1), + NorthwindFactory.OrderDetail(10288,68,10,3,0.1), + NorthwindFactory.OrderDetail(10289,3,8,30,0), + + NorthwindFactory.OrderDetail(10289,64,26.6m,9,0), + NorthwindFactory.OrderDetail(10290,5,17,20,0), + NorthwindFactory.OrderDetail(10290,29,99,15,0), + NorthwindFactory.OrderDetail(10290,49,16,15,0), + NorthwindFactory.OrderDetail(10290,77,10.4m,10,0), + NorthwindFactory.OrderDetail(10291,13,4.8m,20,0.1), + NorthwindFactory.OrderDetail(10291,44,15.5m,24,0.1), + NorthwindFactory.OrderDetail(10291,51,42.4m,2,0.1), + NorthwindFactory.OrderDetail(10292,20,64.8m,20,0), + NorthwindFactory.OrderDetail(10293,18,50,12,0), + + NorthwindFactory.OrderDetail(10293,24,3.6m,10,0), + NorthwindFactory.OrderDetail(10293,63,35.1m,5,0), + NorthwindFactory.OrderDetail(10293,75,6.2m,6,0), + NorthwindFactory.OrderDetail(10294,1,14.4m,18,0), + NorthwindFactory.OrderDetail(10294,17,31.2m,15,0), + NorthwindFactory.OrderDetail(10294,43,36.8m,15,0), + NorthwindFactory.OrderDetail(10294,60,27.2m,21,0), + NorthwindFactory.OrderDetail(10294,75,6.2m,6,0), + NorthwindFactory.OrderDetail(10295,56,30.4m,4,0), + NorthwindFactory.OrderDetail(10296,11,16.8m,12,0), + + NorthwindFactory.OrderDetail(10296,16,13.9m,30,0), + NorthwindFactory.OrderDetail(10296,69,28.8m,15,0), + NorthwindFactory.OrderDetail(10297,39,14.4m,60,0), + NorthwindFactory.OrderDetail(10297,72,27.8m,20,0), + NorthwindFactory.OrderDetail(10298,2,15.2m,40,0), + NorthwindFactory.OrderDetail(10298,36,15.2m,40,0.25), + NorthwindFactory.OrderDetail(10298,59,44,30,0.25), + NorthwindFactory.OrderDetail(10298,62,39.4m,15,0), + NorthwindFactory.OrderDetail(10299,19,7.3m,15,0), + NorthwindFactory.OrderDetail(10299,70,12,20,0), + + NorthwindFactory.OrderDetail(10300,66,13.6m,30,0), + NorthwindFactory.OrderDetail(10300,68,10,20,0), + NorthwindFactory.OrderDetail(10301,40,14.7m,10,0), + NorthwindFactory.OrderDetail(10301,56,30.4m,20,0), + NorthwindFactory.OrderDetail(10302,17,31.2m,40,0), + NorthwindFactory.OrderDetail(10302,28,36.4m,28,0), + NorthwindFactory.OrderDetail(10302,43,36.8m,12,0), + NorthwindFactory.OrderDetail(10303,40,14.7m,40,0.1), + NorthwindFactory.OrderDetail(10303,65,16.8m,30,0.1), + NorthwindFactory.OrderDetail(10303,68,10,15,0.1), + + NorthwindFactory.OrderDetail(10304,49,16,30,0), + NorthwindFactory.OrderDetail(10304,59,44,10,0), + NorthwindFactory.OrderDetail(10304,71,17.2m,2,0), + NorthwindFactory.OrderDetail(10305,18,50,25,0.1), + NorthwindFactory.OrderDetail(10305,29,99,25,0.1), + NorthwindFactory.OrderDetail(10305,39,14.4m,30,0.1), + NorthwindFactory.OrderDetail(10306,30,20.7m,10,0), + NorthwindFactory.OrderDetail(10306,53,26.2m,10,0), + NorthwindFactory.OrderDetail(10306,54,5.9m,5,0), + NorthwindFactory.OrderDetail(10307,62,39.4m,10,0), + + NorthwindFactory.OrderDetail(10307,68,10,3,0), + NorthwindFactory.OrderDetail(10308,69,28.8m,1,0), + NorthwindFactory.OrderDetail(10308,70,12,5,0), + NorthwindFactory.OrderDetail(10309,4,17.6m,20,0), + NorthwindFactory.OrderDetail(10309,6,20,30,0), + NorthwindFactory.OrderDetail(10309,42,11.2m,2,0), + NorthwindFactory.OrderDetail(10309,43,36.8m,20,0), + NorthwindFactory.OrderDetail(10309,71,17.2m,3,0), + NorthwindFactory.OrderDetail(10310,16,13.9m,10,0), + NorthwindFactory.OrderDetail(10310,62,39.4m,5,0), + + NorthwindFactory.OrderDetail(10311,42,11.2m,6,0), + NorthwindFactory.OrderDetail(10311,69,28.8m,7,0), + NorthwindFactory.OrderDetail(10312,28,36.4m,4,0), + NorthwindFactory.OrderDetail(10312,43,36.8m,24,0), + NorthwindFactory.OrderDetail(10312,53,26.2m,20,0), + NorthwindFactory.OrderDetail(10312,75,6.2m,10,0), + NorthwindFactory.OrderDetail(10313,36,15.2m,12,0), + NorthwindFactory.OrderDetail(10314,32,25.6m,40,0.1), + NorthwindFactory.OrderDetail(10314,58,10.6m,30,0.1), + NorthwindFactory.OrderDetail(10314,62,39.4m,25,0.1), + + NorthwindFactory.OrderDetail(10315,34,11.2m,14,0), + NorthwindFactory.OrderDetail(10315,70,12,30,0), + NorthwindFactory.OrderDetail(10316,41,7.7m,10,0), + NorthwindFactory.OrderDetail(10316,62,39.4m,70,0), + NorthwindFactory.OrderDetail(10317,1,14.4m,20,0), + NorthwindFactory.OrderDetail(10318,41,7.7m,20,0), + NorthwindFactory.OrderDetail(10318,76,14.4m,6,0), + NorthwindFactory.OrderDetail(10319,17,31.2m,8,0), + NorthwindFactory.OrderDetail(10319,28,36.4m,14,0), + NorthwindFactory.OrderDetail(10319,76,14.4m,30,0), + + NorthwindFactory.OrderDetail(10320,71,17.2m,30,0), + NorthwindFactory.OrderDetail(10321,35,14.4m,10,0), + NorthwindFactory.OrderDetail(10322,52,5.6m,20,0), + NorthwindFactory.OrderDetail(10323,15,12.4m,5,0), + NorthwindFactory.OrderDetail(10323,25,11.2m,4,0), + NorthwindFactory.OrderDetail(10323,39,14.4m,4,0), + NorthwindFactory.OrderDetail(10324,16,13.9m,21,0.15), + NorthwindFactory.OrderDetail(10324,35,14.4m,70,0.15), + NorthwindFactory.OrderDetail(10324,46,9.6m,30,0), + NorthwindFactory.OrderDetail(10324,59,44,40,0.15), + + NorthwindFactory.OrderDetail(10324,63,35.1m,80,0.15), + NorthwindFactory.OrderDetail(10325,6,20,6,0), + NorthwindFactory.OrderDetail(10325,13,4.8m,12,0), + NorthwindFactory.OrderDetail(10325,14,18.6m,9,0), + NorthwindFactory.OrderDetail(10325,31,10,4,0), + NorthwindFactory.OrderDetail(10325,72,27.8m,40,0), + NorthwindFactory.OrderDetail(10326,4,17.6m,24,0), + NorthwindFactory.OrderDetail(10326,57,15.6m,16,0), + NorthwindFactory.OrderDetail(10326,75,6.2m,50,0), + NorthwindFactory.OrderDetail(10327,2,15.2m,25,0.2), + + NorthwindFactory.OrderDetail(10327,11,16.8m,50,0.2), + NorthwindFactory.OrderDetail(10327,30,20.7m,35,0.2), + NorthwindFactory.OrderDetail(10327,58,10.6m,30,0.2), + NorthwindFactory.OrderDetail(10328,59,44,9,0), + NorthwindFactory.OrderDetail(10328,65,16.8m,40,0), + NorthwindFactory.OrderDetail(10328,68,10,10,0), + NorthwindFactory.OrderDetail(10329,19,7.3m,10,0.05), + NorthwindFactory.OrderDetail(10329,30,20.7m,8,0.05), + NorthwindFactory.OrderDetail(10329,38,210.8m,20,0.05), + NorthwindFactory.OrderDetail(10329,56,30.4m,12,0.05), + + NorthwindFactory.OrderDetail(10330,26,24.9m,50,0.15), + NorthwindFactory.OrderDetail(10330,72,27.8m,25,0.15), + NorthwindFactory.OrderDetail(10331,54,5.9m,15,0), + NorthwindFactory.OrderDetail(10332,18,50,40,0.2), + NorthwindFactory.OrderDetail(10332,42,11.2m,10,0.2), + NorthwindFactory.OrderDetail(10332,47,7.6m,16,0.2), + NorthwindFactory.OrderDetail(10333,14,18.6m,10,0), + NorthwindFactory.OrderDetail(10333,21,8,10,0.1), + NorthwindFactory.OrderDetail(10333,71,17.2m,40,0.1), + NorthwindFactory.OrderDetail(10334,52,5.6m,8,0), + + NorthwindFactory.OrderDetail(10334,68,10,10,0), + NorthwindFactory.OrderDetail(10335,2,15.2m,7,0.2), + NorthwindFactory.OrderDetail(10335,31,10,25,0.2), + NorthwindFactory.OrderDetail(10335,32,25.6m,6,0.2), + NorthwindFactory.OrderDetail(10335,51,42.4m,48,0.2), + NorthwindFactory.OrderDetail(10336,4,17.6m,18,0.1), + NorthwindFactory.OrderDetail(10337,23,7.2m,40,0), + NorthwindFactory.OrderDetail(10337,26,24.9m,24,0), + NorthwindFactory.OrderDetail(10337,36,15.2m,20,0), + NorthwindFactory.OrderDetail(10337,37,20.8m,28,0), + + NorthwindFactory.OrderDetail(10337,72,27.8m,25,0), + NorthwindFactory.OrderDetail(10338,17,31.2m,20,0), + NorthwindFactory.OrderDetail(10338,30,20.7m,15,0), + NorthwindFactory.OrderDetail(10339,4,17.6m,10,0), + NorthwindFactory.OrderDetail(10339,17,31.2m,70,0.05), + NorthwindFactory.OrderDetail(10339,62,39.4m,28,0), + NorthwindFactory.OrderDetail(10340,18,50,20,0.05), + NorthwindFactory.OrderDetail(10340,41,7.7m,12,0.05), + NorthwindFactory.OrderDetail(10340,43,36.8m,40,0.05), + NorthwindFactory.OrderDetail(10341,33,2,8,0), + + NorthwindFactory.OrderDetail(10341,59,44,9,0.15), + NorthwindFactory.OrderDetail(10342,2,15.2m,24,0.2), + NorthwindFactory.OrderDetail(10342,31,10,56,0.2), + NorthwindFactory.OrderDetail(10342,36,15.2m,40,0.2), + NorthwindFactory.OrderDetail(10342,55,19.2m,40,0.2), + NorthwindFactory.OrderDetail(10343,64,26.6m,50,0), + NorthwindFactory.OrderDetail(10343,68,10,4,0.05), + NorthwindFactory.OrderDetail(10343,76,14.4m,15,0), + NorthwindFactory.OrderDetail(10344,4,17.6m,35,0), + NorthwindFactory.OrderDetail(10344,8,32,70,0.25), + + NorthwindFactory.OrderDetail(10345,8,32,70,0), + NorthwindFactory.OrderDetail(10345,19,7.3m,80,0), + NorthwindFactory.OrderDetail(10345,42,11.2m,9,0), + NorthwindFactory.OrderDetail(10346,17,31.2m,36,0.1), + NorthwindFactory.OrderDetail(10346,56,30.4m,20,0), + NorthwindFactory.OrderDetail(10347,25,11.2m,10,0), + NorthwindFactory.OrderDetail(10347,39,14.4m,50,0.15), + NorthwindFactory.OrderDetail(10347,40,14.7m,4,0), + NorthwindFactory.OrderDetail(10347,75,6.2m,6,0.15), + NorthwindFactory.OrderDetail(10348,1,14.4m,15,0.15), + + NorthwindFactory.OrderDetail(10348,23,7.2m,25,0), + NorthwindFactory.OrderDetail(10349,54,5.9m,24,0), + NorthwindFactory.OrderDetail(10350,50,13,15,0.1), + NorthwindFactory.OrderDetail(10350,69,28.8m,18,0.1), + NorthwindFactory.OrderDetail(10351,38,210.8m,20,0.05), + NorthwindFactory.OrderDetail(10351,41,7.7m,13,0), + NorthwindFactory.OrderDetail(10351,44,15.5m,77,0.05), + NorthwindFactory.OrderDetail(10351,65,16.8m,10,0.05), + NorthwindFactory.OrderDetail(10352,24,3.6m,10,0), + NorthwindFactory.OrderDetail(10352,54,5.9m,20,0.15), + + NorthwindFactory.OrderDetail(10353,11,16.8m,12,0.2), + NorthwindFactory.OrderDetail(10353,38,210.8m,50,0.2), + NorthwindFactory.OrderDetail(10354,1,14.4m,12,0), + NorthwindFactory.OrderDetail(10354,29,99,4,0), + NorthwindFactory.OrderDetail(10355,24,3.6m,25,0), + NorthwindFactory.OrderDetail(10355,57,15.6m,25,0), + NorthwindFactory.OrderDetail(10356,31,10,30,0), + NorthwindFactory.OrderDetail(10356,55,19.2m,12,0), + NorthwindFactory.OrderDetail(10356,69,28.8m,20,0), + NorthwindFactory.OrderDetail(10357,10,24.8m,30,0.2), + + NorthwindFactory.OrderDetail(10357,26,24.9m,16,0), + NorthwindFactory.OrderDetail(10357,60,27.2m,8,0.2), + NorthwindFactory.OrderDetail(10358,24,3.6m,10,0.05), + NorthwindFactory.OrderDetail(10358,34,11.2m,10,0.05), + NorthwindFactory.OrderDetail(10358,36,15.2m,20,0.05), + NorthwindFactory.OrderDetail(10359,16,13.9m,56,0.05), + NorthwindFactory.OrderDetail(10359,31,10,70,0.05), + NorthwindFactory.OrderDetail(10359,60,27.2m,80,0.05), + NorthwindFactory.OrderDetail(10360,28,36.4m,30,0), + NorthwindFactory.OrderDetail(10360,29,99,35,0), + + NorthwindFactory.OrderDetail(10360,38,210.8m,10,0), + NorthwindFactory.OrderDetail(10360,49,16,35,0), + NorthwindFactory.OrderDetail(10360,54,5.9m,28,0), + NorthwindFactory.OrderDetail(10361,39,14.4m,54,0.1), + NorthwindFactory.OrderDetail(10361,60,27.2m,55,0.1), + NorthwindFactory.OrderDetail(10362,25,11.2m,50,0), + NorthwindFactory.OrderDetail(10362,51,42.4m,20,0), + NorthwindFactory.OrderDetail(10362,54,5.9m,24,0), + NorthwindFactory.OrderDetail(10363,31,10,20,0), + NorthwindFactory.OrderDetail(10363,75,6.2m,12,0), + + NorthwindFactory.OrderDetail(10363,76,14.4m,12,0), + NorthwindFactory.OrderDetail(10364,69,28.8m,30,0), + NorthwindFactory.OrderDetail(10364,71,17.2m,5,0), + NorthwindFactory.OrderDetail(10365,11,16.8m,24,0), + NorthwindFactory.OrderDetail(10366,65,16.8m,5,0), + NorthwindFactory.OrderDetail(10366,77,10.4m,5,0), + NorthwindFactory.OrderDetail(10367,34,11.2m,36,0), + NorthwindFactory.OrderDetail(10367,54,5.9m,18,0), + NorthwindFactory.OrderDetail(10367,65,16.8m,15,0), + NorthwindFactory.OrderDetail(10367,77,10.4m,7,0), + + NorthwindFactory.OrderDetail(10368,21,8,5,0.1), + NorthwindFactory.OrderDetail(10368,28,36.4m,13,0.1), + NorthwindFactory.OrderDetail(10368,57,15.6m,25,0), + NorthwindFactory.OrderDetail(10368,64,26.6m,35,0.1), + NorthwindFactory.OrderDetail(10369,29,99,20,0), + NorthwindFactory.OrderDetail(10369,56,30.4m,18,0.25), + NorthwindFactory.OrderDetail(10370,1,14.4m,15,0.15), + NorthwindFactory.OrderDetail(10370,64,26.6m,30,0), + NorthwindFactory.OrderDetail(10370,74,8,20,0.15), + NorthwindFactory.OrderDetail(10371,36,15.2m,6,0.2), + + NorthwindFactory.OrderDetail(10372,20,64.8m,12,0.25), + NorthwindFactory.OrderDetail(10372,38,210.8m,40,0.25), + NorthwindFactory.OrderDetail(10372,60,27.2m,70,0.25), + NorthwindFactory.OrderDetail(10372,72,27.8m,42,0.25), + NorthwindFactory.OrderDetail(10373,58,10.6m,80,0.2), + NorthwindFactory.OrderDetail(10373,71,17.2m,50,0.2), + NorthwindFactory.OrderDetail(10374,31,10,30,0), + NorthwindFactory.OrderDetail(10374,58,10.6m,15,0), + NorthwindFactory.OrderDetail(10375,14,18.6m,15,0), + NorthwindFactory.OrderDetail(10375,54,5.9m,10,0), + + NorthwindFactory.OrderDetail(10376,31,10,42,0.05), + NorthwindFactory.OrderDetail(10377,28,36.4m,20,0.15), + NorthwindFactory.OrderDetail(10377,39,14.4m,20,0.15), + NorthwindFactory.OrderDetail(10378,71,17.2m,6,0), + NorthwindFactory.OrderDetail(10379,41,7.7m,8,0.1), + NorthwindFactory.OrderDetail(10379,63,35.1m,16,0.1), + NorthwindFactory.OrderDetail(10379,65,16.8m,20,0.1), + NorthwindFactory.OrderDetail(10380,30,20.7m,18,0.1), + NorthwindFactory.OrderDetail(10380,53,26.2m,20,0.1), + NorthwindFactory.OrderDetail(10380,60,27.2m,6,0.1), + + NorthwindFactory.OrderDetail(10380,70,12,30,0), + NorthwindFactory.OrderDetail(10381,74,8,14,0), + NorthwindFactory.OrderDetail(10382,5,17,32,0), + NorthwindFactory.OrderDetail(10382,18,50,9,0), + NorthwindFactory.OrderDetail(10382,29,99,14,0), + NorthwindFactory.OrderDetail(10382,33,2,60,0), + NorthwindFactory.OrderDetail(10382,74,8,50,0), + NorthwindFactory.OrderDetail(10383,13,4.8m,20,0), + NorthwindFactory.OrderDetail(10383,50,13,15,0), + NorthwindFactory.OrderDetail(10383,56,30.4m,20,0), + + NorthwindFactory.OrderDetail(10384,20,64.8m,28,0), + NorthwindFactory.OrderDetail(10384,60,27.2m,15,0), + NorthwindFactory.OrderDetail(10385,7,24,10,0.2), + NorthwindFactory.OrderDetail(10385,60,27.2m,20,0.2), + NorthwindFactory.OrderDetail(10385,68,10,8,0.2), + NorthwindFactory.OrderDetail(10386,24,3.6m,15,0), + NorthwindFactory.OrderDetail(10386,34,11.2m,10,0), + NorthwindFactory.OrderDetail(10387,24,3.6m,15,0), + NorthwindFactory.OrderDetail(10387,28,36.4m,6,0), + NorthwindFactory.OrderDetail(10387,59,44,12,0), + + NorthwindFactory.OrderDetail(10387,71,17.2m,15,0), + NorthwindFactory.OrderDetail(10388,45,7.6m,15,0.2), + NorthwindFactory.OrderDetail(10388,52,5.6m,20,0.2), + NorthwindFactory.OrderDetail(10388,53,26.2m,40,0), + NorthwindFactory.OrderDetail(10389,10,24.8m,16,0), + NorthwindFactory.OrderDetail(10389,55,19.2m,15,0), + NorthwindFactory.OrderDetail(10389,62,39.4m,20,0), + NorthwindFactory.OrderDetail(10389,70,12,30,0), + NorthwindFactory.OrderDetail(10390,31,10,60,0.1), + NorthwindFactory.OrderDetail(10390,35,14.4m,40,0.1), + + NorthwindFactory.OrderDetail(10390,46,9.6m,45,0), + NorthwindFactory.OrderDetail(10390,72,27.8m,24,0.1), + NorthwindFactory.OrderDetail(10391,13,4.8m,18,0), + NorthwindFactory.OrderDetail(10392,69,28.8m,50,0), + NorthwindFactory.OrderDetail(10393,2,15.2m,25,0.25), + NorthwindFactory.OrderDetail(10393,14,18.6m,42,0.25), + NorthwindFactory.OrderDetail(10393,25,11.2m,7,0.25), + NorthwindFactory.OrderDetail(10393,26,24.9m,70,0.25), + NorthwindFactory.OrderDetail(10393,31,10,32,0), + NorthwindFactory.OrderDetail(10394,13,4.8m,10,0), + + NorthwindFactory.OrderDetail(10394,62,39.4m,10,0), + NorthwindFactory.OrderDetail(10395,46,9.6m,28,0.1), + NorthwindFactory.OrderDetail(10395,53,26.2m,70,0.1), + NorthwindFactory.OrderDetail(10395,69,28.8m,8,0), + NorthwindFactory.OrderDetail(10396,23,7.2m,40,0), + NorthwindFactory.OrderDetail(10396,71,17.2m,60,0), + NorthwindFactory.OrderDetail(10396,72,27.8m,21,0), + NorthwindFactory.OrderDetail(10397,21,8,10,0.15), + NorthwindFactory.OrderDetail(10397,51,42.4m,18,0.15), + NorthwindFactory.OrderDetail(10398,35,14.4m,30,0), + + NorthwindFactory.OrderDetail(10398,55,19.2m,120,0.1), + NorthwindFactory.OrderDetail(10399,68,10,60,0), + NorthwindFactory.OrderDetail(10399,71,17.2m,30,0), + NorthwindFactory.OrderDetail(10399,76,14.4m,35,0), + NorthwindFactory.OrderDetail(10399,77,10.4m,14,0), + NorthwindFactory.OrderDetail(10400,29,99,21,0), + NorthwindFactory.OrderDetail(10400,35,14.4m,35,0), + NorthwindFactory.OrderDetail(10400,49,16,30,0), + NorthwindFactory.OrderDetail(10401,30,20.7m,18,0), + NorthwindFactory.OrderDetail(10401,56,30.4m,70,0), + + NorthwindFactory.OrderDetail(10401,65,16.8m,20,0), + NorthwindFactory.OrderDetail(10401,71,17.2m,60,0), + NorthwindFactory.OrderDetail(10402,23,7.2m,60,0), + NorthwindFactory.OrderDetail(10402,63,35.1m,65,0), + NorthwindFactory.OrderDetail(10403,16,13.9m,21,0.15), + NorthwindFactory.OrderDetail(10403,48,10.2m,70,0.15), + NorthwindFactory.OrderDetail(10404,26,24.9m,30,0.05), + NorthwindFactory.OrderDetail(10404,42,11.2m,40,0.05), + NorthwindFactory.OrderDetail(10404,49,16,30,0.05), + NorthwindFactory.OrderDetail(10405,3,8,50,0), + + NorthwindFactory.OrderDetail(10406,1,14.4m,10,0), + NorthwindFactory.OrderDetail(10406,21,8,30,0.1), + NorthwindFactory.OrderDetail(10406,28,36.4m,42,0.1), + NorthwindFactory.OrderDetail(10406,36,15.2m,5,0.1), + NorthwindFactory.OrderDetail(10406,40,14.7m,2,0.1), + NorthwindFactory.OrderDetail(10407,11,16.8m,30,0), + NorthwindFactory.OrderDetail(10407,69,28.8m,15,0), + NorthwindFactory.OrderDetail(10407,71,17.2m,15,0), + NorthwindFactory.OrderDetail(10408,37,20.8m,10,0), + NorthwindFactory.OrderDetail(10408,54,5.9m,6,0), + + NorthwindFactory.OrderDetail(10408,62,39.4m,35,0), + NorthwindFactory.OrderDetail(10409,14,18.6m,12,0), + NorthwindFactory.OrderDetail(10409,21,8,12,0), + NorthwindFactory.OrderDetail(10410,33,2,49,0), + NorthwindFactory.OrderDetail(10410,59,44,16,0), + NorthwindFactory.OrderDetail(10411,41,7.7m,25,0.2), + NorthwindFactory.OrderDetail(10411,44,15.5m,40,0.2), + NorthwindFactory.OrderDetail(10411,59,44,9,0.2), + NorthwindFactory.OrderDetail(10412,14,18.6m,20,0.1), + NorthwindFactory.OrderDetail(10413,1,14.4m,24,0), + + NorthwindFactory.OrderDetail(10413,62,39.4m,40,0), + NorthwindFactory.OrderDetail(10413,76,14.4m,14,0), + NorthwindFactory.OrderDetail(10414,19,7.3m,18,0.05), + NorthwindFactory.OrderDetail(10414,33,2,50,0), + NorthwindFactory.OrderDetail(10415,17,31.2m,2,0), + NorthwindFactory.OrderDetail(10415,33,2,20,0), + NorthwindFactory.OrderDetail(10416,19,7.3m,20,0), + NorthwindFactory.OrderDetail(10416,53,26.2m,10,0), + NorthwindFactory.OrderDetail(10416,57,15.6m,20,0), + NorthwindFactory.OrderDetail(10417,38,210.8m,50,0), + + NorthwindFactory.OrderDetail(10417,46,9.6m,2,0.25), + NorthwindFactory.OrderDetail(10417,68,10,36,0.25), + NorthwindFactory.OrderDetail(10417,77,10.4m,35,0), + NorthwindFactory.OrderDetail(10418,2,15.2m,60,0), + NorthwindFactory.OrderDetail(10418,47,7.6m,55,0), + NorthwindFactory.OrderDetail(10418,61,22.8m,16,0), + NorthwindFactory.OrderDetail(10418,74,8,15,0), + NorthwindFactory.OrderDetail(10419,60,27.2m,60,0.05), + NorthwindFactory.OrderDetail(10419,69,28.8m,20,0.05), + NorthwindFactory.OrderDetail(10420,9,77.6m,20,0.1), + + NorthwindFactory.OrderDetail(10420,13,4.8m,2,0.1), + NorthwindFactory.OrderDetail(10420,70,12,8,0.1), + NorthwindFactory.OrderDetail(10420,73,12,20,0.1), + NorthwindFactory.OrderDetail(10421,19,7.3m,4,0.15), + NorthwindFactory.OrderDetail(10421,26,24.9m,30,0), + NorthwindFactory.OrderDetail(10421,53,26.2m,15,0.15), + NorthwindFactory.OrderDetail(10421,77,10.4m,10,0.15), + NorthwindFactory.OrderDetail(10422,26,24.9m,2,0), + NorthwindFactory.OrderDetail(10423,31,10,14,0), + NorthwindFactory.OrderDetail(10423,59,44,20,0), + + NorthwindFactory.OrderDetail(10424,35,14.4m,60,0.2), + NorthwindFactory.OrderDetail(10424,38,210.8m,49,0.2), + NorthwindFactory.OrderDetail(10424,68,10,30,0.2), + NorthwindFactory.OrderDetail(10425,55,19.2m,10,0.25), + NorthwindFactory.OrderDetail(10425,76,14.4m,20,0.25), + NorthwindFactory.OrderDetail(10426,56,30.4m,5,0), + NorthwindFactory.OrderDetail(10426,64,26.6m,7,0), + NorthwindFactory.OrderDetail(10427,14,18.6m,35,0), + NorthwindFactory.OrderDetail(10428,46,9.6m,20,0), + NorthwindFactory.OrderDetail(10429,50,13,40,0), + + NorthwindFactory.OrderDetail(10429,63,35.1m,35,0.25), + NorthwindFactory.OrderDetail(10430,17,31.2m,45,0.2), + NorthwindFactory.OrderDetail(10430,21,8,50,0), + NorthwindFactory.OrderDetail(10430,56,30.4m,30,0), + NorthwindFactory.OrderDetail(10430,59,44,70,0.2), + NorthwindFactory.OrderDetail(10431,17,31.2m,50,0.25), + NorthwindFactory.OrderDetail(10431,40,14.7m,50,0.25), + NorthwindFactory.OrderDetail(10431,47,7.6m,30,0.25), + NorthwindFactory.OrderDetail(10432,26,24.9m,10,0), + NorthwindFactory.OrderDetail(10432,54,5.9m,40,0), + + NorthwindFactory.OrderDetail(10433,56,30.4m,28,0), + NorthwindFactory.OrderDetail(10434,11,16.8m,6,0), + NorthwindFactory.OrderDetail(10434,76,14.4m,18,0.15), + NorthwindFactory.OrderDetail(10435,2,15.2m,10,0), + NorthwindFactory.OrderDetail(10435,22,16.8m,12,0), + NorthwindFactory.OrderDetail(10435,72,27.8m,10,0), + NorthwindFactory.OrderDetail(10436,46,9.6m,5,0), + NorthwindFactory.OrderDetail(10436,56,30.4m,40,0.1), + NorthwindFactory.OrderDetail(10436,64,26.6m,30,0.1), + NorthwindFactory.OrderDetail(10436,75,6.2m,24,0.1), + + NorthwindFactory.OrderDetail(10437,53,26.2m,15,0), + NorthwindFactory.OrderDetail(10438,19,7.3m,15,0.2), + NorthwindFactory.OrderDetail(10438,34,11.2m,20,0.2), + NorthwindFactory.OrderDetail(10438,57,15.6m,15,0.2), + NorthwindFactory.OrderDetail(10439,12,30.4m,15,0), + NorthwindFactory.OrderDetail(10439,16,13.9m,16,0), + NorthwindFactory.OrderDetail(10439,64,26.6m,6,0), + NorthwindFactory.OrderDetail(10439,74,8,30,0), + NorthwindFactory.OrderDetail(10440,2,15.2m,45,0.15), + NorthwindFactory.OrderDetail(10440,16,13.9m,49,0.15), + + NorthwindFactory.OrderDetail(10440,29,99,24,0.15), + NorthwindFactory.OrderDetail(10440,61,22.8m,90,0.15), + NorthwindFactory.OrderDetail(10441,27,35.1m,50,0), + NorthwindFactory.OrderDetail(10442,11,16.8m,30,0), + NorthwindFactory.OrderDetail(10442,54,5.9m,80,0), + NorthwindFactory.OrderDetail(10442,66,13.6m,60,0), + NorthwindFactory.OrderDetail(10443,11,16.8m,6,0.2), + NorthwindFactory.OrderDetail(10443,28,36.4m,12,0), + NorthwindFactory.OrderDetail(10444,17,31.2m,10,0), + NorthwindFactory.OrderDetail(10444,26,24.9m,15,0), + + NorthwindFactory.OrderDetail(10444,35,14.4m,8,0), + NorthwindFactory.OrderDetail(10444,41,7.7m,30,0), + NorthwindFactory.OrderDetail(10445,39,14.4m,6,0), + NorthwindFactory.OrderDetail(10445,54,5.9m,15,0), + NorthwindFactory.OrderDetail(10446,19,7.3m,12,0.1), + NorthwindFactory.OrderDetail(10446,24,3.6m,20,0.1), + NorthwindFactory.OrderDetail(10446,31,10,3,0.1), + NorthwindFactory.OrderDetail(10446,52,5.6m,15,0.1), + NorthwindFactory.OrderDetail(10447,19,7.3m,40,0), + NorthwindFactory.OrderDetail(10447,65,16.8m,35,0), + + NorthwindFactory.OrderDetail(10447,71,17.2m,2,0), + NorthwindFactory.OrderDetail(10448,26,24.9m,6,0), + NorthwindFactory.OrderDetail(10448,40,14.7m,20,0), + NorthwindFactory.OrderDetail(10449,10,24.8m,14,0), + NorthwindFactory.OrderDetail(10449,52,5.6m,20,0), + NorthwindFactory.OrderDetail(10449,62,39.4m,35,0), + NorthwindFactory.OrderDetail(10450,10,24.8m,20,0.2), + NorthwindFactory.OrderDetail(10450,54,5.9m,6,0.2), + NorthwindFactory.OrderDetail(10451,55,19.2m,120,0.1), + NorthwindFactory.OrderDetail(10451,64,26.6m,35,0.1), + + NorthwindFactory.OrderDetail(10451,65,16.8m,28,0.1), + NorthwindFactory.OrderDetail(10451,77,10.4m,55,0.1), + NorthwindFactory.OrderDetail(10452,28,36.4m,15,0), + NorthwindFactory.OrderDetail(10452,44,15.5m,100,0.05), + NorthwindFactory.OrderDetail(10453,48,10.2m,15,0.1), + NorthwindFactory.OrderDetail(10453,70,12,25,0.1), + NorthwindFactory.OrderDetail(10454,16,13.9m,20,0.2), + NorthwindFactory.OrderDetail(10454,33,2,20,0.2), + NorthwindFactory.OrderDetail(10454,46,9.6m,10,0.2), + NorthwindFactory.OrderDetail(10455,39,14.4m,20,0), + + NorthwindFactory.OrderDetail(10455,53,26.2m,50,0), + NorthwindFactory.OrderDetail(10455,61,22.8m,25,0), + NorthwindFactory.OrderDetail(10455,71,17.2m,30,0), + NorthwindFactory.OrderDetail(10456,21,8,40,0.15), + NorthwindFactory.OrderDetail(10456,49,16,21,0.15), + NorthwindFactory.OrderDetail(10457,59,44,36,0), + NorthwindFactory.OrderDetail(10458,26,24.9m,30,0), + NorthwindFactory.OrderDetail(10458,28,36.4m,30,0), + NorthwindFactory.OrderDetail(10458,43,36.8m,20,0), + NorthwindFactory.OrderDetail(10458,56,30.4m,15,0), + + NorthwindFactory.OrderDetail(10458,71,17.2m,50,0), + NorthwindFactory.OrderDetail(10459,7,24,16,0.05), + NorthwindFactory.OrderDetail(10459,46,9.6m,20,0.05), + NorthwindFactory.OrderDetail(10459,72,27.8m,40,0), + NorthwindFactory.OrderDetail(10460,68,10,21,0.25), + NorthwindFactory.OrderDetail(10460,75,6.2m,4,0.25), + NorthwindFactory.OrderDetail(10461,21,8,40,0.25), + NorthwindFactory.OrderDetail(10461,30,20.7m,28,0.25), + NorthwindFactory.OrderDetail(10461,55,19.2m,60,0.25), + NorthwindFactory.OrderDetail(10462,13,4.8m,1,0), + + NorthwindFactory.OrderDetail(10462,23,7.2m,21,0), + NorthwindFactory.OrderDetail(10463,19,7.3m,21,0), + NorthwindFactory.OrderDetail(10463,42,11.2m,50,0), + NorthwindFactory.OrderDetail(10464,4,17.6m,16,0.2), + NorthwindFactory.OrderDetail(10464,43,36.8m,3,0), + NorthwindFactory.OrderDetail(10464,56,30.4m,30,0.2), + NorthwindFactory.OrderDetail(10464,60,27.2m,20,0), + NorthwindFactory.OrderDetail(10465,24,3.6m,25,0), + NorthwindFactory.OrderDetail(10465,29,99,18,0.1), + NorthwindFactory.OrderDetail(10465,40,14.7m,20,0), + + NorthwindFactory.OrderDetail(10465,45,7.6m,30,0.1), + NorthwindFactory.OrderDetail(10465,50,13,25,0), + NorthwindFactory.OrderDetail(10466,11,16.8m,10,0), + NorthwindFactory.OrderDetail(10466,46,9.6m,5,0), + NorthwindFactory.OrderDetail(10467,24,3.6m,28,0), + NorthwindFactory.OrderDetail(10467,25,11.2m,12,0), + NorthwindFactory.OrderDetail(10468,30,20.7m,8,0), + NorthwindFactory.OrderDetail(10468,43,36.8m,15,0), + NorthwindFactory.OrderDetail(10469,2,15.2m,40,0.15), + NorthwindFactory.OrderDetail(10469,16,13.9m,35,0.15), + + NorthwindFactory.OrderDetail(10469,44,15.5m,2,0.15), + NorthwindFactory.OrderDetail(10470,18,50,30,0), + NorthwindFactory.OrderDetail(10470,23,7.2m,15,0), + NorthwindFactory.OrderDetail(10470,64,26.6m,8,0), + NorthwindFactory.OrderDetail(10471,7,24,30,0), + NorthwindFactory.OrderDetail(10471,56,30.4m,20,0), + NorthwindFactory.OrderDetail(10472,24,3.6m,80,0.05), + NorthwindFactory.OrderDetail(10472,51,42.4m,18,0), + NorthwindFactory.OrderDetail(10473,33,2,12,0), + NorthwindFactory.OrderDetail(10473,71,17.2m,12,0), + + NorthwindFactory.OrderDetail(10474,14,18.6m,12,0), + NorthwindFactory.OrderDetail(10474,28,36.4m,18,0), + NorthwindFactory.OrderDetail(10474,40,14.7m,21,0), + NorthwindFactory.OrderDetail(10474,75,6.2m,10,0), + NorthwindFactory.OrderDetail(10475,31,10,35,0.15), + NorthwindFactory.OrderDetail(10475,66,13.6m,60,0.15), + NorthwindFactory.OrderDetail(10475,76,14.4m,42,0.15), + NorthwindFactory.OrderDetail(10476,55,19.2m,2,0.05), + NorthwindFactory.OrderDetail(10476,70,12,12,0), + NorthwindFactory.OrderDetail(10477,1,14.4m,15,0), + + NorthwindFactory.OrderDetail(10477,21,8,21,0.25), + NorthwindFactory.OrderDetail(10477,39,14.4m,20,0.25), + NorthwindFactory.OrderDetail(10478,10,24.8m,20,0.05), + NorthwindFactory.OrderDetail(10479,38,210.8m,30,0), + NorthwindFactory.OrderDetail(10479,53,26.2m,28,0), + NorthwindFactory.OrderDetail(10479,59,44,60,0), + NorthwindFactory.OrderDetail(10479,64,26.6m,30,0), + NorthwindFactory.OrderDetail(10480,47,7.6m,30,0), + NorthwindFactory.OrderDetail(10480,59,44,12,0), + NorthwindFactory.OrderDetail(10481,49,16,24,0), + + NorthwindFactory.OrderDetail(10481,60,27.2m,40,0), + NorthwindFactory.OrderDetail(10482,40,14.7m,10,0), + NorthwindFactory.OrderDetail(10483,34,11.2m,35,0.05), + NorthwindFactory.OrderDetail(10483,77,10.4m,30,0.05), + NorthwindFactory.OrderDetail(10484,21,8,14,0), + NorthwindFactory.OrderDetail(10484,40,14.7m,10,0), + NorthwindFactory.OrderDetail(10484,51,42.4m,3,0), + NorthwindFactory.OrderDetail(10485,2,15.2m,20,0.1), + NorthwindFactory.OrderDetail(10485,3,8,20,0.1), + NorthwindFactory.OrderDetail(10485,55,19.2m,30,0.1), + + NorthwindFactory.OrderDetail(10485,70,12,60,0.1), + NorthwindFactory.OrderDetail(10486,11,16.8m,5,0), + NorthwindFactory.OrderDetail(10486,51,42.4m,25,0), + NorthwindFactory.OrderDetail(10486,74,8,16,0), + NorthwindFactory.OrderDetail(10487,19,7.3m,5,0), + NorthwindFactory.OrderDetail(10487,26,24.9m,30,0), + NorthwindFactory.OrderDetail(10487,54,5.9m,24,0.25), + NorthwindFactory.OrderDetail(10488,59,44,30,0), + NorthwindFactory.OrderDetail(10488,73,12,20,0.2), + NorthwindFactory.OrderDetail(10489,11,16.8m,15,0.25), + + NorthwindFactory.OrderDetail(10489,16,13.9m,18,0), + NorthwindFactory.OrderDetail(10490,59,44,60,0), + NorthwindFactory.OrderDetail(10490,68,10,30,0), + NorthwindFactory.OrderDetail(10490,75,6.2m,36,0), + NorthwindFactory.OrderDetail(10491,44,15.5m,15,0.15), + NorthwindFactory.OrderDetail(10491,77,10.4m,7,0.15), + NorthwindFactory.OrderDetail(10492,25,11.2m,60,0.05), + NorthwindFactory.OrderDetail(10492,42,11.2m,20,0.05), + NorthwindFactory.OrderDetail(10493,65,16.8m,15,0.1), + NorthwindFactory.OrderDetail(10493,66,13.6m,10,0.1), + + NorthwindFactory.OrderDetail(10493,69,28.8m,10,0.1), + NorthwindFactory.OrderDetail(10494,56,30.4m,30,0), + NorthwindFactory.OrderDetail(10495,23,7.2m,10,0), + NorthwindFactory.OrderDetail(10495,41,7.7m,20,0), + NorthwindFactory.OrderDetail(10495,77,10.4m,5,0), + NorthwindFactory.OrderDetail(10496,31,10,20,0.05), + NorthwindFactory.OrderDetail(10497,56,30.4m,14,0), + NorthwindFactory.OrderDetail(10497,72,27.8m,25,0), + NorthwindFactory.OrderDetail(10497,77,10.4m,25,0), + NorthwindFactory.OrderDetail(10498,24,4.5m,14,0), + + NorthwindFactory.OrderDetail(10498,40,18.4m,5,0), + NorthwindFactory.OrderDetail(10498,42,14,30,0), + NorthwindFactory.OrderDetail(10499,28,45.6m,20,0), + NorthwindFactory.OrderDetail(10499,49,20,25,0), + NorthwindFactory.OrderDetail(10500,15,15.5m,12,0.05), + NorthwindFactory.OrderDetail(10500,28,45.6m,8,0.05), + NorthwindFactory.OrderDetail(10501,54,7.45m,20,0), + NorthwindFactory.OrderDetail(10502,45,9.5m,21,0), + NorthwindFactory.OrderDetail(10502,53,32.8m,6,0), + NorthwindFactory.OrderDetail(10502,67,14,30,0), + + NorthwindFactory.OrderDetail(10503,14,23.25m,70,0), + NorthwindFactory.OrderDetail(10503,65,21.05m,20,0), + NorthwindFactory.OrderDetail(10504,2,19,12,0), + NorthwindFactory.OrderDetail(10504,21,10,12,0), + NorthwindFactory.OrderDetail(10504,53,32.8m,10,0), + NorthwindFactory.OrderDetail(10504,61,28.5m,25,0), + NorthwindFactory.OrderDetail(10505,62,49.3m,3,0), + NorthwindFactory.OrderDetail(10506,25,14,18,0.1), + NorthwindFactory.OrderDetail(10506,70,15,14,0.1), + NorthwindFactory.OrderDetail(10507,43,46,15,0.15), + + NorthwindFactory.OrderDetail(10507,48,12.75m,15,0.15), + NorthwindFactory.OrderDetail(10508,13,6,10,0), + NorthwindFactory.OrderDetail(10508,39,18,10,0), + NorthwindFactory.OrderDetail(10509,28,45.6m,3,0), + NorthwindFactory.OrderDetail(10510,29,123.79m,36,0), + NorthwindFactory.OrderDetail(10510,75,7.75m,36,0.1), + NorthwindFactory.OrderDetail(10511,4,22,50,0.15), + NorthwindFactory.OrderDetail(10511,7,30,50,0.15), + NorthwindFactory.OrderDetail(10511,8,40,10,0.15), + NorthwindFactory.OrderDetail(10512,24,4.5m,10,0.15), + + NorthwindFactory.OrderDetail(10512,46,12,9,0.15), + NorthwindFactory.OrderDetail(10512,47,9.5m,6,0.15), + NorthwindFactory.OrderDetail(10512,60,34,12,0.15), + NorthwindFactory.OrderDetail(10513,21,10,40,0.2), + NorthwindFactory.OrderDetail(10513,32,32,50,0.2), + NorthwindFactory.OrderDetail(10513,61,28.5m,15,0.2), + NorthwindFactory.OrderDetail(10514,20,81,39,0), + NorthwindFactory.OrderDetail(10514,28,45.6m,35,0), + NorthwindFactory.OrderDetail(10514,56,38,70,0), + NorthwindFactory.OrderDetail(10514,65,21.05m,39,0), + + NorthwindFactory.OrderDetail(10514,75,7.75m,50,0), + NorthwindFactory.OrderDetail(10515,9,97,16,0.15), + NorthwindFactory.OrderDetail(10515,16,17.45m,50,0), + NorthwindFactory.OrderDetail(10515,27,43.9m,120,0), + NorthwindFactory.OrderDetail(10515,33,2.5m,16,0.15), + NorthwindFactory.OrderDetail(10515,60,34,84,0.15), + NorthwindFactory.OrderDetail(10516,18,62.5m,25,0.1), + NorthwindFactory.OrderDetail(10516,41,9.65m,80,0.1), + NorthwindFactory.OrderDetail(10516,42,14,20,0), + NorthwindFactory.OrderDetail(10517,52,7,6,0), + + NorthwindFactory.OrderDetail(10517,59,55,4,0), + NorthwindFactory.OrderDetail(10517,70,15,6,0), + NorthwindFactory.OrderDetail(10518,24,4.5m,5,0), + NorthwindFactory.OrderDetail(10518,38,263.5m,15,0), + NorthwindFactory.OrderDetail(10518,44,19.45m,9,0), + NorthwindFactory.OrderDetail(10519,10,31,16,0.05), + NorthwindFactory.OrderDetail(10519,56,38,40,0), + NorthwindFactory.OrderDetail(10519,60,34,10,0.05), + NorthwindFactory.OrderDetail(10520,24,4.5m,8,0), + NorthwindFactory.OrderDetail(10520,53,32.8m,5,0), + + NorthwindFactory.OrderDetail(10521,35,18,3,0), + NorthwindFactory.OrderDetail(10521,41,9.65m,10,0), + NorthwindFactory.OrderDetail(10521,68,12.5m,6,0), + NorthwindFactory.OrderDetail(10522,1,18,40,0.2), + NorthwindFactory.OrderDetail(10522,8,40,24,0), + NorthwindFactory.OrderDetail(10522,30,25.89m,20,0.2), + NorthwindFactory.OrderDetail(10522,40,18.4m,25,0.2), + NorthwindFactory.OrderDetail(10523,17,39,25,0.1), + NorthwindFactory.OrderDetail(10523,20,81,15,0.1), + NorthwindFactory.OrderDetail(10523,37,26,18,0.1), + + NorthwindFactory.OrderDetail(10523,41,9.65m,6,0.1), + NorthwindFactory.OrderDetail(10524,10,31,2,0), + NorthwindFactory.OrderDetail(10524,30,25.89m,10,0), + NorthwindFactory.OrderDetail(10524,43,46,60,0), + NorthwindFactory.OrderDetail(10524,54,7.45m,15,0), + NorthwindFactory.OrderDetail(10525,36,19,30,0), + NorthwindFactory.OrderDetail(10525,40,18.4m,15,0.1), + NorthwindFactory.OrderDetail(10526,1,18,8,0.15), + NorthwindFactory.OrderDetail(10526,13,6,10,0), + NorthwindFactory.OrderDetail(10526,56,38,30,0.15), + + NorthwindFactory.OrderDetail(10527,4,22,50,0.1), + NorthwindFactory.OrderDetail(10527,36,19,30,0.1), + NorthwindFactory.OrderDetail(10528,11,21,3,0), + NorthwindFactory.OrderDetail(10528,33,2.5m,8,0.2), + NorthwindFactory.OrderDetail(10528,72,34.8m,9,0), + NorthwindFactory.OrderDetail(10529,55,24,14,0), + NorthwindFactory.OrderDetail(10529,68,12.5m,20,0), + NorthwindFactory.OrderDetail(10529,69,36,10,0), + NorthwindFactory.OrderDetail(10530,17,39,40,0), + NorthwindFactory.OrderDetail(10530,43,46,25,0), + + NorthwindFactory.OrderDetail(10530,61,28.5m,20,0), + NorthwindFactory.OrderDetail(10530,76,18,50,0), + NorthwindFactory.OrderDetail(10531,59,55,2,0), + NorthwindFactory.OrderDetail(10532,30,25.89m,15,0), + NorthwindFactory.OrderDetail(10532,66,17,24,0), + NorthwindFactory.OrderDetail(10533,4,22,50,0.05), + NorthwindFactory.OrderDetail(10533,72,34.8m,24,0), + NorthwindFactory.OrderDetail(10533,73,15,24,0.05), + NorthwindFactory.OrderDetail(10534,30,25.89m,10,0), + NorthwindFactory.OrderDetail(10534,40,18.4m,10,0.2), + + NorthwindFactory.OrderDetail(10534,54,7.45m,10,0.2), + NorthwindFactory.OrderDetail(10535,11,21,50,0.1), + NorthwindFactory.OrderDetail(10535,40,18.4m,10,0.1), + NorthwindFactory.OrderDetail(10535,57,19.5m,5,0.1), + NorthwindFactory.OrderDetail(10535,59,55,15,0.1), + NorthwindFactory.OrderDetail(10536,12,38,15,0.25), + NorthwindFactory.OrderDetail(10536,31,12.5m,20,0), + NorthwindFactory.OrderDetail(10536,33,2.5m,30,0), + NorthwindFactory.OrderDetail(10536,60,34,35,0.25), + NorthwindFactory.OrderDetail(10537,31,12.5m,30,0), + + NorthwindFactory.OrderDetail(10537,51,53,6,0), + NorthwindFactory.OrderDetail(10537,58,13.25m,20,0), + NorthwindFactory.OrderDetail(10537,72,34.8m,21,0), + NorthwindFactory.OrderDetail(10537,73,15,9,0), + NorthwindFactory.OrderDetail(10538,70,15,7,0), + NorthwindFactory.OrderDetail(10538,72,34.8m,1,0), + NorthwindFactory.OrderDetail(10539,13,6,8,0), + NorthwindFactory.OrderDetail(10539,21,10,15,0), + NorthwindFactory.OrderDetail(10539,33,2.5m,15,0), + NorthwindFactory.OrderDetail(10539,49,20,6,0), + + NorthwindFactory.OrderDetail(10540,3,10,60,0), + NorthwindFactory.OrderDetail(10540,26,31.23m,40,0), + NorthwindFactory.OrderDetail(10540,38,263.5m,30,0), + NorthwindFactory.OrderDetail(10540,68,12.5m,35,0), + NorthwindFactory.OrderDetail(10541,24,4.5m,35,0.1), + NorthwindFactory.OrderDetail(10541,38,263.5m,4,0.1), + NorthwindFactory.OrderDetail(10541,65,21.05m,36,0.1), + NorthwindFactory.OrderDetail(10541,71,21.5m,9,0.1), + NorthwindFactory.OrderDetail(10542,11,21,15,0.05), + NorthwindFactory.OrderDetail(10542,54,7.45m,24,0.05), + + NorthwindFactory.OrderDetail(10543,12,38,30,0.15), + NorthwindFactory.OrderDetail(10543,23,9,70,0.15), + NorthwindFactory.OrderDetail(10544,28,45.6m,7,0), + NorthwindFactory.OrderDetail(10544,67,14,7,0), + NorthwindFactory.OrderDetail(10545,11,21,10,0), + NorthwindFactory.OrderDetail(10546,7,30,10,0), + NorthwindFactory.OrderDetail(10546,35,18,30,0), + NorthwindFactory.OrderDetail(10546,62,49.3m,40,0), + NorthwindFactory.OrderDetail(10547,32,32,24,0.15), + NorthwindFactory.OrderDetail(10547,36,19,60,0), + + NorthwindFactory.OrderDetail(10548,34,14,10,0.25), + NorthwindFactory.OrderDetail(10548,41,9.65m,14,0), + NorthwindFactory.OrderDetail(10549,31,12.5m,55,0.15), + NorthwindFactory.OrderDetail(10549,45,9.5m,100,0.15), + NorthwindFactory.OrderDetail(10549,51,53,48,0.15), + NorthwindFactory.OrderDetail(10550,17,39,8,0.1), + NorthwindFactory.OrderDetail(10550,19,9.2m,10,0), + NorthwindFactory.OrderDetail(10550,21,10,6,0.1), + NorthwindFactory.OrderDetail(10550,61,28.5m,10,0.1), + NorthwindFactory.OrderDetail(10551,16,17.45m,40,0.15), + + NorthwindFactory.OrderDetail(10551,35,18,20,0.15), + NorthwindFactory.OrderDetail(10551,44,19.45m,40,0), + NorthwindFactory.OrderDetail(10552,69,36,18,0), + NorthwindFactory.OrderDetail(10552,75,7.75m,30,0), + NorthwindFactory.OrderDetail(10553,11,21,15,0), + NorthwindFactory.OrderDetail(10553,16,17.45m,14,0), + NorthwindFactory.OrderDetail(10553,22,21,24,0), + NorthwindFactory.OrderDetail(10553,31,12.5m,30,0), + NorthwindFactory.OrderDetail(10553,35,18,6,0), + NorthwindFactory.OrderDetail(10554,16,17.45m,30,0.05), + + NorthwindFactory.OrderDetail(10554,23,9,20,0.05), + NorthwindFactory.OrderDetail(10554,62,49.3m,20,0.05), + NorthwindFactory.OrderDetail(10554,77,13,10,0.05), + NorthwindFactory.OrderDetail(10555,14,23.25m,30,0.2), + NorthwindFactory.OrderDetail(10555,19,9.2m,35,0.2), + NorthwindFactory.OrderDetail(10555,24,4.5m,18,0.2), + NorthwindFactory.OrderDetail(10555,51,53,20,0.2), + NorthwindFactory.OrderDetail(10555,56,38,40,0.2), + NorthwindFactory.OrderDetail(10556,72,34.8m,24,0), + NorthwindFactory.OrderDetail(10557,64,33.25m,30,0), + + NorthwindFactory.OrderDetail(10557,75,7.75m,20,0), + NorthwindFactory.OrderDetail(10558,47,9.5m,25,0), + NorthwindFactory.OrderDetail(10558,51,53,20,0), + NorthwindFactory.OrderDetail(10558,52,7,30,0), + NorthwindFactory.OrderDetail(10558,53,32.8m,18,0), + NorthwindFactory.OrderDetail(10558,73,15,3,0), + NorthwindFactory.OrderDetail(10559,41,9.65m,12,0.05), + NorthwindFactory.OrderDetail(10559,55,24,18,0.05), + NorthwindFactory.OrderDetail(10560,30,25.89m,20,0), + NorthwindFactory.OrderDetail(10560,62,49.3m,15,0.25), + + NorthwindFactory.OrderDetail(10561,44,19.45m,10,0), + NorthwindFactory.OrderDetail(10561,51,53,50,0), + NorthwindFactory.OrderDetail(10562,33,2.5m,20,0.1), + NorthwindFactory.OrderDetail(10562,62,49.3m,10,0.1), + NorthwindFactory.OrderDetail(10563,36,19,25,0), + NorthwindFactory.OrderDetail(10563,52,7,70,0), + NorthwindFactory.OrderDetail(10564,17,39,16,0.05), + NorthwindFactory.OrderDetail(10564,31,12.5m,6,0.05), + NorthwindFactory.OrderDetail(10564,55,24,25,0.05), + NorthwindFactory.OrderDetail(10565,24,4.5m,25,0.1), + + NorthwindFactory.OrderDetail(10565,64,33.25m,18,0.1), + NorthwindFactory.OrderDetail(10566,11,21,35,0.15), + NorthwindFactory.OrderDetail(10566,18,62.5m,18,0.15), + NorthwindFactory.OrderDetail(10566,76,18,10,0), + NorthwindFactory.OrderDetail(10567,31,12.5m,60,0.2), + NorthwindFactory.OrderDetail(10567,51,53,3,0), + NorthwindFactory.OrderDetail(10567,59,55,40,0.2), + NorthwindFactory.OrderDetail(10568,10,31,5,0), + NorthwindFactory.OrderDetail(10569,31,12.5m,35,0.2), + NorthwindFactory.OrderDetail(10569,76,18,30,0), + + NorthwindFactory.OrderDetail(10570,11,21,15,0.05), + NorthwindFactory.OrderDetail(10570,56,38,60,0.05), + NorthwindFactory.OrderDetail(10571,14,23.25m,11,0.15), + NorthwindFactory.OrderDetail(10571,42,14,28,0.15), + NorthwindFactory.OrderDetail(10572,16,17.45m,12,0.1), + NorthwindFactory.OrderDetail(10572,32,32,10,0.1), + NorthwindFactory.OrderDetail(10572,40,18.4m,50,0), + NorthwindFactory.OrderDetail(10572,75,7.75m,15,0.1), + NorthwindFactory.OrderDetail(10573,17,39,18,0), + NorthwindFactory.OrderDetail(10573,34,14,40,0), + + NorthwindFactory.OrderDetail(10573,53,32.8m,25,0), + NorthwindFactory.OrderDetail(10574,33,2.5m,14,0), + NorthwindFactory.OrderDetail(10574,40,18.4m,2,0), + NorthwindFactory.OrderDetail(10574,62,49.3m,10,0), + NorthwindFactory.OrderDetail(10574,64,33.25m,6,0), + NorthwindFactory.OrderDetail(10575,59,55,12,0), + NorthwindFactory.OrderDetail(10575,63,43.9m,6,0), + NorthwindFactory.OrderDetail(10575,72,34.8m,30,0), + NorthwindFactory.OrderDetail(10575,76,18,10,0), + NorthwindFactory.OrderDetail(10576,1,18,10,0), + + NorthwindFactory.OrderDetail(10576,31,12.5m,20,0), + NorthwindFactory.OrderDetail(10576,44,19.45m,21,0), + NorthwindFactory.OrderDetail(10577,39,18,10,0), + NorthwindFactory.OrderDetail(10577,75,7.75m,20,0), + NorthwindFactory.OrderDetail(10577,77,13,18,0), + NorthwindFactory.OrderDetail(10578,35,18,20,0), + NorthwindFactory.OrderDetail(10578,57,19.5m,6,0), + NorthwindFactory.OrderDetail(10579,15,15.5m,10,0), + NorthwindFactory.OrderDetail(10579,75,7.75m,21,0), + NorthwindFactory.OrderDetail(10580,14,23.25m,15,0.05), + + NorthwindFactory.OrderDetail(10580,41,9.65m,9,0.05), + NorthwindFactory.OrderDetail(10580,65,21.05m,30,0.05), + NorthwindFactory.OrderDetail(10581,75,7.75m,50,0.2), + NorthwindFactory.OrderDetail(10582,57,19.5m,4,0), + NorthwindFactory.OrderDetail(10582,76,18,14,0), + NorthwindFactory.OrderDetail(10583,29,123.79m,10,0), + NorthwindFactory.OrderDetail(10583,60,34,24,0.15), + NorthwindFactory.OrderDetail(10583,69,36,10,0.15), + NorthwindFactory.OrderDetail(10584,31,12.5m,50,0.05), + NorthwindFactory.OrderDetail(10585,47,9.5m,15,0), + + NorthwindFactory.OrderDetail(10586,52,7,4,0.15), + NorthwindFactory.OrderDetail(10587,26,31.23m,6,0), + NorthwindFactory.OrderDetail(10587,35,18,20,0), + NorthwindFactory.OrderDetail(10587,77,13,20,0), + NorthwindFactory.OrderDetail(10588,18,62.5m,40,0.2), + NorthwindFactory.OrderDetail(10588,42,14,100,0.2), + NorthwindFactory.OrderDetail(10589,35,18,4,0), + NorthwindFactory.OrderDetail(10590,1,18,20,0), + NorthwindFactory.OrderDetail(10590,77,13,60,0.05), + NorthwindFactory.OrderDetail(10591,3,10,14,0), + + NorthwindFactory.OrderDetail(10591,7,30,10,0), + NorthwindFactory.OrderDetail(10591,54,7.45m,50,0), + NorthwindFactory.OrderDetail(10592,15,15.5m,25,0.05), + NorthwindFactory.OrderDetail(10592,26,31.23m,5,0.05), + NorthwindFactory.OrderDetail(10593,20,81,21,0.2), + NorthwindFactory.OrderDetail(10593,69,36,20,0.2), + NorthwindFactory.OrderDetail(10593,76,18,4,0.2), + NorthwindFactory.OrderDetail(10594,52,7,24,0), + NorthwindFactory.OrderDetail(10594,58,13.25m,30,0), + NorthwindFactory.OrderDetail(10595,35,18,30,0.25), + + NorthwindFactory.OrderDetail(10595,61,28.5m,120,0.25), + NorthwindFactory.OrderDetail(10595,69,36,65,0.25), + NorthwindFactory.OrderDetail(10596,56,38,5,0.2), + NorthwindFactory.OrderDetail(10596,63,43.9m,24,0.2), + NorthwindFactory.OrderDetail(10596,75,7.75m,30,0.2), + NorthwindFactory.OrderDetail(10597,24,4.5m,35,0.2), + NorthwindFactory.OrderDetail(10597,57,19.5m,20,0), + NorthwindFactory.OrderDetail(10597,65,21.05m,12,0.2), + NorthwindFactory.OrderDetail(10598,27,43.9m,50,0), + NorthwindFactory.OrderDetail(10598,71,21.5m,9,0), + + NorthwindFactory.OrderDetail(10599,62,49.3m,10,0), + NorthwindFactory.OrderDetail(10600,54,7.45m,4,0), + NorthwindFactory.OrderDetail(10600,73,15,30,0), + NorthwindFactory.OrderDetail(10601,13,6,60,0), + NorthwindFactory.OrderDetail(10601,59,55,35,0), + NorthwindFactory.OrderDetail(10602,77,13,5,0.25), + NorthwindFactory.OrderDetail(10603,22,21,48,0), + NorthwindFactory.OrderDetail(10603,49,20,25,0.05), + NorthwindFactory.OrderDetail(10604,48,12.75m,6,0.1), + NorthwindFactory.OrderDetail(10604,76,18,10,0.1), + + NorthwindFactory.OrderDetail(10605,16,17.45m,30,0.05), + NorthwindFactory.OrderDetail(10605,59,55,20,0.05), + NorthwindFactory.OrderDetail(10605,60,34,70,0.05), + NorthwindFactory.OrderDetail(10605,71,21.5m,15,0.05), + NorthwindFactory.OrderDetail(10606,4,22,20,0.2), + NorthwindFactory.OrderDetail(10606,55,24,20,0.2), + NorthwindFactory.OrderDetail(10606,62,49.3m,10,0.2), + NorthwindFactory.OrderDetail(10607,7,30,45,0), + NorthwindFactory.OrderDetail(10607,17,39,100,0), + NorthwindFactory.OrderDetail(10607,33,2.5m,14,0), + + NorthwindFactory.OrderDetail(10607,40,18.4m,42,0), + NorthwindFactory.OrderDetail(10607,72,34.8m,12,0), + NorthwindFactory.OrderDetail(10608,56,38,28,0), + NorthwindFactory.OrderDetail(10609,1,18,3,0), + NorthwindFactory.OrderDetail(10609,10,31,10,0), + NorthwindFactory.OrderDetail(10609,21,10,6,0), + NorthwindFactory.OrderDetail(10610,36,19,21,0.25), + NorthwindFactory.OrderDetail(10611,1,18,6,0), + NorthwindFactory.OrderDetail(10611,2,19,10,0), + NorthwindFactory.OrderDetail(10611,60,34,15,0), + + NorthwindFactory.OrderDetail(10612,10,31,70,0), + NorthwindFactory.OrderDetail(10612,36,19,55,0), + NorthwindFactory.OrderDetail(10612,49,20,18,0), + NorthwindFactory.OrderDetail(10612,60,34,40,0), + NorthwindFactory.OrderDetail(10612,76,18,80,0), + NorthwindFactory.OrderDetail(10613,13,6,8,0.1), + NorthwindFactory.OrderDetail(10613,75,7.75m,40,0), + NorthwindFactory.OrderDetail(10614,11,21,14,0), + NorthwindFactory.OrderDetail(10614,21,10,8,0), + NorthwindFactory.OrderDetail(10614,39,18,5,0), + + NorthwindFactory.OrderDetail(10615,55,24,5,0), + NorthwindFactory.OrderDetail(10616,38,263.5m,15,0.05), + NorthwindFactory.OrderDetail(10616,56,38,14,0), + NorthwindFactory.OrderDetail(10616,70,15,15,0.05), + NorthwindFactory.OrderDetail(10616,71,21.5m,15,0.05), + NorthwindFactory.OrderDetail(10617,59,55,30,0.15), + NorthwindFactory.OrderDetail(10618,6,25,70,0), + NorthwindFactory.OrderDetail(10618,56,38,20,0), + NorthwindFactory.OrderDetail(10618,68,12.5m,15,0), + NorthwindFactory.OrderDetail(10619,21,10,42,0), + + NorthwindFactory.OrderDetail(10619,22,21,40,0), + NorthwindFactory.OrderDetail(10620,24,4.5m,5,0), + NorthwindFactory.OrderDetail(10620,52,7,5,0), + NorthwindFactory.OrderDetail(10621,19,9.2m,5,0), + NorthwindFactory.OrderDetail(10621,23,9,10,0), + NorthwindFactory.OrderDetail(10621,70,15,20,0), + NorthwindFactory.OrderDetail(10621,71,21.5m,15,0), + NorthwindFactory.OrderDetail(10622,2,19,20,0), + NorthwindFactory.OrderDetail(10622,68,12.5m,18,0.2), + NorthwindFactory.OrderDetail(10623,14,23.25m,21,0), + + NorthwindFactory.OrderDetail(10623,19,9.2m,15,0.1), + NorthwindFactory.OrderDetail(10623,21,10,25,0.1), + NorthwindFactory.OrderDetail(10623,24,4.5m,3,0), + NorthwindFactory.OrderDetail(10623,35,18,30,0.1), + NorthwindFactory.OrderDetail(10624,28,45.6m,10,0), + NorthwindFactory.OrderDetail(10624,29,123.79m,6,0), + NorthwindFactory.OrderDetail(10624,44,19.45m,10,0), + NorthwindFactory.OrderDetail(10625,14,23.25m,3,0), + NorthwindFactory.OrderDetail(10625,42,14,5,0), + NorthwindFactory.OrderDetail(10625,60,34,10,0), + + NorthwindFactory.OrderDetail(10626,53,32.8m,12,0), + NorthwindFactory.OrderDetail(10626,60,34,20,0), + NorthwindFactory.OrderDetail(10626,71,21.5m,20,0), + NorthwindFactory.OrderDetail(10627,62,49.3m,15,0), + NorthwindFactory.OrderDetail(10627,73,15,35,0.15), + NorthwindFactory.OrderDetail(10628,1,18,25,0), + NorthwindFactory.OrderDetail(10629,29,123.79m,20,0), + NorthwindFactory.OrderDetail(10629,64,33.25m,9,0), + NorthwindFactory.OrderDetail(10630,55,24,12,0.05), + NorthwindFactory.OrderDetail(10630,76,18,35,0), + + NorthwindFactory.OrderDetail(10631,75,7.75m,8,0.1), + NorthwindFactory.OrderDetail(10632,2,19,30,0.05), + NorthwindFactory.OrderDetail(10632,33,2.5m,20,0.05), + NorthwindFactory.OrderDetail(10633,12,38,36,0.15), + NorthwindFactory.OrderDetail(10633,13,6,13,0.15), + NorthwindFactory.OrderDetail(10633,26,31.23m,35,0.15), + NorthwindFactory.OrderDetail(10633,62,49.3m,80,0.15), + NorthwindFactory.OrderDetail(10634,7,30,35,0), + NorthwindFactory.OrderDetail(10634,18,62.5m,50,0), + NorthwindFactory.OrderDetail(10634,51,53,15,0), + + NorthwindFactory.OrderDetail(10634,75,7.75m,2,0), + NorthwindFactory.OrderDetail(10635,4,22,10,0.1), + NorthwindFactory.OrderDetail(10635,5,21.35m,15,0.1), + NorthwindFactory.OrderDetail(10635,22,21,40,0), + NorthwindFactory.OrderDetail(10636,4,22,25,0), + NorthwindFactory.OrderDetail(10636,58,13.25m,6,0), + NorthwindFactory.OrderDetail(10637,11,21,10,0), + NorthwindFactory.OrderDetail(10637,50,16.25m,25,0.05), + NorthwindFactory.OrderDetail(10637,56,38,60,0.05), + NorthwindFactory.OrderDetail(10638,45,9.5m,20,0), + + NorthwindFactory.OrderDetail(10638,65,21.05m,21,0), + NorthwindFactory.OrderDetail(10638,72,34.8m,60,0), + NorthwindFactory.OrderDetail(10639,18,62.5m,8,0), + NorthwindFactory.OrderDetail(10640,69,36,20,0.25), + NorthwindFactory.OrderDetail(10640,70,15,15,0.25), + NorthwindFactory.OrderDetail(10641,2,19,50,0), + NorthwindFactory.OrderDetail(10641,40,18.4m,60,0), + NorthwindFactory.OrderDetail(10642,21,10,30,0.2), + NorthwindFactory.OrderDetail(10642,61,28.5m,20,0.2), + NorthwindFactory.OrderDetail(10643,28,45.6m,15,0.25), + + NorthwindFactory.OrderDetail(10643,39,18,21,0.25), + NorthwindFactory.OrderDetail(10643,46,12,2,0.25), + NorthwindFactory.OrderDetail(10644,18,62.5m,4,0.1), + NorthwindFactory.OrderDetail(10644,43,46,20,0), + NorthwindFactory.OrderDetail(10644,46,12,21,0.1), + NorthwindFactory.OrderDetail(10645,18,62.5m,20,0), + NorthwindFactory.OrderDetail(10645,36,19,15,0), + NorthwindFactory.OrderDetail(10646,1,18,15,0.25), + NorthwindFactory.OrderDetail(10646,10,31,18,0.25), + NorthwindFactory.OrderDetail(10646,71,21.5m,30,0.25), + + NorthwindFactory.OrderDetail(10646,77,13,35,0.25), + NorthwindFactory.OrderDetail(10647,19,9.2m,30,0), + NorthwindFactory.OrderDetail(10647,39,18,20,0), + NorthwindFactory.OrderDetail(10648,22,21,15,0), + NorthwindFactory.OrderDetail(10648,24,4.5m,15,0.15), + NorthwindFactory.OrderDetail(10649,28,45.6m,20,0), + NorthwindFactory.OrderDetail(10649,72,34.8m,15,0), + NorthwindFactory.OrderDetail(10650,30,25.89m,30,0), + NorthwindFactory.OrderDetail(10650,53,32.8m,25,0.05), + NorthwindFactory.OrderDetail(10650,54,7.45m,30,0), + + NorthwindFactory.OrderDetail(10651,19,9.2m,12,0.25), + NorthwindFactory.OrderDetail(10651,22,21,20,0.25), + NorthwindFactory.OrderDetail(10652,30,25.89m,2,0.25), + NorthwindFactory.OrderDetail(10652,42,14,20,0), + NorthwindFactory.OrderDetail(10653,16,17.45m,30,0.1), + NorthwindFactory.OrderDetail(10653,60,34,20,0.1), + NorthwindFactory.OrderDetail(10654,4,22,12,0.1), + NorthwindFactory.OrderDetail(10654,39,18,20,0.1), + NorthwindFactory.OrderDetail(10654,54,7.45m,6,0.1), + NorthwindFactory.OrderDetail(10655,41,9.65m,20,0.2), + + NorthwindFactory.OrderDetail(10656,14,23.25m,3,0.1), + NorthwindFactory.OrderDetail(10656,44,19.45m,28,0.1), + NorthwindFactory.OrderDetail(10656,47,9.5m,6,0.1), + NorthwindFactory.OrderDetail(10657,15,15.5m,50,0), + NorthwindFactory.OrderDetail(10657,41,9.65m,24,0), + NorthwindFactory.OrderDetail(10657,46,12,45,0), + NorthwindFactory.OrderDetail(10657,47,9.5m,10,0), + NorthwindFactory.OrderDetail(10657,56,38,45,0), + NorthwindFactory.OrderDetail(10657,60,34,30,0), + NorthwindFactory.OrderDetail(10658,21,10,60,0), + + NorthwindFactory.OrderDetail(10658,40,18.4m,70,0.05), + NorthwindFactory.OrderDetail(10658,60,34,55,0.05), + NorthwindFactory.OrderDetail(10658,77,13,70,0.05), + NorthwindFactory.OrderDetail(10659,31,12.5m,20,0.05), + NorthwindFactory.OrderDetail(10659,40,18.4m,24,0.05), + NorthwindFactory.OrderDetail(10659,70,15,40,0.05), + NorthwindFactory.OrderDetail(10660,20,81,21,0), + NorthwindFactory.OrderDetail(10661,39,18,3,0.2), + NorthwindFactory.OrderDetail(10661,58,13.25m,49,0.2), + NorthwindFactory.OrderDetail(10662,68,12.5m,10,0), + + NorthwindFactory.OrderDetail(10663,40,18.4m,30,0.05), + NorthwindFactory.OrderDetail(10663,42,14,30,0.05), + NorthwindFactory.OrderDetail(10663,51,53,20,0.05), + NorthwindFactory.OrderDetail(10664,10,31,24,0.15), + NorthwindFactory.OrderDetail(10664,56,38,12,0.15), + NorthwindFactory.OrderDetail(10664,65,21.05m,15,0.15), + NorthwindFactory.OrderDetail(10665,51,53,20,0), + NorthwindFactory.OrderDetail(10665,59,55,1,0), + NorthwindFactory.OrderDetail(10665,76,18,10,0), + NorthwindFactory.OrderDetail(10666,29,123.79m,36,0), + + NorthwindFactory.OrderDetail(10666,65,21.05m,10,0), + NorthwindFactory.OrderDetail(10667,69,36,45,0.2), + NorthwindFactory.OrderDetail(10667,71,21.5m,14,0.2), + NorthwindFactory.OrderDetail(10668,31,12.5m,8,0.1), + NorthwindFactory.OrderDetail(10668,55,24,4,0.1), + NorthwindFactory.OrderDetail(10668,64,33.25m,15,0.1), + NorthwindFactory.OrderDetail(10669,36,19,30,0), + NorthwindFactory.OrderDetail(10670,23,9,32,0), + NorthwindFactory.OrderDetail(10670,46,12,60,0), + NorthwindFactory.OrderDetail(10670,67,14,25,0), + + NorthwindFactory.OrderDetail(10670,73,15,50,0), + NorthwindFactory.OrderDetail(10670,75,7.75m,25,0), + NorthwindFactory.OrderDetail(10671,16,17.45m,10,0), + NorthwindFactory.OrderDetail(10671,62,49.3m,10,0), + NorthwindFactory.OrderDetail(10671,65,21.05m,12,0), + NorthwindFactory.OrderDetail(10672,38,263.5m,15,0.1), + NorthwindFactory.OrderDetail(10672,71,21.5m,12,0), + NorthwindFactory.OrderDetail(10673,16,17.45m,3,0), + NorthwindFactory.OrderDetail(10673,42,14,6,0), + NorthwindFactory.OrderDetail(10673,43,46,6,0), + + NorthwindFactory.OrderDetail(10674,23,9,5,0), + NorthwindFactory.OrderDetail(10675,14,23.25m,30,0), + NorthwindFactory.OrderDetail(10675,53,32.8m,10,0), + NorthwindFactory.OrderDetail(10675,58,13.25m,30,0), + NorthwindFactory.OrderDetail(10676,10,31,2,0), + NorthwindFactory.OrderDetail(10676,19,9.2m,7,0), + NorthwindFactory.OrderDetail(10676,44,19.45m,21,0), + NorthwindFactory.OrderDetail(10677,26,31.23m,30,0.15), + NorthwindFactory.OrderDetail(10677,33,2.5m,8,0.15), + NorthwindFactory.OrderDetail(10678,12,38,100,0), + + NorthwindFactory.OrderDetail(10678,33,2.5m,30,0), + NorthwindFactory.OrderDetail(10678,41,9.65m,120,0), + NorthwindFactory.OrderDetail(10678,54,7.45m,30,0), + NorthwindFactory.OrderDetail(10679,59,55,12,0), + NorthwindFactory.OrderDetail(10680,16,17.45m,50,0.25), + NorthwindFactory.OrderDetail(10680,31,12.5m,20,0.25), + NorthwindFactory.OrderDetail(10680,42,14,40,0.25), + NorthwindFactory.OrderDetail(10681,19,9.2m,30,0.1), + NorthwindFactory.OrderDetail(10681,21,10,12,0.1), + NorthwindFactory.OrderDetail(10681,64,33.25m,28,0), + + NorthwindFactory.OrderDetail(10682,33,2.5m,30,0), + NorthwindFactory.OrderDetail(10682,66,17,4,0), + NorthwindFactory.OrderDetail(10682,75,7.75m,30,0), + NorthwindFactory.OrderDetail(10683,52,7,9,0), + NorthwindFactory.OrderDetail(10684,40,18.4m,20,0), + NorthwindFactory.OrderDetail(10684,47,9.5m,40,0), + NorthwindFactory.OrderDetail(10684,60,34,30,0), + NorthwindFactory.OrderDetail(10685,10,31,20,0), + NorthwindFactory.OrderDetail(10685,41,9.65m,4,0), + NorthwindFactory.OrderDetail(10685,47,9.5m,15,0), + + NorthwindFactory.OrderDetail(10686,17,39,30,0.2), + NorthwindFactory.OrderDetail(10686,26,31.23m,15,0), + NorthwindFactory.OrderDetail(10687,9,97,50,0.25), + NorthwindFactory.OrderDetail(10687,29,123.79m,10,0), + NorthwindFactory.OrderDetail(10687,36,19,6,0.25), + NorthwindFactory.OrderDetail(10688,10,31,18,0.1), + NorthwindFactory.OrderDetail(10688,28,45.6m,60,0.1), + NorthwindFactory.OrderDetail(10688,34,14,14,0), + NorthwindFactory.OrderDetail(10689,1,18,35,0.25), + NorthwindFactory.OrderDetail(10690,56,38,20,0.25), + + NorthwindFactory.OrderDetail(10690,77,13,30,0.25), + NorthwindFactory.OrderDetail(10691,1,18,30,0), + NorthwindFactory.OrderDetail(10691,29,123.79m,40,0), + NorthwindFactory.OrderDetail(10691,43,46,40,0), + NorthwindFactory.OrderDetail(10691,44,19.45m,24,0), + NorthwindFactory.OrderDetail(10691,62,49.3m,48,0), + NorthwindFactory.OrderDetail(10692,63,43.9m,20,0), + NorthwindFactory.OrderDetail(10693,9,97,6,0), + NorthwindFactory.OrderDetail(10693,54,7.45m,60,0.15), + NorthwindFactory.OrderDetail(10693,69,36,30,0.15), + + NorthwindFactory.OrderDetail(10693,73,15,15,0.15), + NorthwindFactory.OrderDetail(10694,7,30,90,0), + NorthwindFactory.OrderDetail(10694,59,55,25,0), + NorthwindFactory.OrderDetail(10694,70,15,50,0), + NorthwindFactory.OrderDetail(10695,8,40,10,0), + NorthwindFactory.OrderDetail(10695,12,38,4,0), + NorthwindFactory.OrderDetail(10695,24,4.5m,20,0), + NorthwindFactory.OrderDetail(10696,17,39,20,0), + NorthwindFactory.OrderDetail(10696,46,12,18,0), + NorthwindFactory.OrderDetail(10697,19,9.2m,7,0.25), + + NorthwindFactory.OrderDetail(10697,35,18,9,0.25), + NorthwindFactory.OrderDetail(10697,58,13.25m,30,0.25), + NorthwindFactory.OrderDetail(10697,70,15,30,0.25), + NorthwindFactory.OrderDetail(10698,11,21,15,0), + NorthwindFactory.OrderDetail(10698,17,39,8,0.05), + NorthwindFactory.OrderDetail(10698,29,123.79m,12,0.05), + NorthwindFactory.OrderDetail(10698,65,21.05m,65,0.05), + NorthwindFactory.OrderDetail(10698,70,15,8,0.05), + NorthwindFactory.OrderDetail(10699,47,9.5m,12,0), + NorthwindFactory.OrderDetail(10700,1,18,5,0.2), + + NorthwindFactory.OrderDetail(10700,34,14,12,0.2), + NorthwindFactory.OrderDetail(10700,68,12.5m,40,0.2), + NorthwindFactory.OrderDetail(10700,71,21.5m,60,0.2), + NorthwindFactory.OrderDetail(10701,59,55,42,0.15), + NorthwindFactory.OrderDetail(10701,71,21.5m,20,0.15), + NorthwindFactory.OrderDetail(10701,76,18,35,0.15), + NorthwindFactory.OrderDetail(10702,3,10,6,0), + NorthwindFactory.OrderDetail(10702,76,18,15,0), + NorthwindFactory.OrderDetail(10703,2,19,5,0), + NorthwindFactory.OrderDetail(10703,59,55,35,0), + + NorthwindFactory.OrderDetail(10703,73,15,35,0), + NorthwindFactory.OrderDetail(10704,4,22,6,0), + NorthwindFactory.OrderDetail(10704,24,4.5m,35,0), + NorthwindFactory.OrderDetail(10704,48,12.75m,24,0), + NorthwindFactory.OrderDetail(10705,31,12.5m,20,0), + NorthwindFactory.OrderDetail(10705,32,32,4,0), + NorthwindFactory.OrderDetail(10706,16,17.45m,20,0), + NorthwindFactory.OrderDetail(10706,43,46,24,0), + NorthwindFactory.OrderDetail(10706,59,55,8,0), + NorthwindFactory.OrderDetail(10707,55,24,21,0), + + NorthwindFactory.OrderDetail(10707,57,19.5m,40,0), + NorthwindFactory.OrderDetail(10707,70,15,28,0.15), + NorthwindFactory.OrderDetail(10708,5,21.35m,4,0), + NorthwindFactory.OrderDetail(10708,36,19,5,0), + NorthwindFactory.OrderDetail(10709,8,40,40,0), + NorthwindFactory.OrderDetail(10709,51,53,28,0), + NorthwindFactory.OrderDetail(10709,60,34,10,0), + NorthwindFactory.OrderDetail(10710,19,9.2m,5,0), + NorthwindFactory.OrderDetail(10710,47,9.5m,5,0), + NorthwindFactory.OrderDetail(10711,19,9.2m,12,0), + + NorthwindFactory.OrderDetail(10711,41,9.65m,42,0), + NorthwindFactory.OrderDetail(10711,53,32.8m,120,0), + NorthwindFactory.OrderDetail(10712,53,32.8m,3,0.05), + NorthwindFactory.OrderDetail(10712,56,38,30,0), + NorthwindFactory.OrderDetail(10713,10,31,18,0), + NorthwindFactory.OrderDetail(10713,26,31.23m,30,0), + NorthwindFactory.OrderDetail(10713,45,9.5m,110,0), + NorthwindFactory.OrderDetail(10713,46,12,24,0), + NorthwindFactory.OrderDetail(10714,2,19,30,0.25), + NorthwindFactory.OrderDetail(10714,17,39,27,0.25), + + NorthwindFactory.OrderDetail(10714,47,9.5m,50,0.25), + NorthwindFactory.OrderDetail(10714,56,38,18,0.25), + NorthwindFactory.OrderDetail(10714,58,13.25m,12,0.25), + NorthwindFactory.OrderDetail(10715,10,31,21,0), + NorthwindFactory.OrderDetail(10715,71,21.5m,30,0), + NorthwindFactory.OrderDetail(10716,21,10,5,0), + NorthwindFactory.OrderDetail(10716,51,53,7,0), + NorthwindFactory.OrderDetail(10716,61,28.5m,10,0), + NorthwindFactory.OrderDetail(10717,21,10,32,0.05), + NorthwindFactory.OrderDetail(10717,54,7.45m,15,0), + + NorthwindFactory.OrderDetail(10717,69,36,25,0.05), + NorthwindFactory.OrderDetail(10718,12,38,36,0), + NorthwindFactory.OrderDetail(10718,16,17.45m,20,0), + NorthwindFactory.OrderDetail(10718,36,19,40,0), + NorthwindFactory.OrderDetail(10718,62,49.3m,20,0), + NorthwindFactory.OrderDetail(10719,18,62.5m,12,0.25), + NorthwindFactory.OrderDetail(10719,30,25.89m,3,0.25), + NorthwindFactory.OrderDetail(10719,54,7.45m,40,0.25), + NorthwindFactory.OrderDetail(10720,35,18,21,0), + NorthwindFactory.OrderDetail(10720,71,21.5m,8,0), + + NorthwindFactory.OrderDetail(10721,44,19.45m,50,0.05), + NorthwindFactory.OrderDetail(10722,2,19,3,0), + NorthwindFactory.OrderDetail(10722,31,12.5m,50,0), + NorthwindFactory.OrderDetail(10722,68,12.5m,45,0), + NorthwindFactory.OrderDetail(10722,75,7.75m,42,0), + NorthwindFactory.OrderDetail(10723,26,31.23m,15,0), + NorthwindFactory.OrderDetail(10724,10,31,16,0), + NorthwindFactory.OrderDetail(10724,61,28.5m,5,0), + NorthwindFactory.OrderDetail(10725,41,9.65m,12,0), + NorthwindFactory.OrderDetail(10725,52,7,4,0), + + NorthwindFactory.OrderDetail(10725,55,24,6,0), + NorthwindFactory.OrderDetail(10726,4,22,25,0), + NorthwindFactory.OrderDetail(10726,11,21,5,0), + NorthwindFactory.OrderDetail(10727,17,39,20,0.05), + NorthwindFactory.OrderDetail(10727,56,38,10,0.05), + NorthwindFactory.OrderDetail(10727,59,55,10,0.05), + NorthwindFactory.OrderDetail(10728,30,25.89m,15,0), + NorthwindFactory.OrderDetail(10728,40,18.4m,6,0), + NorthwindFactory.OrderDetail(10728,55,24,12,0), + NorthwindFactory.OrderDetail(10728,60,34,15,0), + + NorthwindFactory.OrderDetail(10729,1,18,50,0), + NorthwindFactory.OrderDetail(10729,21,10,30,0), + NorthwindFactory.OrderDetail(10729,50,16.25m,40,0), + NorthwindFactory.OrderDetail(10730,16,17.45m,15,0.05), + NorthwindFactory.OrderDetail(10730,31,12.5m,3,0.05), + NorthwindFactory.OrderDetail(10730,65,21.05m,10,0.05), + NorthwindFactory.OrderDetail(10731,21,10,40,0.05), + NorthwindFactory.OrderDetail(10731,51,53,30,0.05), + NorthwindFactory.OrderDetail(10732,76,18,20,0), + NorthwindFactory.OrderDetail(10733,14,23.25m,16,0), + + NorthwindFactory.OrderDetail(10733,28,45.6m,20,0), + NorthwindFactory.OrderDetail(10733,52,7,25,0), + NorthwindFactory.OrderDetail(10734,6,25,30,0), + NorthwindFactory.OrderDetail(10734,30,25.89m,15,0), + NorthwindFactory.OrderDetail(10734,76,18,20,0), + NorthwindFactory.OrderDetail(10735,61,28.5m,20,0.1), + NorthwindFactory.OrderDetail(10735,77,13,2,0.1), + NorthwindFactory.OrderDetail(10736,65,21.05m,40,0), + NorthwindFactory.OrderDetail(10736,75,7.75m,20,0), + NorthwindFactory.OrderDetail(10737,13,6,4,0), + + NorthwindFactory.OrderDetail(10737,41,9.65m,12,0), + NorthwindFactory.OrderDetail(10738,16,17.45m,3,0), + NorthwindFactory.OrderDetail(10739,36,19,6,0), + NorthwindFactory.OrderDetail(10739,52,7,18,0), + NorthwindFactory.OrderDetail(10740,28,45.6m,5,0.2), + NorthwindFactory.OrderDetail(10740,35,18,35,0.2), + NorthwindFactory.OrderDetail(10740,45,9.5m,40,0.2), + NorthwindFactory.OrderDetail(10740,56,38,14,0.2), + NorthwindFactory.OrderDetail(10741,2,19,15,0.2), + NorthwindFactory.OrderDetail(10742,3,10,20,0), + + NorthwindFactory.OrderDetail(10742,60,34,50,0), + NorthwindFactory.OrderDetail(10742,72,34.8m,35,0), + NorthwindFactory.OrderDetail(10743,46,12,28,0.05), + NorthwindFactory.OrderDetail(10744,40,18.4m,50,0.2), + NorthwindFactory.OrderDetail(10745,18,62.5m,24,0), + NorthwindFactory.OrderDetail(10745,44,19.45m,16,0), + NorthwindFactory.OrderDetail(10745,59,55,45,0), + NorthwindFactory.OrderDetail(10745,72,34.8m,7,0), + NorthwindFactory.OrderDetail(10746,13,6,6,0), + NorthwindFactory.OrderDetail(10746,42,14,28,0), + + NorthwindFactory.OrderDetail(10746,62,49.3m,9,0), + NorthwindFactory.OrderDetail(10746,69,36,40,0), + NorthwindFactory.OrderDetail(10747,31,12.5m,8,0), + NorthwindFactory.OrderDetail(10747,41,9.65m,35,0), + NorthwindFactory.OrderDetail(10747,63,43.9m,9,0), + NorthwindFactory.OrderDetail(10747,69,36,30,0), + NorthwindFactory.OrderDetail(10748,23,9,44,0), + NorthwindFactory.OrderDetail(10748,40,18.4m,40,0), + NorthwindFactory.OrderDetail(10748,56,38,28,0), + NorthwindFactory.OrderDetail(10749,56,38,15,0), + + NorthwindFactory.OrderDetail(10749,59,55,6,0), + NorthwindFactory.OrderDetail(10749,76,18,10,0), + NorthwindFactory.OrderDetail(10750,14,23.25m,5,0.15), + NorthwindFactory.OrderDetail(10750,45,9.5m,40,0.15), + NorthwindFactory.OrderDetail(10750,59,55,25,0.15), + NorthwindFactory.OrderDetail(10751,26,31.23m,12,0.1), + NorthwindFactory.OrderDetail(10751,30,25.89m,30,0), + NorthwindFactory.OrderDetail(10751,50,16.25m,20,0.1), + NorthwindFactory.OrderDetail(10751,73,15,15,0), + NorthwindFactory.OrderDetail(10752,1,18,8,0), + + NorthwindFactory.OrderDetail(10752,69,36,3,0), + NorthwindFactory.OrderDetail(10753,45,9.5m,4,0), + NorthwindFactory.OrderDetail(10753,74,10,5,0), + NorthwindFactory.OrderDetail(10754,40,18.4m,3,0), + NorthwindFactory.OrderDetail(10755,47,9.5m,30,0.25), + NorthwindFactory.OrderDetail(10755,56,38,30,0.25), + NorthwindFactory.OrderDetail(10755,57,19.5m,14,0.25), + NorthwindFactory.OrderDetail(10755,69,36,25,0.25), + NorthwindFactory.OrderDetail(10756,18,62.5m,21,0.2), + NorthwindFactory.OrderDetail(10756,36,19,20,0.2), + + NorthwindFactory.OrderDetail(10756,68,12.5m,6,0.2), + NorthwindFactory.OrderDetail(10756,69,36,20,0.2), + NorthwindFactory.OrderDetail(10757,34,14,30,0), + NorthwindFactory.OrderDetail(10757,59,55,7,0), + NorthwindFactory.OrderDetail(10757,62,49.3m,30,0), + NorthwindFactory.OrderDetail(10757,64,33.25m,24,0), + NorthwindFactory.OrderDetail(10758,26,31.23m,20,0), + NorthwindFactory.OrderDetail(10758,52,7,60,0), + NorthwindFactory.OrderDetail(10758,70,15,40,0), + NorthwindFactory.OrderDetail(10759,32,32,10,0), + + NorthwindFactory.OrderDetail(10760,25,14,12,0.25), + NorthwindFactory.OrderDetail(10760,27,43.9m,40,0), + NorthwindFactory.OrderDetail(10760,43,46,30,0.25), + NorthwindFactory.OrderDetail(10761,25,14,35,0.25), + NorthwindFactory.OrderDetail(10761,75,7.75m,18,0), + NorthwindFactory.OrderDetail(10762,39,18,16,0), + NorthwindFactory.OrderDetail(10762,47,9.5m,30,0), + NorthwindFactory.OrderDetail(10762,51,53,28,0), + NorthwindFactory.OrderDetail(10762,56,38,60,0), + NorthwindFactory.OrderDetail(10763,21,10,40,0), + + NorthwindFactory.OrderDetail(10763,22,21,6,0), + NorthwindFactory.OrderDetail(10763,24,4.5m,20,0), + NorthwindFactory.OrderDetail(10764,3,10,20,0.1), + NorthwindFactory.OrderDetail(10764,39,18,130,0.1), + NorthwindFactory.OrderDetail(10765,65,21.05m,80,0.1), + NorthwindFactory.OrderDetail(10766,2,19,40,0), + NorthwindFactory.OrderDetail(10766,7,30,35,0), + NorthwindFactory.OrderDetail(10766,68,12.5m,40,0), + NorthwindFactory.OrderDetail(10767,42,14,2,0), + NorthwindFactory.OrderDetail(10768,22,21,4,0), + + NorthwindFactory.OrderDetail(10768,31,12.5m,50,0), + NorthwindFactory.OrderDetail(10768,60,34,15,0), + NorthwindFactory.OrderDetail(10768,71,21.5m,12,0), + NorthwindFactory.OrderDetail(10769,41,9.65m,30,0.05), + NorthwindFactory.OrderDetail(10769,52,7,15,0.05), + NorthwindFactory.OrderDetail(10769,61,28.5m,20,0), + NorthwindFactory.OrderDetail(10769,62,49.3m,15,0), + NorthwindFactory.OrderDetail(10770,11,21,15,0.25), + NorthwindFactory.OrderDetail(10771,71,21.5m,16,0), + NorthwindFactory.OrderDetail(10772,29,123.79m,18,0), + + NorthwindFactory.OrderDetail(10772,59,55,25,0), + NorthwindFactory.OrderDetail(10773,17,39,33,0), + NorthwindFactory.OrderDetail(10773,31,12.5m,70,0.2), + NorthwindFactory.OrderDetail(10773,75,7.75m,7,0.2), + NorthwindFactory.OrderDetail(10774,31,12.5m,2,0.25), + NorthwindFactory.OrderDetail(10774,66,17,50,0), + NorthwindFactory.OrderDetail(10775,10,31,6,0), + NorthwindFactory.OrderDetail(10775,67,14,3,0), + NorthwindFactory.OrderDetail(10776,31,12.5m,16,0.05), + NorthwindFactory.OrderDetail(10776,42,14,12,0.05), + + NorthwindFactory.OrderDetail(10776,45,9.5m,27,0.05), + NorthwindFactory.OrderDetail(10776,51,53,120,0.05), + NorthwindFactory.OrderDetail(10777,42,14,20,0.2), + NorthwindFactory.OrderDetail(10778,41,9.65m,10,0), + NorthwindFactory.OrderDetail(10779,16,17.45m,20,0), + NorthwindFactory.OrderDetail(10779,62,49.3m,20,0), + NorthwindFactory.OrderDetail(10780,70,15,35,0), + NorthwindFactory.OrderDetail(10780,77,13,15,0), + NorthwindFactory.OrderDetail(10781,54,7.45m,3,0.2), + NorthwindFactory.OrderDetail(10781,56,38,20,0.2), + + NorthwindFactory.OrderDetail(10781,74,10,35,0), + NorthwindFactory.OrderDetail(10782,31,12.5m,1,0), + NorthwindFactory.OrderDetail(10783,31,12.5m,10,0), + NorthwindFactory.OrderDetail(10783,38,263.5m,5,0), + NorthwindFactory.OrderDetail(10784,36,19,30,0), + NorthwindFactory.OrderDetail(10784,39,18,2,0.15), + NorthwindFactory.OrderDetail(10784,72,34.8m,30,0.15), + NorthwindFactory.OrderDetail(10785,10,31,10,0), + NorthwindFactory.OrderDetail(10785,75,7.75m,10,0), + NorthwindFactory.OrderDetail(10786,8,40,30,0.2), + + NorthwindFactory.OrderDetail(10786,30,25.89m,15,0.2), + NorthwindFactory.OrderDetail(10786,75,7.75m,42,0.2), + NorthwindFactory.OrderDetail(10787,2,19,15,0.05), + NorthwindFactory.OrderDetail(10787,29,123.79m,20,0.05), + NorthwindFactory.OrderDetail(10788,19,9.2m,50,0.05), + NorthwindFactory.OrderDetail(10788,75,7.75m,40,0.05), + NorthwindFactory.OrderDetail(10789,18,62.5m,30,0), + NorthwindFactory.OrderDetail(10789,35,18,15,0), + NorthwindFactory.OrderDetail(10789,63,43.9m,30,0), + NorthwindFactory.OrderDetail(10789,68,12.5m,18,0), + + NorthwindFactory.OrderDetail(10790,7,30,3,0.15), + NorthwindFactory.OrderDetail(10790,56,38,20,0.15), + NorthwindFactory.OrderDetail(10791,29,123.79m,14,0.05), + NorthwindFactory.OrderDetail(10791,41,9.65m,20,0.05), + NorthwindFactory.OrderDetail(10792,2,19,10,0), + NorthwindFactory.OrderDetail(10792,54,7.45m,3,0), + NorthwindFactory.OrderDetail(10792,68,12.5m,15,0), + NorthwindFactory.OrderDetail(10793,41,9.65m,14,0), + NorthwindFactory.OrderDetail(10793,52,7,8,0), + NorthwindFactory.OrderDetail(10794,14,23.25m,15,0.2), + + NorthwindFactory.OrderDetail(10794,54,7.45m,6,0.2), + NorthwindFactory.OrderDetail(10795,16,17.45m,65,0), + NorthwindFactory.OrderDetail(10795,17,39,35,0.25), + NorthwindFactory.OrderDetail(10796,26,31.23m,21,0.2), + NorthwindFactory.OrderDetail(10796,44,19.45m,10,0), + NorthwindFactory.OrderDetail(10796,64,33.25m,35,0.2), + NorthwindFactory.OrderDetail(10796,69,36,24,0.2), + NorthwindFactory.OrderDetail(10797,11,21,20,0), + NorthwindFactory.OrderDetail(10798,62,49.3m,2,0), + NorthwindFactory.OrderDetail(10798,72,34.8m,10,0), + + NorthwindFactory.OrderDetail(10799,13,6,20,0.15), + NorthwindFactory.OrderDetail(10799,24,4.5m,20,0.15), + NorthwindFactory.OrderDetail(10799,59,55,25,0), + NorthwindFactory.OrderDetail(10800,11,21,50,0.1), + NorthwindFactory.OrderDetail(10800,51,53,10,0.1), + NorthwindFactory.OrderDetail(10800,54,7.45m,7,0.1), + NorthwindFactory.OrderDetail(10801,17,39,40,0.25), + NorthwindFactory.OrderDetail(10801,29,123.79m,20,0.25), + NorthwindFactory.OrderDetail(10802,30,25.89m,25,0.25), + NorthwindFactory.OrderDetail(10802,51,53,30,0.25), + + NorthwindFactory.OrderDetail(10802,55,24,60,0.25), + NorthwindFactory.OrderDetail(10802,62,49.3m,5,0.25), + NorthwindFactory.OrderDetail(10803,19,9.2m,24,0.05), + NorthwindFactory.OrderDetail(10803,25,14,15,0.05), + NorthwindFactory.OrderDetail(10803,59,55,15,0.05), + NorthwindFactory.OrderDetail(10804,10,31,36,0), + NorthwindFactory.OrderDetail(10804,28,45.6m,24,0), + NorthwindFactory.OrderDetail(10804,49,20,4,0.15), + NorthwindFactory.OrderDetail(10805,34,14,10,0), + NorthwindFactory.OrderDetail(10805,38,263.5m,10,0), + + NorthwindFactory.OrderDetail(10806,2,19,20,0.25), + NorthwindFactory.OrderDetail(10806,65,21.05m,2,0), + NorthwindFactory.OrderDetail(10806,74,10,15,0.25), + NorthwindFactory.OrderDetail(10807,40,18.4m,1,0), + NorthwindFactory.OrderDetail(10808,56,38,20,0.15), + NorthwindFactory.OrderDetail(10808,76,18,50,0.15), + NorthwindFactory.OrderDetail(10809,52,7,20,0), + NorthwindFactory.OrderDetail(10810,13,6,7,0), + NorthwindFactory.OrderDetail(10810,25,14,5,0), + NorthwindFactory.OrderDetail(10810,70,15,5,0), + + NorthwindFactory.OrderDetail(10811,19,9.2m,15,0), + NorthwindFactory.OrderDetail(10811,23,9,18,0), + NorthwindFactory.OrderDetail(10811,40,18.4m,30,0), + NorthwindFactory.OrderDetail(10812,31,12.5m,16,0.1), + NorthwindFactory.OrderDetail(10812,72,34.8m,40,0.1), + NorthwindFactory.OrderDetail(10812,77,13,20,0), + NorthwindFactory.OrderDetail(10813,2,19,12,0.2), + NorthwindFactory.OrderDetail(10813,46,12,35,0), + NorthwindFactory.OrderDetail(10814,41,9.65m,20,0), + NorthwindFactory.OrderDetail(10814,43,46,20,0.15), + + NorthwindFactory.OrderDetail(10814,48,12.75m,8,0.15), + NorthwindFactory.OrderDetail(10814,61,28.5m,30,0.15), + NorthwindFactory.OrderDetail(10815,33,2.5m,16,0), + NorthwindFactory.OrderDetail(10816,38,263.5m,30,0.05), + NorthwindFactory.OrderDetail(10816,62,49.3m,20,0.05), + NorthwindFactory.OrderDetail(10817,26,31.23m,40,0.15), + NorthwindFactory.OrderDetail(10817,38,263.5m,30,0), + NorthwindFactory.OrderDetail(10817,40,18.4m,60,0.15), + NorthwindFactory.OrderDetail(10817,62,49.3m,25,0.15), + NorthwindFactory.OrderDetail(10818,32,32,20,0), + + NorthwindFactory.OrderDetail(10818,41,9.65m,20,0), + NorthwindFactory.OrderDetail(10819,43,46,7,0), + NorthwindFactory.OrderDetail(10819,75,7.75m,20,0), + NorthwindFactory.OrderDetail(10820,56,38,30,0), + NorthwindFactory.OrderDetail(10821,35,18,20,0), + NorthwindFactory.OrderDetail(10821,51,53,6,0), + NorthwindFactory.OrderDetail(10822,62,49.3m,3,0), + NorthwindFactory.OrderDetail(10822,70,15,6,0), + NorthwindFactory.OrderDetail(10823,11,21,20,0.1), + NorthwindFactory.OrderDetail(10823,57,19.5m,15,0), + + NorthwindFactory.OrderDetail(10823,59,55,40,0.1), + NorthwindFactory.OrderDetail(10823,77,13,15,0.1), + NorthwindFactory.OrderDetail(10824,41,9.65m,12,0), + NorthwindFactory.OrderDetail(10824,70,15,9,0), + NorthwindFactory.OrderDetail(10825,26,31.23m,12,0), + NorthwindFactory.OrderDetail(10825,53,32.8m,20,0), + NorthwindFactory.OrderDetail(10826,31,12.5m,35,0), + NorthwindFactory.OrderDetail(10826,57,19.5m,15,0), + NorthwindFactory.OrderDetail(10827,10,31,15,0), + NorthwindFactory.OrderDetail(10827,39,18,21,0), + + NorthwindFactory.OrderDetail(10828,20,81,5,0), + NorthwindFactory.OrderDetail(10828,38,263.5m,2,0), + NorthwindFactory.OrderDetail(10829,2,19,10,0), + NorthwindFactory.OrderDetail(10829,8,40,20,0), + NorthwindFactory.OrderDetail(10829,13,6,10,0), + NorthwindFactory.OrderDetail(10829,60,34,21,0), + NorthwindFactory.OrderDetail(10830,6,25,6,0), + NorthwindFactory.OrderDetail(10830,39,18,28,0), + NorthwindFactory.OrderDetail(10830,60,34,30,0), + NorthwindFactory.OrderDetail(10830,68,12.5m,24,0), + + NorthwindFactory.OrderDetail(10831,19,9.2m,2,0), + NorthwindFactory.OrderDetail(10831,35,18,8,0), + NorthwindFactory.OrderDetail(10831,38,263.5m,8,0), + NorthwindFactory.OrderDetail(10831,43,46,9,0), + NorthwindFactory.OrderDetail(10832,13,6,3,0.2), + NorthwindFactory.OrderDetail(10832,25,14,10,0.2), + NorthwindFactory.OrderDetail(10832,44,19.45m,16,0.2), + NorthwindFactory.OrderDetail(10832,64,33.25m,3,0), + NorthwindFactory.OrderDetail(10833,7,30,20,0.1), + NorthwindFactory.OrderDetail(10833,31,12.5m,9,0.1), + + NorthwindFactory.OrderDetail(10833,53,32.8m,9,0.1), + NorthwindFactory.OrderDetail(10834,29,123.79m,8,0.05), + NorthwindFactory.OrderDetail(10834,30,25.89m,20,0.05), + NorthwindFactory.OrderDetail(10835,59,55,15,0), + NorthwindFactory.OrderDetail(10835,77,13,2,0.2), + NorthwindFactory.OrderDetail(10836,22,21,52,0), + NorthwindFactory.OrderDetail(10836,35,18,6,0), + NorthwindFactory.OrderDetail(10836,57,19.5m,24,0), + NorthwindFactory.OrderDetail(10836,60,34,60,0), + NorthwindFactory.OrderDetail(10836,64,33.25m,30,0), + + NorthwindFactory.OrderDetail(10837,13,6,6,0), + NorthwindFactory.OrderDetail(10837,40,18.4m,25,0), + NorthwindFactory.OrderDetail(10837,47,9.5m,40,0.25), + NorthwindFactory.OrderDetail(10837,76,18,21,0.25), + NorthwindFactory.OrderDetail(10838,1,18,4,0.25), + NorthwindFactory.OrderDetail(10838,18,62.5m,25,0.25), + NorthwindFactory.OrderDetail(10838,36,19,50,0.25), + NorthwindFactory.OrderDetail(10839,58,13.25m,30,0.1), + NorthwindFactory.OrderDetail(10839,72,34.8m,15,0.1), + NorthwindFactory.OrderDetail(10840,25,14,6,0.2), + + NorthwindFactory.OrderDetail(10840,39,18,10,0.2), + NorthwindFactory.OrderDetail(10841,10,31,16,0), + NorthwindFactory.OrderDetail(10841,56,38,30,0), + NorthwindFactory.OrderDetail(10841,59,55,50,0), + NorthwindFactory.OrderDetail(10841,77,13,15,0), + NorthwindFactory.OrderDetail(10842,11,21,15,0), + NorthwindFactory.OrderDetail(10842,43,46,5,0), + NorthwindFactory.OrderDetail(10842,68,12.5m,20,0), + NorthwindFactory.OrderDetail(10842,70,15,12,0), + NorthwindFactory.OrderDetail(10843,51,53,4,0.25), + + NorthwindFactory.OrderDetail(10844,22,21,35,0), + NorthwindFactory.OrderDetail(10845,23,9,70,0.1), + NorthwindFactory.OrderDetail(10845,35,18,25,0.1), + NorthwindFactory.OrderDetail(10845,42,14,42,0.1), + NorthwindFactory.OrderDetail(10845,58,13.25m,60,0.1), + NorthwindFactory.OrderDetail(10845,64,33.25m,48,0), + NorthwindFactory.OrderDetail(10846,4,22,21,0), + NorthwindFactory.OrderDetail(10846,70,15,30,0), + NorthwindFactory.OrderDetail(10846,74,10,20,0), + NorthwindFactory.OrderDetail(10847,1,18,80,0.2), + + NorthwindFactory.OrderDetail(10847,19,9.2m,12,0.2), + NorthwindFactory.OrderDetail(10847,37,26,60,0.2), + NorthwindFactory.OrderDetail(10847,45,9.5m,36,0.2), + NorthwindFactory.OrderDetail(10847,60,34,45,0.2), + NorthwindFactory.OrderDetail(10847,71,21.5m,55,0.2), + NorthwindFactory.OrderDetail(10848,5,21.35m,30,0), + NorthwindFactory.OrderDetail(10848,9,97,3,0), + NorthwindFactory.OrderDetail(10849,3,10,49,0), + NorthwindFactory.OrderDetail(10849,26,31.23m,18,0.15), + NorthwindFactory.OrderDetail(10850,25,14,20,0.15), + + NorthwindFactory.OrderDetail(10850,33,2.5m,4,0.15), + NorthwindFactory.OrderDetail(10850,70,15,30,0.15), + NorthwindFactory.OrderDetail(10851,2,19,5,0.05), + NorthwindFactory.OrderDetail(10851,25,14,10,0.05), + NorthwindFactory.OrderDetail(10851,57,19.5m,10,0.05), + NorthwindFactory.OrderDetail(10851,59,55,42,0.05), + NorthwindFactory.OrderDetail(10852,2,19,15,0), + NorthwindFactory.OrderDetail(10852,17,39,6,0), + NorthwindFactory.OrderDetail(10852,62,49.3m,50,0), + NorthwindFactory.OrderDetail(10853,18,62.5m,10,0), + + NorthwindFactory.OrderDetail(10854,10,31,100,0.15), + NorthwindFactory.OrderDetail(10854,13,6,65,0.15), + NorthwindFactory.OrderDetail(10855,16,17.45m,50,0), + NorthwindFactory.OrderDetail(10855,31,12.5m,14,0), + NorthwindFactory.OrderDetail(10855,56,38,24,0), + NorthwindFactory.OrderDetail(10855,65,21.05m,15,0.15), + NorthwindFactory.OrderDetail(10856,2,19,20,0), + NorthwindFactory.OrderDetail(10856,42,14,20,0), + NorthwindFactory.OrderDetail(10857,3,10,30,0), + NorthwindFactory.OrderDetail(10857,26,31.23m,35,0.25), + + NorthwindFactory.OrderDetail(10857,29,123.79m,10,0.25), + NorthwindFactory.OrderDetail(10858,7,30,5,0), + NorthwindFactory.OrderDetail(10858,27,43.9m,10,0), + NorthwindFactory.OrderDetail(10858,70,15,4,0), + NorthwindFactory.OrderDetail(10859,24,4.5m,40,0.25), + NorthwindFactory.OrderDetail(10859,54,7.45m,35,0.25), + NorthwindFactory.OrderDetail(10859,64,33.25m,30,0.25), + NorthwindFactory.OrderDetail(10860,51,53,3,0), + NorthwindFactory.OrderDetail(10860,76,18,20,0), + NorthwindFactory.OrderDetail(10861,17,39,42,0), + + NorthwindFactory.OrderDetail(10861,18,62.5m,20,0), + NorthwindFactory.OrderDetail(10861,21,10,40,0), + NorthwindFactory.OrderDetail(10861,33,2.5m,35,0), + NorthwindFactory.OrderDetail(10861,62,49.3m,3,0), + NorthwindFactory.OrderDetail(10862,11,21,25,0), + NorthwindFactory.OrderDetail(10862,52,7,8,0), + NorthwindFactory.OrderDetail(10863,1,18,20,0.15), + NorthwindFactory.OrderDetail(10863,58,13.25m,12,0.15), + NorthwindFactory.OrderDetail(10864,35,18,4,0), + NorthwindFactory.OrderDetail(10864,67,14,15,0), + + NorthwindFactory.OrderDetail(10865,38,263.5m,60,0.05), + NorthwindFactory.OrderDetail(10865,39,18,80,0.05), + NorthwindFactory.OrderDetail(10866,2,19,21,0.25), + NorthwindFactory.OrderDetail(10866,24,4.5m,6,0.25), + NorthwindFactory.OrderDetail(10866,30,25.89m,40,0.25), + NorthwindFactory.OrderDetail(10867,53,32.8m,3,0), + NorthwindFactory.OrderDetail(10868,26,31.23m,20,0), + NorthwindFactory.OrderDetail(10868,35,18,30,0), + NorthwindFactory.OrderDetail(10868,49,20,42,0.1), + NorthwindFactory.OrderDetail(10869,1,18,40,0), + + NorthwindFactory.OrderDetail(10869,11,21,10,0), + NorthwindFactory.OrderDetail(10869,23,9,50,0), + NorthwindFactory.OrderDetail(10869,68,12.5m,20,0), + NorthwindFactory.OrderDetail(10870,35,18,3,0), + NorthwindFactory.OrderDetail(10870,51,53,2,0), + NorthwindFactory.OrderDetail(10871,6,25,50,0.05), + NorthwindFactory.OrderDetail(10871,16,17.45m,12,0.05), + NorthwindFactory.OrderDetail(10871,17,39,16,0.05), + NorthwindFactory.OrderDetail(10872,55,24,10,0.05), + NorthwindFactory.OrderDetail(10872,62,49.3m,20,0.05), + + NorthwindFactory.OrderDetail(10872,64,33.25m,15,0.05), + NorthwindFactory.OrderDetail(10872,65,21.05m,21,0.05), + NorthwindFactory.OrderDetail(10873,21,10,20,0), + NorthwindFactory.OrderDetail(10873,28,45.6m,3,0), + NorthwindFactory.OrderDetail(10874,10,31,10,0), + NorthwindFactory.OrderDetail(10875,19,9.2m,25,0), + NorthwindFactory.OrderDetail(10875,47,9.5m,21,0.1), + NorthwindFactory.OrderDetail(10875,49,20,15,0), + NorthwindFactory.OrderDetail(10876,46,12,21,0), + NorthwindFactory.OrderDetail(10876,64,33.25m,20,0), + + NorthwindFactory.OrderDetail(10877,16,17.45m,30,0.25), + NorthwindFactory.OrderDetail(10877,18,62.5m,25,0), + NorthwindFactory.OrderDetail(10878,20,81,20,0.05), + NorthwindFactory.OrderDetail(10879,40,18.4m,12,0), + NorthwindFactory.OrderDetail(10879,65,21.05m,10,0), + NorthwindFactory.OrderDetail(10879,76,18,10,0), + NorthwindFactory.OrderDetail(10880,23,9,30,0.2), + NorthwindFactory.OrderDetail(10880,61,28.5m,30,0.2), + NorthwindFactory.OrderDetail(10880,70,15,50,0.2), + NorthwindFactory.OrderDetail(10881,73,15,10,0), + + NorthwindFactory.OrderDetail(10882,42,14,25,0), + NorthwindFactory.OrderDetail(10882,49,20,20,0.15), + NorthwindFactory.OrderDetail(10882,54,7.45m,32,0.15), + NorthwindFactory.OrderDetail(10883,24,4.5m,8,0), + NorthwindFactory.OrderDetail(10884,21,10,40,0.05), + NorthwindFactory.OrderDetail(10884,56,38,21,0.05), + NorthwindFactory.OrderDetail(10884,65,21.05m,12,0.05), + NorthwindFactory.OrderDetail(10885,2,19,20,0), + NorthwindFactory.OrderDetail(10885,24,4.5m,12,0), + NorthwindFactory.OrderDetail(10885,70,15,30,0), + + NorthwindFactory.OrderDetail(10885,77,13,25,0), + NorthwindFactory.OrderDetail(10886,10,31,70,0), + NorthwindFactory.OrderDetail(10886,31,12.5m,35,0), + NorthwindFactory.OrderDetail(10886,77,13,40,0), + NorthwindFactory.OrderDetail(10887,25,14,5,0), + NorthwindFactory.OrderDetail(10888,2,19,20,0), + NorthwindFactory.OrderDetail(10888,68,12.5m,18,0), + NorthwindFactory.OrderDetail(10889,11,21,40,0), + NorthwindFactory.OrderDetail(10889,38,263.5m,40,0), + NorthwindFactory.OrderDetail(10890,17,39,15,0), + + NorthwindFactory.OrderDetail(10890,34,14,10,0), + NorthwindFactory.OrderDetail(10890,41,9.65m,14,0), + NorthwindFactory.OrderDetail(10891,30,25.89m,15,0.05), + NorthwindFactory.OrderDetail(10892,59,55,40,0.05), + NorthwindFactory.OrderDetail(10893,8,40,30,0), + NorthwindFactory.OrderDetail(10893,24,4.5m,10,0), + NorthwindFactory.OrderDetail(10893,29,123.79m,24,0), + NorthwindFactory.OrderDetail(10893,30,25.89m,35,0), + NorthwindFactory.OrderDetail(10893,36,19,20,0), + NorthwindFactory.OrderDetail(10894,13,6,28,0.05), + + NorthwindFactory.OrderDetail(10894,69,36,50,0.05), + NorthwindFactory.OrderDetail(10894,75,7.75m,120,0.05), + NorthwindFactory.OrderDetail(10895,24,4.5m,110,0), + NorthwindFactory.OrderDetail(10895,39,18,45,0), + NorthwindFactory.OrderDetail(10895,40,18.4m,91,0), + NorthwindFactory.OrderDetail(10895,60,34,100,0), + NorthwindFactory.OrderDetail(10896,45,9.5m,15,0), + NorthwindFactory.OrderDetail(10896,56,38,16,0), + NorthwindFactory.OrderDetail(10897,29,123.79m,80,0), + NorthwindFactory.OrderDetail(10897,30,25.89m,36,0), + + NorthwindFactory.OrderDetail(10898,13,6,5,0), + NorthwindFactory.OrderDetail(10899,39,18,8,0.15), + NorthwindFactory.OrderDetail(10900,70,15,3,0.25), + NorthwindFactory.OrderDetail(10901,41,9.65m,30,0), + NorthwindFactory.OrderDetail(10901,71,21.5m,30,0), + NorthwindFactory.OrderDetail(10902,55,24,30,0.15), + NorthwindFactory.OrderDetail(10902,62,49.3m,6,0.15), + NorthwindFactory.OrderDetail(10903,13,6,40,0), + NorthwindFactory.OrderDetail(10903,65,21.05m,21,0), + NorthwindFactory.OrderDetail(10903,68,12.5m,20,0), + + NorthwindFactory.OrderDetail(10904,58,13.25m,15,0), + NorthwindFactory.OrderDetail(10904,62,49.3m,35,0), + NorthwindFactory.OrderDetail(10905,1,18,20,0.05), + NorthwindFactory.OrderDetail(10906,61,28.5m,15,0), + NorthwindFactory.OrderDetail(10907,75,7.75m,14,0), + NorthwindFactory.OrderDetail(10908,7,30,20,0.05), + NorthwindFactory.OrderDetail(10908,52,7,14,0.05), + NorthwindFactory.OrderDetail(10909,7,30,12,0), + NorthwindFactory.OrderDetail(10909,16,17.45m,15,0), + NorthwindFactory.OrderDetail(10909,41,9.65m,5,0), + + NorthwindFactory.OrderDetail(10910,19,9.2m,12,0), + NorthwindFactory.OrderDetail(10910,49,20,10,0), + NorthwindFactory.OrderDetail(10910,61,28.5m,5,0), + NorthwindFactory.OrderDetail(10911,1,18,10,0), + NorthwindFactory.OrderDetail(10911,17,39,12,0), + NorthwindFactory.OrderDetail(10911,67,14,15,0), + NorthwindFactory.OrderDetail(10912,11,21,40,0.25), + NorthwindFactory.OrderDetail(10912,29,123.79m,60,0.25), + NorthwindFactory.OrderDetail(10913,4,22,30,0.25), + NorthwindFactory.OrderDetail(10913,33,2.5m,40,0.25), + + NorthwindFactory.OrderDetail(10913,58,13.25m,15,0), + NorthwindFactory.OrderDetail(10914,71,21.5m,25,0), + NorthwindFactory.OrderDetail(10915,17,39,10,0), + NorthwindFactory.OrderDetail(10915,33,2.5m,30,0), + NorthwindFactory.OrderDetail(10915,54,7.45m,10,0), + NorthwindFactory.OrderDetail(10916,16,17.45m,6,0), + NorthwindFactory.OrderDetail(10916,32,32,6,0), + NorthwindFactory.OrderDetail(10916,57,19.5m,20,0), + NorthwindFactory.OrderDetail(10917,30,25.89m,1,0), + NorthwindFactory.OrderDetail(10917,60,34,10,0), + + NorthwindFactory.OrderDetail(10918,1,18,60,0.25), + NorthwindFactory.OrderDetail(10918,60,34,25,0.25), + NorthwindFactory.OrderDetail(10919,16,17.45m,24,0), + NorthwindFactory.OrderDetail(10919,25,14,24,0), + NorthwindFactory.OrderDetail(10919,40,18.4m,20,0), + NorthwindFactory.OrderDetail(10920,50,16.25m,24,0), + NorthwindFactory.OrderDetail(10921,35,18,10,0), + NorthwindFactory.OrderDetail(10921,63,43.9m,40,0), + NorthwindFactory.OrderDetail(10922,17,39,15,0), + NorthwindFactory.OrderDetail(10922,24,4.5m,35,0), + + NorthwindFactory.OrderDetail(10923,42,14,10,0.2), + NorthwindFactory.OrderDetail(10923,43,46,10,0.2), + NorthwindFactory.OrderDetail(10923,67,14,24,0.2), + NorthwindFactory.OrderDetail(10924,10,31,20,0.1), + NorthwindFactory.OrderDetail(10924,28,45.6m,30,0.1), + NorthwindFactory.OrderDetail(10924,75,7.75m,6,0), + NorthwindFactory.OrderDetail(10925,36,19,25,0.15), + NorthwindFactory.OrderDetail(10925,52,7,12,0.15), + NorthwindFactory.OrderDetail(10926,11,21,2,0), + NorthwindFactory.OrderDetail(10926,13,6,10,0), + + NorthwindFactory.OrderDetail(10926,19,9.2m,7,0), + NorthwindFactory.OrderDetail(10926,72,34.8m,10,0), + NorthwindFactory.OrderDetail(10927,20,81,5,0), + NorthwindFactory.OrderDetail(10927,52,7,5,0), + NorthwindFactory.OrderDetail(10927,76,18,20,0), + NorthwindFactory.OrderDetail(10928,47,9.5m,5,0), + NorthwindFactory.OrderDetail(10928,76,18,5,0), + NorthwindFactory.OrderDetail(10929,21,10,60,0), + NorthwindFactory.OrderDetail(10929,75,7.75m,49,0), + NorthwindFactory.OrderDetail(10929,77,13,15,0), + + NorthwindFactory.OrderDetail(10930,21,10,36,0), + NorthwindFactory.OrderDetail(10930,27,43.9m,25,0), + NorthwindFactory.OrderDetail(10930,55,24,25,0.2), + NorthwindFactory.OrderDetail(10930,58,13.25m,30,0.2), + NorthwindFactory.OrderDetail(10931,13,6,42,0.15), + NorthwindFactory.OrderDetail(10931,57,19.5m,30,0), + NorthwindFactory.OrderDetail(10932,16,17.45m,30,0.1), + NorthwindFactory.OrderDetail(10932,62,49.3m,14,0.1), + NorthwindFactory.OrderDetail(10932,72,34.8m,16,0), + NorthwindFactory.OrderDetail(10932,75,7.75m,20,0.1), + + NorthwindFactory.OrderDetail(10933,53,32.8m,2,0), + NorthwindFactory.OrderDetail(10933,61,28.5m,30,0), + NorthwindFactory.OrderDetail(10934,6,25,20,0), + NorthwindFactory.OrderDetail(10935,1,18,21,0), + NorthwindFactory.OrderDetail(10935,18,62.5m,4,0.25), + NorthwindFactory.OrderDetail(10935,23,9,8,0.25), + NorthwindFactory.OrderDetail(10936,36,19,30,0.2), + NorthwindFactory.OrderDetail(10937,28,45.6m,8,0), + NorthwindFactory.OrderDetail(10937,34,14,20,0), + NorthwindFactory.OrderDetail(10938,13,6,20,0.25), + + NorthwindFactory.OrderDetail(10938,43,46,24,0.25), + NorthwindFactory.OrderDetail(10938,60,34,49,0.25), + NorthwindFactory.OrderDetail(10938,71,21.5m,35,0.25), + NorthwindFactory.OrderDetail(10939,2,19,10,0.15), + NorthwindFactory.OrderDetail(10939,67,14,40,0.15), + NorthwindFactory.OrderDetail(10940,7,30,8,0), + NorthwindFactory.OrderDetail(10940,13,6,20,0), + NorthwindFactory.OrderDetail(10941,31,12.5m,44,0.25), + NorthwindFactory.OrderDetail(10941,62,49.3m,30,0.25), + NorthwindFactory.OrderDetail(10941,68,12.5m,80,0.25), + + NorthwindFactory.OrderDetail(10941,72,34.8m,50,0), + NorthwindFactory.OrderDetail(10942,49,20,28,0), + NorthwindFactory.OrderDetail(10943,13,6,15,0), + NorthwindFactory.OrderDetail(10943,22,21,21,0), + NorthwindFactory.OrderDetail(10943,46,12,15,0), + NorthwindFactory.OrderDetail(10944,11,21,5,0.25), + NorthwindFactory.OrderDetail(10944,44,19.45m,18,0.25), + NorthwindFactory.OrderDetail(10944,56,38,18,0), + NorthwindFactory.OrderDetail(10945,13,6,20,0), + NorthwindFactory.OrderDetail(10945,31,12.5m,10,0), + + NorthwindFactory.OrderDetail(10946,10,31,25,0), + NorthwindFactory.OrderDetail(10946,24,4.5m,25,0), + NorthwindFactory.OrderDetail(10946,77,13,40,0), + NorthwindFactory.OrderDetail(10947,59,55,4,0), + NorthwindFactory.OrderDetail(10948,50,16.25m,9,0), + NorthwindFactory.OrderDetail(10948,51,53,40,0), + NorthwindFactory.OrderDetail(10948,55,24,4,0), + NorthwindFactory.OrderDetail(10949,6,25,12,0), + NorthwindFactory.OrderDetail(10949,10,31,30,0), + NorthwindFactory.OrderDetail(10949,17,39,6,0), + + NorthwindFactory.OrderDetail(10949,62,49.3m,60,0), + NorthwindFactory.OrderDetail(10950,4,22,5,0), + NorthwindFactory.OrderDetail(10951,33,2.5m,15,0.05), + NorthwindFactory.OrderDetail(10951,41,9.65m,6,0.05), + NorthwindFactory.OrderDetail(10951,75,7.75m,50,0.05), + NorthwindFactory.OrderDetail(10952,6,25,16,0.05), + NorthwindFactory.OrderDetail(10952,28,45.6m,2,0), + NorthwindFactory.OrderDetail(10953,20,81,50,0.05), + NorthwindFactory.OrderDetail(10953,31,12.5m,50,0.05), + NorthwindFactory.OrderDetail(10954,16,17.45m,28,0.15), + + NorthwindFactory.OrderDetail(10954,31,12.5m,25,0.15), + NorthwindFactory.OrderDetail(10954,45,9.5m,30,0), + NorthwindFactory.OrderDetail(10954,60,34,24,0.15), + NorthwindFactory.OrderDetail(10955,75,7.75m,12,0.2), + NorthwindFactory.OrderDetail(10956,21,10,12,0), + NorthwindFactory.OrderDetail(10956,47,9.5m,14,0), + NorthwindFactory.OrderDetail(10956,51,53,8,0), + NorthwindFactory.OrderDetail(10957,30,25.89m,30,0), + NorthwindFactory.OrderDetail(10957,35,18,40,0), + NorthwindFactory.OrderDetail(10957,64,33.25m,8,0), + + NorthwindFactory.OrderDetail(10958,5,21.35m,20,0), + NorthwindFactory.OrderDetail(10958,7,30,6,0), + NorthwindFactory.OrderDetail(10958,72,34.8m,5,0), + NorthwindFactory.OrderDetail(10959,75,7.75m,20,0.15), + NorthwindFactory.OrderDetail(10960,24,4.5m,10,0.25), + NorthwindFactory.OrderDetail(10960,41,9.65m,24,0), + NorthwindFactory.OrderDetail(10961,52,7,6,0.05), + NorthwindFactory.OrderDetail(10961,76,18,60,0), + NorthwindFactory.OrderDetail(10962,7,30,45,0), + NorthwindFactory.OrderDetail(10962,13,6,77,0), + + NorthwindFactory.OrderDetail(10962,53,32.8m,20,0), + NorthwindFactory.OrderDetail(10962,69,36,9,0), + NorthwindFactory.OrderDetail(10962,76,18,44,0), + NorthwindFactory.OrderDetail(10963,60,34,2,0.15), + NorthwindFactory.OrderDetail(10964,18,62.5m,6,0), + NorthwindFactory.OrderDetail(10964,38,263.5m,5,0), + NorthwindFactory.OrderDetail(10964,69,36,10,0), + NorthwindFactory.OrderDetail(10965,51,53,16,0), + NorthwindFactory.OrderDetail(10966,37,26,8,0), + NorthwindFactory.OrderDetail(10966,56,38,12,0.15), + + NorthwindFactory.OrderDetail(10966,62,49.3m,12,0.15), + NorthwindFactory.OrderDetail(10967,19,9.2m,12,0), + NorthwindFactory.OrderDetail(10967,49,20,40,0), + NorthwindFactory.OrderDetail(10968,12,38,30,0), + NorthwindFactory.OrderDetail(10968,24,4.5m,30,0), + NorthwindFactory.OrderDetail(10968,64,33.25m,4,0), + NorthwindFactory.OrderDetail(10969,46,12,9,0), + NorthwindFactory.OrderDetail(10970,52,7,40,0.2), + NorthwindFactory.OrderDetail(10971,29,123.79m,14,0), + NorthwindFactory.OrderDetail(10972,17,39,6,0), + + NorthwindFactory.OrderDetail(10972,33,2.5m,7,0), + NorthwindFactory.OrderDetail(10973,26,31.23m,5,0), + NorthwindFactory.OrderDetail(10973,41,9.65m,6,0), + NorthwindFactory.OrderDetail(10973,75,7.75m,10,0), + NorthwindFactory.OrderDetail(10974,63,43.9m,10,0), + NorthwindFactory.OrderDetail(10975,8,40,16,0), + NorthwindFactory.OrderDetail(10975,75,7.75m,10,0), + NorthwindFactory.OrderDetail(10976,28,45.6m,20,0), + NorthwindFactory.OrderDetail(10977,39,18,30,0), + NorthwindFactory.OrderDetail(10977,47,9.5m,30,0), + + NorthwindFactory.OrderDetail(10977,51,53,10,0), + NorthwindFactory.OrderDetail(10977,63,43.9m,20,0), + NorthwindFactory.OrderDetail(10978,8,40,20,0.15), + NorthwindFactory.OrderDetail(10978,21,10,40,0.15), + NorthwindFactory.OrderDetail(10978,40,18.4m,10,0), + NorthwindFactory.OrderDetail(10978,44,19.45m,6,0.15), + NorthwindFactory.OrderDetail(10979,7,30,18,0), + NorthwindFactory.OrderDetail(10979,12,38,20,0), + NorthwindFactory.OrderDetail(10979,24,4.5m,80,0), + NorthwindFactory.OrderDetail(10979,27,43.9m,30,0), + + NorthwindFactory.OrderDetail(10979,31,12.5m,24,0), + NorthwindFactory.OrderDetail(10979,63,43.9m,35,0), + NorthwindFactory.OrderDetail(10980,75,7.75m,40,0.2), + NorthwindFactory.OrderDetail(10981,38,263.5m,60,0), + NorthwindFactory.OrderDetail(10982,7,30,20,0), + NorthwindFactory.OrderDetail(10982,43,46,9,0), + NorthwindFactory.OrderDetail(10983,13,6,84,0.15), + NorthwindFactory.OrderDetail(10983,57,19.5m,15,0), + NorthwindFactory.OrderDetail(10984,16,17.45m,55,0), + NorthwindFactory.OrderDetail(10984,24,4.5m,20,0), + + NorthwindFactory.OrderDetail(10984,36,19,40,0), + NorthwindFactory.OrderDetail(10985,16,17.45m,36,0.1), + NorthwindFactory.OrderDetail(10985,18,62.5m,8,0.1), + NorthwindFactory.OrderDetail(10985,32,32,35,0.1), + NorthwindFactory.OrderDetail(10986,11,21,30,0), + NorthwindFactory.OrderDetail(10986,20,81,15,0), + NorthwindFactory.OrderDetail(10986,76,18,10,0), + NorthwindFactory.OrderDetail(10986,77,13,15,0), + NorthwindFactory.OrderDetail(10987,7,30,60,0), + NorthwindFactory.OrderDetail(10987,43,46,6,0), + + NorthwindFactory.OrderDetail(10987,72,34.8m,20,0), + NorthwindFactory.OrderDetail(10988,7,30,60,0), + NorthwindFactory.OrderDetail(10988,62,49.3m,40,0.1), + NorthwindFactory.OrderDetail(10989,6,25,40,0), + NorthwindFactory.OrderDetail(10989,11,21,15,0), + NorthwindFactory.OrderDetail(10989,41,9.65m,4,0), + NorthwindFactory.OrderDetail(10990,21,10,65,0), + NorthwindFactory.OrderDetail(10990,34,14,60,0.15), + NorthwindFactory.OrderDetail(10990,55,24,65,0.15), + NorthwindFactory.OrderDetail(10990,61,28.5m,66,0.15), + + NorthwindFactory.OrderDetail(10991,2,19,50,0.2), + NorthwindFactory.OrderDetail(10991,70,15,20,0.2), + NorthwindFactory.OrderDetail(10991,76,18,90,0.2), + NorthwindFactory.OrderDetail(10992,72,34.8m,2,0), + NorthwindFactory.OrderDetail(10993,29,123.79m,50,0.25), + NorthwindFactory.OrderDetail(10993,41,9.65m,35,0.25), + NorthwindFactory.OrderDetail(10994,59,55,18,0.05), + NorthwindFactory.OrderDetail(10995,51,53,20,0), + NorthwindFactory.OrderDetail(10995,60,34,4,0), + NorthwindFactory.OrderDetail(10996,42,14,40,0), + + NorthwindFactory.OrderDetail(10997,32,32,50,0), + NorthwindFactory.OrderDetail(10997,46,12,20,0.25), + NorthwindFactory.OrderDetail(10997,52,7,20,0.25), + NorthwindFactory.OrderDetail(10998,24,4.5m,12,0), + NorthwindFactory.OrderDetail(10998,61,28.5m,7,0), + NorthwindFactory.OrderDetail(10998,74,10,20,0), + NorthwindFactory.OrderDetail(10998,75,7.75m,30,0), + NorthwindFactory.OrderDetail(10999,41,9.65m,20,0.05), + NorthwindFactory.OrderDetail(10999,51,53,15,0.05), + NorthwindFactory.OrderDetail(10999,77,13,21,0.05), + + NorthwindFactory.OrderDetail(11000,4,22,25,0.25), + NorthwindFactory.OrderDetail(11000,24,4.5m,30,0.25), + NorthwindFactory.OrderDetail(11000,77,13,30,0), + NorthwindFactory.OrderDetail(11001,7,30,60,0), + NorthwindFactory.OrderDetail(11001,22,21,25,0), + NorthwindFactory.OrderDetail(11001,46,12,25,0), + NorthwindFactory.OrderDetail(11001,55,24,6,0), + NorthwindFactory.OrderDetail(11002,13,6,56,0), + NorthwindFactory.OrderDetail(11002,35,18,15,0.15), + NorthwindFactory.OrderDetail(11002,42,14,24,0.15), + + NorthwindFactory.OrderDetail(11002,55,24,40,0), + NorthwindFactory.OrderDetail(11003,1,18,4,0), + NorthwindFactory.OrderDetail(11003,40,18.4m,10,0), + NorthwindFactory.OrderDetail(11003,52,7,10,0), + NorthwindFactory.OrderDetail(11004,26,31.23m,6,0), + NorthwindFactory.OrderDetail(11004,76,18,6,0), + NorthwindFactory.OrderDetail(11005,1,18,2,0), + NorthwindFactory.OrderDetail(11005,59,55,10,0), + NorthwindFactory.OrderDetail(11006,1,18,8,0), + NorthwindFactory.OrderDetail(11006,29,123.79m,2,0.25), + + NorthwindFactory.OrderDetail(11007,8,40,30,0), + NorthwindFactory.OrderDetail(11007,29,123.79m,10,0), + NorthwindFactory.OrderDetail(11007,42,14,14,0), + NorthwindFactory.OrderDetail(11008,28,45.6m,70,0.05), + NorthwindFactory.OrderDetail(11008,34,14,90,0.05), + NorthwindFactory.OrderDetail(11008,71,21.5m,21,0), + NorthwindFactory.OrderDetail(11009,24,4.5m,12,0), + NorthwindFactory.OrderDetail(11009,36,19,18,0.25), + NorthwindFactory.OrderDetail(11009,60,34,9,0), + NorthwindFactory.OrderDetail(11010,7,30,20,0), + + NorthwindFactory.OrderDetail(11010,24,4.5m,10,0), + NorthwindFactory.OrderDetail(11011,58,13.25m,40,0.05), + NorthwindFactory.OrderDetail(11011,71,21.5m,20,0), + NorthwindFactory.OrderDetail(11012,19,9.2m,50,0.05), + NorthwindFactory.OrderDetail(11012,60,34,36,0.05), + NorthwindFactory.OrderDetail(11012,71,21.5m,60,0.05), + NorthwindFactory.OrderDetail(11013,23,9,10,0), + NorthwindFactory.OrderDetail(11013,42,14,4,0), + NorthwindFactory.OrderDetail(11013,45,9.5m,20,0), + NorthwindFactory.OrderDetail(11013,68,12.5m,2,0), + + NorthwindFactory.OrderDetail(11014,41,9.65m,28,0.1), + NorthwindFactory.OrderDetail(11015,30,25.89m,15,0), + NorthwindFactory.OrderDetail(11015,77,13,18,0), + NorthwindFactory.OrderDetail(11016,31,12.5m,15,0), + NorthwindFactory.OrderDetail(11016,36,19,16,0), + NorthwindFactory.OrderDetail(11017,3,10,25,0), + NorthwindFactory.OrderDetail(11017,59,55,110,0), + NorthwindFactory.OrderDetail(11017,70,15,30,0), + NorthwindFactory.OrderDetail(11018,12,38,20,0), + NorthwindFactory.OrderDetail(11018,18,62.5m,10,0), + + NorthwindFactory.OrderDetail(11018,56,38,5,0), + NorthwindFactory.OrderDetail(11019,46,12,3,0), + NorthwindFactory.OrderDetail(11019,49,20,2,0), + NorthwindFactory.OrderDetail(11020,10,31,24,0.15), + NorthwindFactory.OrderDetail(11021,2,19,11,0.25), + NorthwindFactory.OrderDetail(11021,20,81,15,0), + NorthwindFactory.OrderDetail(11021,26,31.23m,63,0), + NorthwindFactory.OrderDetail(11021,51,53,44,0.25), + NorthwindFactory.OrderDetail(11021,72,34.8m,35,0), + NorthwindFactory.OrderDetail(11022,19,9.2m,35,0), + + NorthwindFactory.OrderDetail(11022,69,36,30,0), + NorthwindFactory.OrderDetail(11023,7,30,4,0), + NorthwindFactory.OrderDetail(11023,43,46,30,0), + NorthwindFactory.OrderDetail(11024,26,31.23m,12,0), + NorthwindFactory.OrderDetail(11024,33,2.5m,30,0), + NorthwindFactory.OrderDetail(11024,65,21.05m,21,0), + NorthwindFactory.OrderDetail(11024,71,21.5m,50,0), + NorthwindFactory.OrderDetail(11025,1,18,10,0.1), + NorthwindFactory.OrderDetail(11025,13,6,20,0.1), + NorthwindFactory.OrderDetail(11026,18,62.5m,8,0), + + NorthwindFactory.OrderDetail(11026,51,53,10,0), + NorthwindFactory.OrderDetail(11027,24,4.5m,30,0.25), + NorthwindFactory.OrderDetail(11027,62,49.3m,21,0.25), + NorthwindFactory.OrderDetail(11028,55,24,35,0), + NorthwindFactory.OrderDetail(11028,59,55,24,0), + NorthwindFactory.OrderDetail(11029,56,38,20,0), + NorthwindFactory.OrderDetail(11029,63,43.9m,12,0), + NorthwindFactory.OrderDetail(11030,2,19,100,0.25), + NorthwindFactory.OrderDetail(11030,5,21.35m,70,0), + NorthwindFactory.OrderDetail(11030,29,123.79m,60,0.25), + + NorthwindFactory.OrderDetail(11030,59,55,100,0.25), + NorthwindFactory.OrderDetail(11031,1,18,45,0), + NorthwindFactory.OrderDetail(11031,13,6,80,0), + NorthwindFactory.OrderDetail(11031,24,4.5m,21,0), + NorthwindFactory.OrderDetail(11031,64,33.25m,20,0), + NorthwindFactory.OrderDetail(11031,71,21.5m,16,0), + NorthwindFactory.OrderDetail(11032,36,19,35,0), + NorthwindFactory.OrderDetail(11032,38,263.5m,25,0), + NorthwindFactory.OrderDetail(11032,59,55,30,0), + NorthwindFactory.OrderDetail(11033,53,32.8m,70,0.1), + + NorthwindFactory.OrderDetail(11033,69,36,36,0.1), + NorthwindFactory.OrderDetail(11034,21,10,15,0.1), + NorthwindFactory.OrderDetail(11034,44,19.45m,12,0), + NorthwindFactory.OrderDetail(11034,61,28.5m,6,0), + NorthwindFactory.OrderDetail(11035,1,18,10,0), + NorthwindFactory.OrderDetail(11035,35,18,60,0), + NorthwindFactory.OrderDetail(11035,42,14,30,0), + NorthwindFactory.OrderDetail(11035,54,7.45m,10,0), + NorthwindFactory.OrderDetail(11036,13,6,7,0), + NorthwindFactory.OrderDetail(11036,59,55,30,0), + + NorthwindFactory.OrderDetail(11037,70,15,4,0), + NorthwindFactory.OrderDetail(11038,40,18.4m,5,0.2), + NorthwindFactory.OrderDetail(11038,52,7,2,0), + NorthwindFactory.OrderDetail(11038,71,21.5m,30,0), + NorthwindFactory.OrderDetail(11039,28,45.6m,20,0), + NorthwindFactory.OrderDetail(11039,35,18,24,0), + NorthwindFactory.OrderDetail(11039,49,20,60,0), + NorthwindFactory.OrderDetail(11039,57,19.5m,28,0), + NorthwindFactory.OrderDetail(11040,21,10,20,0), + NorthwindFactory.OrderDetail(11041,2,19,30,0.2), + + NorthwindFactory.OrderDetail(11041,63,43.9m,30,0), + NorthwindFactory.OrderDetail(11042,44,19.45m,15,0), + NorthwindFactory.OrderDetail(11042,61,28.5m,4,0), + NorthwindFactory.OrderDetail(11043,11,21,10,0), + NorthwindFactory.OrderDetail(11044,62,49.3m,12,0), + NorthwindFactory.OrderDetail(11045,33,2.5m,15,0), + NorthwindFactory.OrderDetail(11045,51,53,24,0), + NorthwindFactory.OrderDetail(11046,12,38,20,0.05), + NorthwindFactory.OrderDetail(11046,32,32,15,0.05), + NorthwindFactory.OrderDetail(11046,35,18,18,0.05), + + NorthwindFactory.OrderDetail(11047,1,18,25,0.25), + NorthwindFactory.OrderDetail(11047,5,21.35m,30,0.25), + NorthwindFactory.OrderDetail(11048,68,12.5m,42,0), + NorthwindFactory.OrderDetail(11049,2,19,10,0.2), + NorthwindFactory.OrderDetail(11049,12,38,4,0.2), + NorthwindFactory.OrderDetail(11050,76,18,50,0.1), + NorthwindFactory.OrderDetail(11051,24,4.5m,10,0.2), + NorthwindFactory.OrderDetail(11052,43,46,30,0.2), + NorthwindFactory.OrderDetail(11052,61,28.5m,10,0.2), + NorthwindFactory.OrderDetail(11053,18,62.5m,35,0.2), + + NorthwindFactory.OrderDetail(11053,32,32,20,0), + NorthwindFactory.OrderDetail(11053,64,33.25m,25,0.2), + NorthwindFactory.OrderDetail(11054,33,2.5m,10,0), + NorthwindFactory.OrderDetail(11054,67,14,20,0), + NorthwindFactory.OrderDetail(11055,24,4.5m,15,0), + NorthwindFactory.OrderDetail(11055,25,14,15,0), + NorthwindFactory.OrderDetail(11055,51,53,20,0), + NorthwindFactory.OrderDetail(11055,57,19.5m,20,0), + NorthwindFactory.OrderDetail(11056,7,30,40,0), + NorthwindFactory.OrderDetail(11056,55,24,35,0), + + NorthwindFactory.OrderDetail(11056,60,34,50,0), + NorthwindFactory.OrderDetail(11057,70,15,3,0), + NorthwindFactory.OrderDetail(11058,21,10,3,0), + NorthwindFactory.OrderDetail(11058,60,34,21,0), + NorthwindFactory.OrderDetail(11058,61,28.5m,4,0), + NorthwindFactory.OrderDetail(11059,13,6,30,0), + NorthwindFactory.OrderDetail(11059,17,39,12,0), + NorthwindFactory.OrderDetail(11059,60,34,35,0), + NorthwindFactory.OrderDetail(11060,60,34,4,0), + NorthwindFactory.OrderDetail(11060,77,13,10,0), + + NorthwindFactory.OrderDetail(11061,60,34,15,0), + NorthwindFactory.OrderDetail(11062,53,32.8m,10,0.2), + NorthwindFactory.OrderDetail(11062,70,15,12,0.2), + NorthwindFactory.OrderDetail(11063,34,14,30,0), + NorthwindFactory.OrderDetail(11063,40,18.4m,40,0.1), + NorthwindFactory.OrderDetail(11063,41,9.65m,30,0.1), + NorthwindFactory.OrderDetail(11064,17,39,77,0.1), + NorthwindFactory.OrderDetail(11064,41,9.65m,12,0), + NorthwindFactory.OrderDetail(11064,53,32.8m,25,0.1), + NorthwindFactory.OrderDetail(11064,55,24,4,0.1), + + NorthwindFactory.OrderDetail(11064,68,12.5m,55,0), + NorthwindFactory.OrderDetail(11065,30,25.89m,4,0.25), + NorthwindFactory.OrderDetail(11065,54,7.45m,20,0.25), + NorthwindFactory.OrderDetail(11066,16,17.45m,3,0), + NorthwindFactory.OrderDetail(11066,19,9.2m,42,0), + NorthwindFactory.OrderDetail(11066,34,14,35,0), + NorthwindFactory.OrderDetail(11067,41,9.65m,9,0), + NorthwindFactory.OrderDetail(11068,28,45.6m,8,0.15), + NorthwindFactory.OrderDetail(11068,43,46,36,0.15), + NorthwindFactory.OrderDetail(11068,77,13,28,0.15), + + NorthwindFactory.OrderDetail(11069,39,18,20,0), + NorthwindFactory.OrderDetail(11070,1,18,40,0.15), + NorthwindFactory.OrderDetail(11070,2,19,20,0.15), + NorthwindFactory.OrderDetail(11070,16,17.45m,30,0.15), + NorthwindFactory.OrderDetail(11070,31,12.5m,20,0), + NorthwindFactory.OrderDetail(11071,7,30,15,0.05), + NorthwindFactory.OrderDetail(11071,13,6,10,0.05), + NorthwindFactory.OrderDetail(11072,2,19,8,0), + NorthwindFactory.OrderDetail(11072,41,9.65m,40,0), + NorthwindFactory.OrderDetail(11072,50,16.25m,22,0), + + NorthwindFactory.OrderDetail(11072,64,33.25m,130,0), + NorthwindFactory.OrderDetail(11073,11,21,10,0), + NorthwindFactory.OrderDetail(11073,24,4.5m,20,0), + NorthwindFactory.OrderDetail(11074,16,17.45m,14,0.05), + NorthwindFactory.OrderDetail(11075,2,19,10,0.15), + NorthwindFactory.OrderDetail(11075,46,12,30,0.15), + NorthwindFactory.OrderDetail(11075,76,18,2,0.15), + NorthwindFactory.OrderDetail(11076,6,25,20,0.25), + NorthwindFactory.OrderDetail(11076,14,23.25m,20,0.25), + NorthwindFactory.OrderDetail(11076,19,9.2m,10,0.25), + + NorthwindFactory.OrderDetail(11077,2,19,24,0.2), + NorthwindFactory.OrderDetail(11077,3,10,4,0), + NorthwindFactory.OrderDetail(11077,4,22,1,0), + NorthwindFactory.OrderDetail(11077,6,25,1,0.02), + NorthwindFactory.OrderDetail(11077,7,30,1,0.05), + NorthwindFactory.OrderDetail(11077,8,40,2,0.1), + NorthwindFactory.OrderDetail(11077,10,31,1,0), + NorthwindFactory.OrderDetail(11077,12,38,2,0.05), + NorthwindFactory.OrderDetail(11077,13,6,4,0), + NorthwindFactory.OrderDetail(11077,14,23.25m,1,0.03), + + NorthwindFactory.OrderDetail(11077,16,17.45m,2,0.03), + NorthwindFactory.OrderDetail(11077,20,81,1,0.04), + NorthwindFactory.OrderDetail(11077,23,9,2,0), + NorthwindFactory.OrderDetail(11077,32,32,1,0), + NorthwindFactory.OrderDetail(11077,39,18,2,0.05), + NorthwindFactory.OrderDetail(11077,41,9.65m,3,0), + NorthwindFactory.OrderDetail(11077,46,12,3,0.02), + NorthwindFactory.OrderDetail(11077,52,7,2,0), + NorthwindFactory.OrderDetail(11077,55,24,2,0), + NorthwindFactory.OrderDetail(11077,60,34,2,0.06), + + NorthwindFactory.OrderDetail(11077,64,33.25m,2,0.03), + NorthwindFactory.OrderDetail(11077,66,17,1,0), + NorthwindFactory.OrderDetail(11077,73,15,2,0.01), + NorthwindFactory.OrderDetail(11077,75,7.75m,4,0), + NorthwindFactory.OrderDetail(11077,77,13,2,0), + }; + + Orders = new List { + NorthwindFactory.Order(10248,"VINET",5,ToDateTime("7/4/1996"),ToDateTime("8/1/1996"),ToDateTime("7/16/1996"),3,32.38m, "Vins et alcools Chevalier","59 rue de l'Abbaye","Reims", null,"51100","France"), + NorthwindFactory.Order(10249,"TOMSP",6,ToDateTime("7/5/1996"),ToDateTime("8/16/1996"),ToDateTime("7/10/1996"),1,11.61m, "Toms Spezialitten","Luisenstr. 48","Mnster", null,"44087","Germany"), + NorthwindFactory.Order(10250,"HANAR",4,ToDateTime("7/8/1996"),ToDateTime("8/5/1996"),ToDateTime("7/12/1996"),2,65.83m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10251,"VICTE",3,ToDateTime("7/8/1996"),ToDateTime("8/5/1996"),ToDateTime("7/15/1996"),1,41.34m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10252,"SUPRD",4,ToDateTime("7/9/1996"),ToDateTime("8/6/1996"),ToDateTime("7/11/1996"),2,51.30m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10253,"HANAR",3,ToDateTime("7/10/1996"),ToDateTime("7/24/1996"),ToDateTime("7/16/1996"),2,58.17m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10254,"CHOPS",5,ToDateTime("7/11/1996"),ToDateTime("8/8/1996"),ToDateTime("7/23/1996"),2,22.98m, "Chop-suey Chinese","Hauptstr. 31","Bern", null,"3012","Switzerland"), + NorthwindFactory.Order(10255,"RICSU",9,ToDateTime("7/12/1996"),ToDateTime("8/9/1996"),ToDateTime("7/15/1996"),3,148.33m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(10256,"WELLI",3,ToDateTime("7/15/1996"),ToDateTime("8/12/1996"),ToDateTime("7/17/1996"),2,13.97m, "Wellington Importadora","Rua do Mercado, 12","Resende", "SP","08737-363","Brazil"), + NorthwindFactory.Order(10257,"HILAA",4,ToDateTime("7/16/1996"),ToDateTime("8/13/1996"),ToDateTime("7/22/1996"),3,81.91m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10258,"ERNSH",1,ToDateTime("7/17/1996"),ToDateTime("8/14/1996"),ToDateTime("7/23/1996"),1,140.51m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10259,"CENTC",4,ToDateTime("7/18/1996"),ToDateTime("8/15/1996"),ToDateTime("7/25/1996"),3,3.25m, "Centro comercial Moctezuma","Sierras de Granada 9993","Mxico D.F.", null,"05022","Mexico"), + NorthwindFactory.Order(10260,"OTTIK",4,ToDateTime("7/19/1996"),ToDateTime("8/16/1996"),ToDateTime("7/29/1996"),1,55.09m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(10261,"QUEDE",4,ToDateTime("7/19/1996"),ToDateTime("8/16/1996"),ToDateTime("7/30/1996"),2,3.05m, "Que Delcia","Rua da Panificadora, 12","Rio de Janeiro", "RJ","02389-673","Brazil"), + NorthwindFactory.Order(10262,"RATTC",8,ToDateTime("7/22/1996"),ToDateTime("8/19/1996"),ToDateTime("7/25/1996"),3,48.29m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10263,"ERNSH",9,ToDateTime("7/23/1996"),ToDateTime("8/20/1996"),ToDateTime("7/31/1996"),3,146.06m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10264,"FOLKO",6,ToDateTime("7/24/1996"),ToDateTime("8/21/1996"),ToDateTime("8/23/1996"),3,3.67m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10265,"BLONP",2,ToDateTime("7/25/1996"),ToDateTime("8/22/1996"),ToDateTime("8/12/1996"),1,55.28m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10266,"WARTH",3,ToDateTime("7/26/1996"),ToDateTime("9/6/1996"),ToDateTime("7/31/1996"),3,25.73m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10267,"FRANK",4,ToDateTime("7/29/1996"),ToDateTime("8/26/1996"),ToDateTime("8/6/1996"),1,208.58m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10268,"GROSR",8,ToDateTime("7/30/1996"),ToDateTime("8/27/1996"),ToDateTime("8/2/1996"),3,66.29m, "GROSELLA-Restaurante","5 Ave. Los Palos Grandes","Caracas", "DF","1081","Venezuela"), + NorthwindFactory.Order(10269,"WHITC",5,ToDateTime("7/31/1996"),ToDateTime("8/14/1996"),ToDateTime("8/9/1996"),1,4.56m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10270,"WARTH",1,ToDateTime("8/1/1996"),ToDateTime("8/29/1996"),ToDateTime("8/2/1996"),1,136.54m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10271,"SPLIR",6,ToDateTime("8/1/1996"),ToDateTime("8/29/1996"),ToDateTime("8/30/1996"),2,4.54m, "Split Rail Beer & Ale","P.O. Box 555","Lander", "WY","82520","USA"), + NorthwindFactory.Order(10272,"RATTC",6,ToDateTime("8/2/1996"),ToDateTime("8/30/1996"),ToDateTime("8/6/1996"),2,98.03m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10273,"QUICK",3,ToDateTime("8/5/1996"),ToDateTime("9/2/1996"),ToDateTime("8/12/1996"),3,76.07m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10274,"VINET",6,ToDateTime("8/6/1996"),ToDateTime("9/3/1996"),ToDateTime("8/16/1996"),1,6.01m, "Vins et alcools Chevalier","59 rue de l'Abbaye","Reims", null,"51100","France"), + NorthwindFactory.Order(10275,"MAGAA",1,ToDateTime("8/7/1996"),ToDateTime("9/4/1996"),ToDateTime("8/9/1996"),1,26.93m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10276,"TORTU",8,ToDateTime("8/8/1996"),ToDateTime("8/22/1996"),ToDateTime("8/14/1996"),3,13.84m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10277,"MORGK",2,ToDateTime("8/9/1996"),ToDateTime("9/6/1996"),ToDateTime("8/13/1996"),3,125.77m, "Morgenstern Gesundkost","Heerstr. 22","Leipzig", null,"04179","Germany"), + NorthwindFactory.Order(10278,"BERGS",8,ToDateTime("8/12/1996"),ToDateTime("9/9/1996"),ToDateTime("8/16/1996"),2,92.69m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10279,"LEHMS",8,ToDateTime("8/13/1996"),ToDateTime("9/10/1996"),ToDateTime("8/16/1996"),2,25.83m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10280,"BERGS",2,ToDateTime("8/14/1996"),ToDateTime("9/11/1996"),ToDateTime("9/12/1996"),1,8.98m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10281,"ROMEY",4,ToDateTime("8/14/1996"),ToDateTime("8/28/1996"),ToDateTime("8/21/1996"),1,2.94m, "Romero y tomillo","Gran Va, 1","Madrid", null,"28001","Spain"), + NorthwindFactory.Order(10282,"ROMEY",4,ToDateTime("8/15/1996"),ToDateTime("9/12/1996"),ToDateTime("8/21/1996"),1,12.69m, "Romero y tomillo","Gran Va, 1","Madrid", null,"28001","Spain"), + NorthwindFactory.Order(10283,"LILAS",3,ToDateTime("8/16/1996"),ToDateTime("9/13/1996"),ToDateTime("8/23/1996"),3,84.81m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10284,"LEHMS",4,ToDateTime("8/19/1996"),ToDateTime("9/16/1996"),ToDateTime("8/27/1996"),1,76.56m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10285,"QUICK",1,ToDateTime("8/20/1996"),ToDateTime("9/17/1996"),ToDateTime("8/26/1996"),2,76.83m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10286,"QUICK",8,ToDateTime("8/21/1996"),ToDateTime("9/18/1996"),ToDateTime("8/30/1996"),3,229.24m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10287,"RICAR",8,ToDateTime("8/22/1996"),ToDateTime("9/19/1996"),ToDateTime("8/28/1996"),3,12.76m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10288,"REGGC",4,ToDateTime("8/23/1996"),ToDateTime("9/20/1996"),ToDateTime("9/3/1996"),1,7.45m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10289,"BSBEV",7,ToDateTime("8/26/1996"),ToDateTime("9/23/1996"),ToDateTime("8/28/1996"),3,22.77m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(10290,"COMMI",8,ToDateTime("8/27/1996"),ToDateTime("9/24/1996"),ToDateTime("9/3/1996"),1,79.70m, "Comrcio Mineiro","Av. dos Lusadas, 23","Sao Paulo", "SP","05432-043","Brazil"), + NorthwindFactory.Order(10291,"QUEDE",6,ToDateTime("8/27/1996"),ToDateTime("9/24/1996"),ToDateTime("9/4/1996"),2,6.40m, "Que Delcia","Rua da Panificadora, 12","Rio de Janeiro", "RJ","02389-673","Brazil"), + NorthwindFactory.Order(10292,"TRADH",1,ToDateTime("8/28/1996"),ToDateTime("9/25/1996"),ToDateTime("9/2/1996"),2,1.35m, "Tradiao Hipermercados","Av. Ins de Castro, 414","Sao Paulo", "SP","05634-030","Brazil"), + NorthwindFactory.Order(10293,"TORTU",1,ToDateTime("8/29/1996"),ToDateTime("9/26/1996"),ToDateTime("9/11/1996"),3,21.18m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10294,"RATTC",4,ToDateTime("8/30/1996"),ToDateTime("9/27/1996"),ToDateTime("9/5/1996"),2,147.26m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10295,"VINET",2,ToDateTime("9/2/1996"),ToDateTime("9/30/1996"),ToDateTime("9/10/1996"),2,1.15m, "Vins et alcools Chevalier","59 rue de l'Abbaye","Reims", null,"51100","France"), + NorthwindFactory.Order(10296,"LILAS",6,ToDateTime("9/3/1996"),ToDateTime("10/1/1996"),ToDateTime("9/11/1996"),1,0.12m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10297,"BLONP",5,ToDateTime("9/4/1996"),ToDateTime("10/16/1996"),ToDateTime("9/10/1996"),2,5.74m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10298,"HUNGO",6,ToDateTime("9/5/1996"),ToDateTime("10/3/1996"),ToDateTime("9/11/1996"),2,168.22m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10299,"RICAR",4,ToDateTime("9/6/1996"),ToDateTime("10/4/1996"),ToDateTime("9/13/1996"),2,29.76m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10300,"MAGAA",2,ToDateTime("9/9/1996"),ToDateTime("10/7/1996"),ToDateTime("9/18/1996"),2,17.68m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10301,"WANDK",8,ToDateTime("9/9/1996"),ToDateTime("10/7/1996"),ToDateTime("9/17/1996"),2,45.08m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(10302,"SUPRD",4,ToDateTime("9/10/1996"),ToDateTime("10/8/1996"),ToDateTime("10/9/1996"),2,6.27m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10303,"GODOS",7,ToDateTime("9/11/1996"),ToDateTime("10/9/1996"),ToDateTime("9/18/1996"),2,107.83m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(10304,"TORTU",1,ToDateTime("9/12/1996"),ToDateTime("10/10/1996"),ToDateTime("9/17/1996"),2,63.79m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10305,"OLDWO",8,ToDateTime("9/13/1996"),ToDateTime("10/11/1996"),ToDateTime("10/9/1996"),3,257.62m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(10306,"ROMEY",1,ToDateTime("9/16/1996"),ToDateTime("10/14/1996"),ToDateTime("9/23/1996"),3,7.56m, "Romero y tomillo","Gran Va, 1","Madrid", null,"28001","Spain"), + NorthwindFactory.Order(10307,"LONEP",2,ToDateTime("9/17/1996"),ToDateTime("10/15/1996"),ToDateTime("9/25/1996"),2,0.56m, "Lonesome Pine Restaurant","89 Chiaroscuro Rd.","Portland", "OR","97219","USA"), + NorthwindFactory.Order(10308,"ANATR",7,ToDateTime("9/18/1996"),ToDateTime("10/16/1996"),ToDateTime("9/24/1996"),3,1.61m, "Ana Trujillo Emparedados y helados","Avda. de la Constitucin 2222","Mxico D.F.", null,"05021","Mexico"), + NorthwindFactory.Order(10309,"HUNGO",3,ToDateTime("9/19/1996"),ToDateTime("10/17/1996"),ToDateTime("10/23/1996"),1,47.30m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10310,"THEBI",8,ToDateTime("9/20/1996"),ToDateTime("10/18/1996"),ToDateTime("9/27/1996"),2,17.52m, "The Big Cheese","89 Jefferson Way Suite 2","Portland", "OR","97201","USA"), + NorthwindFactory.Order(10311,"DUMON",1,ToDateTime("9/20/1996"),ToDateTime("10/4/1996"),ToDateTime("9/26/1996"),3,24.69m, "Du monde entier","67, rue des Cinquante Otages","Nantes", null,"44000","France"), + NorthwindFactory.Order(10312,"WANDK",2,ToDateTime("9/23/1996"),ToDateTime("10/21/1996"),ToDateTime("10/3/1996"),2,40.26m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(10313,"QUICK",2,ToDateTime("9/24/1996"),ToDateTime("10/22/1996"),ToDateTime("10/4/1996"),2,1.96m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10314,"RATTC",1,ToDateTime("9/25/1996"),ToDateTime("10/23/1996"),ToDateTime("10/4/1996"),2,74.16m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10315,"ISLAT",4,ToDateTime("9/26/1996"),ToDateTime("10/24/1996"),ToDateTime("10/3/1996"),2,41.76m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10316,"RATTC",1,ToDateTime("9/27/1996"),ToDateTime("10/25/1996"),ToDateTime("10/8/1996"),3,150.15m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10317,"LONEP",6,ToDateTime("9/30/1996"),ToDateTime("10/28/1996"),ToDateTime("10/10/1996"),1,12.69m, "Lonesome Pine Restaurant","89 Chiaroscuro Rd.","Portland", "OR","97219","USA"), + NorthwindFactory.Order(10318,"ISLAT",8,ToDateTime("10/1/1996"),ToDateTime("10/29/1996"),ToDateTime("10/4/1996"),2,4.73m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10319,"TORTU",7,ToDateTime("10/2/1996"),ToDateTime("10/30/1996"),ToDateTime("10/11/1996"),3,64.50m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10320,"WARTH",5,ToDateTime("10/3/1996"),ToDateTime("10/17/1996"),ToDateTime("10/18/1996"),3,34.57m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10321,"ISLAT",3,ToDateTime("10/3/1996"),ToDateTime("10/31/1996"),ToDateTime("10/11/1996"),2,3.43m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10322,"PERIC",7,ToDateTime("10/4/1996"),ToDateTime("11/1/1996"),ToDateTime("10/23/1996"),3,0.40m, "Pericles Comidas clsicas","Calle Dr. Jorge Cash 321","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10323,"KOENE",4,ToDateTime("10/7/1996"),ToDateTime("11/4/1996"),ToDateTime("10/14/1996"),1,4.88m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10324,"SAVEA",9,ToDateTime("10/8/1996"),ToDateTime("11/5/1996"),ToDateTime("10/10/1996"),1,214.27m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10325,"KOENE",1,ToDateTime("10/9/1996"),ToDateTime("10/23/1996"),ToDateTime("10/14/1996"),3,64.86m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10326,"BOLID",4,ToDateTime("10/10/1996"),ToDateTime("11/7/1996"),ToDateTime("10/14/1996"),2,77.92m, "Blido Comidas preparadas","C/ Araquil, 67","Madrid", null,"28023","Spain"), + NorthwindFactory.Order(10327,"FOLKO",2,ToDateTime("10/11/1996"),ToDateTime("11/8/1996"),ToDateTime("10/14/1996"),1,63.36m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10328,"FURIB",4,ToDateTime("10/14/1996"),ToDateTime("11/11/1996"),ToDateTime("10/17/1996"),3,87.03m, "Furia Bacalhau e Frutos do Mar","Jardim das rosas n. 32","Lisboa", null,"1675","Portugal"), + NorthwindFactory.Order(10329,"SPLIR",4,ToDateTime("10/15/1996"),ToDateTime("11/26/1996"),ToDateTime("10/23/1996"),2,191.67m, "Split Rail Beer & Ale","P.O. Box 555","Lander", "WY","82520","USA"), + NorthwindFactory.Order(10330,"LILAS",3,ToDateTime("10/16/1996"),ToDateTime("11/13/1996"),ToDateTime("10/28/1996"),1,12.75m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10331,"BONAP",9,ToDateTime("10/16/1996"),ToDateTime("11/27/1996"),ToDateTime("10/21/1996"),1,10.19m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10332,"MEREP",3,ToDateTime("10/17/1996"),ToDateTime("11/28/1996"),ToDateTime("10/21/1996"),2,52.84m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10333,"WARTH",5,ToDateTime("10/18/1996"),ToDateTime("11/15/1996"),ToDateTime("10/25/1996"),3,0.59m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10334,"VICTE",8,ToDateTime("10/21/1996"),ToDateTime("11/18/1996"),ToDateTime("10/28/1996"),2,8.56m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10335,"HUNGO",7,ToDateTime("10/22/1996"),ToDateTime("11/19/1996"),ToDateTime("10/24/1996"),2,42.11m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10336,"PRINI",7,ToDateTime("10/23/1996"),ToDateTime("11/20/1996"),ToDateTime("10/25/1996"),2,15.51m, "Princesa Isabel Vinhos","Estrada da sade n. 58","Lisboa", null,"1756","Portugal"), + NorthwindFactory.Order(10337,"FRANK",4,ToDateTime("10/24/1996"),ToDateTime("11/21/1996"),ToDateTime("10/29/1996"),3,108.26m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10338,"OLDWO",4,ToDateTime("10/25/1996"),ToDateTime("11/22/1996"),ToDateTime("10/29/1996"),3,84.21m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(10339,"MEREP",2,ToDateTime("10/28/1996"),ToDateTime("11/25/1996"),ToDateTime("11/4/1996"),2,15.66m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10340,"BONAP",1,ToDateTime("10/29/1996"),ToDateTime("11/26/1996"),ToDateTime("11/8/1996"),3,166.31m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10341,"SIMOB",7,ToDateTime("10/29/1996"),ToDateTime("11/26/1996"),ToDateTime("11/5/1996"),3,26.78m, "Simons bistro","Vinbltet 34","Kobenhavn", null,"1734","Denmark"), + NorthwindFactory.Order(10342,"FRANK",4,ToDateTime("10/30/1996"),ToDateTime("11/13/1996"),ToDateTime("11/4/1996"),2,54.83m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10343,"LEHMS",4,ToDateTime("10/31/1996"),ToDateTime("11/28/1996"),ToDateTime("11/6/1996"),1,110.37m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10344,"WHITC",4,ToDateTime("11/1/1996"),ToDateTime("11/29/1996"),ToDateTime("11/5/1996"),2,23.29m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10345,"QUICK",2,ToDateTime("11/4/1996"),ToDateTime("12/2/1996"),ToDateTime("11/11/1996"),2,249.06m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10346,"RATTC",3,ToDateTime("11/5/1996"),ToDateTime("12/17/1996"),ToDateTime("11/8/1996"),3,142.08m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10347,"FAMIA",4,ToDateTime("11/6/1996"),ToDateTime("12/4/1996"),ToDateTime("11/8/1996"),3,3.10m, "Familia Arquibaldo","Rua Ors, 92","Sao Paulo", "SP","05442-030","Brazil"), + NorthwindFactory.Order(10348,"WANDK",4,ToDateTime("11/7/1996"),ToDateTime("12/5/1996"),ToDateTime("11/15/1996"),2,0.78m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(10349,"SPLIR",7,ToDateTime("11/8/1996"),ToDateTime("12/6/1996"),ToDateTime("11/15/1996"),1,8.63m, "Split Rail Beer & Ale","P.O. Box 555","Lander", "WY","82520","USA"), + NorthwindFactory.Order(10350,"LAMAI",6,ToDateTime("11/11/1996"),ToDateTime("12/9/1996"),ToDateTime("12/3/1996"),2,64.19m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10351,"ERNSH",1,ToDateTime("11/11/1996"),ToDateTime("12/9/1996"),ToDateTime("11/20/1996"),1,162.33m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10352,"FURIB",3,ToDateTime("11/12/1996"),ToDateTime("11/26/1996"),ToDateTime("11/18/1996"),3,1.30m, "Furia Bacalhau e Frutos do Mar","Jardim das rosas n. 32","Lisboa", null,"1675","Portugal"), + NorthwindFactory.Order(10353,"PICCO",7,ToDateTime("11/13/1996"),ToDateTime("12/11/1996"),ToDateTime("11/25/1996"),3,360.63m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(10354,"PERIC",8,ToDateTime("11/14/1996"),ToDateTime("12/12/1996"),ToDateTime("11/20/1996"),3,53.80m, "Pericles Comidas clsicas","Calle Dr. Jorge Cash 321","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10355,"AROUT",6,ToDateTime("11/15/1996"),ToDateTime("12/13/1996"),ToDateTime("11/20/1996"),1,41.95m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10356,"WANDK",6,ToDateTime("11/18/1996"),ToDateTime("12/16/1996"),ToDateTime("11/27/1996"),2,36.71m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(10357,"LILAS",1,ToDateTime("11/19/1996"),ToDateTime("12/17/1996"),ToDateTime("12/2/1996"),3,34.88m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10358,"LAMAI",5,ToDateTime("11/20/1996"),ToDateTime("12/18/1996"),ToDateTime("11/27/1996"),1,19.64m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10359,"SEVES",5,ToDateTime("11/21/1996"),ToDateTime("12/19/1996"),ToDateTime("11/26/1996"),3,288.43m, "Seven Seas Imports","90 Wadhurst Rd.","London", null,"OX15 4NB","UK"), + NorthwindFactory.Order(10360,"BLONP",4,ToDateTime("11/22/1996"),ToDateTime("12/20/1996"),ToDateTime("12/2/1996"),3,131.70m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10361,"QUICK",1,ToDateTime("11/22/1996"),ToDateTime("12/20/1996"),ToDateTime("12/3/1996"),2,183.17m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10362,"BONAP",3,ToDateTime("11/25/1996"),ToDateTime("12/23/1996"),ToDateTime("11/28/1996"),1,96.04m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10363,"DRACD",4,ToDateTime("11/26/1996"),ToDateTime("12/24/1996"),ToDateTime("12/4/1996"),3,30.54m, "Drachenblut Delikatessen","Walserweg 21","Aachen", null,"52066","Germany"), + NorthwindFactory.Order(10364,"EASTC",1,ToDateTime("11/26/1996"),ToDateTime("1/7/1997"),ToDateTime("12/4/1996"),1,71.97m, "Eastern Connection","35 King George","London", null,"WX3 6FW","UK"), + NorthwindFactory.Order(10365,"ANTON",3,ToDateTime("11/27/1996"),ToDateTime("12/25/1996"),ToDateTime("12/2/1996"),2,22.00m, "Antonio Moreno Taquera","Mataderos 2312","Mxico D.F.", null,"05023","Mexico"), + NorthwindFactory.Order(10366,"GALED",8,ToDateTime("11/28/1996"),ToDateTime("1/9/1997"),ToDateTime("12/30/1996"),2,10.14m, "Galera del gastronmo","Rambla de Catalua, 23","Barcelona", null,"8022","Spain"), + NorthwindFactory.Order(10367,"VAFFE",7,ToDateTime("11/28/1996"),ToDateTime("12/26/1996"),ToDateTime("12/2/1996"),3,13.55m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10368,"ERNSH",2,ToDateTime("11/29/1996"),ToDateTime("12/27/1996"),ToDateTime("12/2/1996"),2,101.95m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10369,"SPLIR",8,ToDateTime("12/2/1996"),ToDateTime("12/30/1996"),ToDateTime("12/9/1996"),2,195.68m, "Split Rail Beer & Ale","P.O. Box 555","Lander", "WY","82520","USA"), + NorthwindFactory.Order(10370,"CHOPS",6,ToDateTime("12/3/1996"),ToDateTime("12/31/1996"),ToDateTime("12/27/1996"),2,1.17m, "Chop-suey Chinese","Hauptstr. 31","Bern", null,"3012","Switzerland"), + NorthwindFactory.Order(10371,"LAMAI",1,ToDateTime("12/3/1996"),ToDateTime("12/31/1996"),ToDateTime("12/24/1996"),1,0.45m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10372,"QUEEN",5,ToDateTime("12/4/1996"),ToDateTime("1/1/1997"),ToDateTime("12/9/1996"),2,890.78m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10373,"HUNGO",4,ToDateTime("12/5/1996"),ToDateTime("1/2/1997"),ToDateTime("12/11/1996"),3,124.12m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10374,"WOLZA",1,ToDateTime("12/5/1996"),ToDateTime("1/2/1997"),ToDateTime("12/9/1996"),3,3.94m, "Wolski Zajazd","ul. Filtrowa 68","Warszawa", null,"01-012","Poland"), + NorthwindFactory.Order(10375,"HUNGC",3,ToDateTime("12/6/1996"),ToDateTime("1/3/1997"),ToDateTime("12/9/1996"),2,20.12m, "Hungry Coyote Import Store","City Center Plaza 516 Main St.","Elgin", "OR","97827","USA"), + NorthwindFactory.Order(10376,"MEREP",1,ToDateTime("12/9/1996"),ToDateTime("1/6/1997"),ToDateTime("12/13/1996"),2,20.39m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10377,"SEVES",1,ToDateTime("12/9/1996"),ToDateTime("1/6/1997"),ToDateTime("12/13/1996"),3,22.21m, "Seven Seas Imports","90 Wadhurst Rd.","London", null,"OX15 4NB","UK"), + NorthwindFactory.Order(10378,"FOLKO",5,ToDateTime("12/10/1996"),ToDateTime("1/7/1997"),ToDateTime("12/19/1996"),3,5.44m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10379,"QUEDE",2,ToDateTime("12/11/1996"),ToDateTime("1/8/1997"),ToDateTime("12/13/1996"),1,45.03m, "Que Delcia","Rua da Panificadora, 12","Rio de Janeiro", "RJ","02389-673","Brazil"), + NorthwindFactory.Order(10380,"HUNGO",8,ToDateTime("12/12/1996"),ToDateTime("1/9/1997"),ToDateTime("1/16/1997"),3,35.03m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10381,"LILAS",3,ToDateTime("12/12/1996"),ToDateTime("1/9/1997"),ToDateTime("12/13/1996"),3,7.99m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10382,"ERNSH",4,ToDateTime("12/13/1996"),ToDateTime("1/10/1997"),ToDateTime("12/16/1996"),1,94.77m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10383,"AROUT",8,ToDateTime("12/16/1996"),ToDateTime("1/13/1997"),ToDateTime("12/18/1996"),3,34.24m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10384,"BERGS",3,ToDateTime("12/16/1996"),ToDateTime("1/13/1997"),ToDateTime("12/20/1996"),3,168.64m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10385,"SPLIR",1,ToDateTime("12/17/1996"),ToDateTime("1/14/1997"),ToDateTime("12/23/1996"),2,30.96m, "Split Rail Beer & Ale","P.O. Box 555","Lander", "WY","82520","USA"), + NorthwindFactory.Order(10386,"FAMIA",9,ToDateTime("12/18/1996"),ToDateTime("1/1/1997"),ToDateTime("12/25/1996"),3,13.99m, "Familia Arquibaldo","Rua Ors, 92","Sao Paulo", "SP","05442-030","Brazil"), + NorthwindFactory.Order(10387,"SANTG",1,ToDateTime("12/18/1996"),ToDateTime("1/15/1997"),ToDateTime("12/20/1996"),2,93.63m, "Sant Gourmet","Erling Skakkes gate 78","Stavern", null,"4110","Norway"), + NorthwindFactory.Order(10388,"SEVES",2,ToDateTime("12/19/1996"),ToDateTime("1/16/1997"),ToDateTime("12/20/1996"),1,34.86m, "Seven Seas Imports","90 Wadhurst Rd.","London", null,"OX15 4NB","UK"), + NorthwindFactory.Order(10389,"BOTTM",4,ToDateTime("12/20/1996"),ToDateTime("1/17/1997"),ToDateTime("12/24/1996"),2,47.42m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10390,"ERNSH",6,ToDateTime("12/23/1996"),ToDateTime("1/20/1997"),ToDateTime("12/26/1996"),1,126.38m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10391,"DRACD",3,ToDateTime("12/23/1996"),ToDateTime("1/20/1997"),ToDateTime("12/31/1996"),3,5.45m, "Drachenblut Delikatessen","Walserweg 21","Aachen", null,"52066","Germany"), + NorthwindFactory.Order(10392,"PICCO",2,ToDateTime("12/24/1996"),ToDateTime("1/21/1997"),ToDateTime("1/1/1997"),3,122.46m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(10393,"SAVEA",1,ToDateTime("12/25/1996"),ToDateTime("1/22/1997"),ToDateTime("1/3/1997"),3,126.56m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10394,"HUNGC",1,ToDateTime("12/25/1996"),ToDateTime("1/22/1997"),ToDateTime("1/3/1997"),3,30.34m, "Hungry Coyote Import Store","City Center Plaza 516 Main St.","Elgin", "OR","97827","USA"), + NorthwindFactory.Order(10395,"HILAA",6,ToDateTime("12/26/1996"),ToDateTime("1/23/1997"),ToDateTime("1/3/1997"),1,184.41m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10396,"FRANK",1,ToDateTime("12/27/1996"),ToDateTime("1/10/1997"),ToDateTime("1/6/1997"),3,135.35m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10397,"PRINI",5,ToDateTime("12/27/1996"),ToDateTime("1/24/1997"),ToDateTime("1/2/1997"),1,60.26m, "Princesa Isabel Vinhos","Estrada da sade n. 58","Lisboa", null,"1756","Portugal"), + NorthwindFactory.Order(10398,"SAVEA",2,ToDateTime("12/30/1996"),ToDateTime("1/27/1997"),ToDateTime("1/9/1997"),3,89.16m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10399,"VAFFE",8,ToDateTime("12/31/1996"),ToDateTime("1/14/1997"),ToDateTime("1/8/1997"),3,27.36m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10400,"EASTC",1,ToDateTime("1/1/1997"),ToDateTime("1/29/1997"),ToDateTime("1/16/1997"),3,83.93m, "Eastern Connection","35 King George","London", null,"WX3 6FW","UK"), + NorthwindFactory.Order(10401,"RATTC",1,ToDateTime("1/1/1997"),ToDateTime("1/29/1997"),ToDateTime("1/10/1997"),1,12.51m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10402,"ERNSH",8,ToDateTime("1/2/1997"),ToDateTime("2/13/1997"),ToDateTime("1/10/1997"),2,67.88m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10403,"ERNSH",4,ToDateTime("1/3/1997"),ToDateTime("1/31/1997"),ToDateTime("1/9/1997"),3,73.79m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10404,"MAGAA",2,ToDateTime("1/3/1997"),ToDateTime("1/31/1997"),ToDateTime("1/8/1997"),1,155.97m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10405,"LINOD",1,ToDateTime("1/6/1997"),ToDateTime("2/3/1997"),ToDateTime("1/22/1997"),1,34.82m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10406,"QUEEN",7,ToDateTime("1/7/1997"),ToDateTime("2/18/1997"),ToDateTime("1/13/1997"),1,108.04m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10407,"OTTIK",2,ToDateTime("1/7/1997"),ToDateTime("2/4/1997"),ToDateTime("1/30/1997"),2,91.48m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(10408,"FOLIG",8,ToDateTime("1/8/1997"),ToDateTime("2/5/1997"),ToDateTime("1/14/1997"),1,11.26m, "Folies gourmandes","184, chausse de Tournai","Lille", null,"59000","France"), + NorthwindFactory.Order(10409,"OCEAN",3,ToDateTime("1/9/1997"),ToDateTime("2/6/1997"),ToDateTime("1/14/1997"),1,29.83m, "Ocano Atlntico Ltda.","Ing. Gustavo Moncada 8585 Piso 20-A","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10410,"BOTTM",3,ToDateTime("1/10/1997"),ToDateTime("2/7/1997"),ToDateTime("1/15/1997"),3,2.40m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10411,"BOTTM",9,ToDateTime("1/10/1997"),ToDateTime("2/7/1997"),ToDateTime("1/21/1997"),3,23.65m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10412,"WARTH",8,ToDateTime("1/13/1997"),ToDateTime("2/10/1997"),ToDateTime("1/15/1997"),2,3.77m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10413,"LAMAI",3,ToDateTime("1/14/1997"),ToDateTime("2/11/1997"),ToDateTime("1/16/1997"),2,95.66m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10414,"FAMIA",2,ToDateTime("1/14/1997"),ToDateTime("2/11/1997"),ToDateTime("1/17/1997"),3,21.48m, "Familia Arquibaldo","Rua Ors, 92","Sao Paulo", "SP","05442-030","Brazil"), + NorthwindFactory.Order(10415,"HUNGC",3,ToDateTime("1/15/1997"),ToDateTime("2/12/1997"),ToDateTime("1/24/1997"),1,0.20m, "Hungry Coyote Import Store","City Center Plaza 516 Main St.","Elgin", "OR","97827","USA"), + NorthwindFactory.Order(10416,"WARTH",8,ToDateTime("1/16/1997"),ToDateTime("2/13/1997"),ToDateTime("1/27/1997"),3,22.72m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10417,"SIMOB",4,ToDateTime("1/16/1997"),ToDateTime("2/13/1997"),ToDateTime("1/28/1997"),3,70.29m, "Simons bistro","Vinbltet 34","Kobenhavn", null,"1734","Denmark"), + NorthwindFactory.Order(10418,"QUICK",4,ToDateTime("1/17/1997"),ToDateTime("2/14/1997"),ToDateTime("1/24/1997"),1,17.55m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10419,"RICSU",4,ToDateTime("1/20/1997"),ToDateTime("2/17/1997"),ToDateTime("1/30/1997"),2,137.35m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(10420,"WELLI",3,ToDateTime("1/21/1997"),ToDateTime("2/18/1997"),ToDateTime("1/27/1997"),1,44.12m, "Wellington Importadora","Rua do Mercado, 12","Resende", "SP","08737-363","Brazil"), + NorthwindFactory.Order(10421,"QUEDE",8,ToDateTime("1/21/1997"),ToDateTime("3/4/1997"),ToDateTime("1/27/1997"),1,99.23m, "Que Delcia","Rua da Panificadora, 12","Rio de Janeiro", "RJ","02389-673","Brazil"), + NorthwindFactory.Order(10422,"FRANS",2,ToDateTime("1/22/1997"),ToDateTime("2/19/1997"),ToDateTime("1/31/1997"),1,3.02m, "Franchi S.p.A.","Via Monte Bianco 34","Torino", null,"10100","Italy"), + NorthwindFactory.Order(10423,"GOURL",6,ToDateTime("1/23/1997"),ToDateTime("2/6/1997"),ToDateTime("2/24/1997"),3,24.50m, "Gourmet Lanchonetes","Av. Brasil, 442","Campinas", "SP","04876-786","Brazil"), + NorthwindFactory.Order(10424,"MEREP",7,ToDateTime("1/23/1997"),ToDateTime("2/20/1997"),ToDateTime("1/27/1997"),2,370.61m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10425,"LAMAI",6,ToDateTime("1/24/1997"),ToDateTime("2/21/1997"),ToDateTime("2/14/1997"),2,7.93m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10426,"GALED",4,ToDateTime("1/27/1997"),ToDateTime("2/24/1997"),ToDateTime("2/6/1997"),1,18.69m, "Galera del gastronmo","Rambla de Catalua, 23","Barcelona", null,"8022","Spain"), + NorthwindFactory.Order(10427,"PICCO",4,ToDateTime("1/27/1997"),ToDateTime("2/24/1997"),ToDateTime("3/3/1997"),2,31.29m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(10428,"REGGC",7,ToDateTime("1/28/1997"),ToDateTime("2/25/1997"),ToDateTime("2/4/1997"),1,11.09m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10429,"HUNGO",3,ToDateTime("1/29/1997"),ToDateTime("3/12/1997"),ToDateTime("2/7/1997"),2,56.63m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10430,"ERNSH",4,ToDateTime("1/30/1997"),ToDateTime("2/13/1997"),ToDateTime("2/3/1997"),1,458.78m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10431,"BOTTM",4,ToDateTime("1/30/1997"),ToDateTime("2/13/1997"),ToDateTime("2/7/1997"),2,44.17m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10432,"SPLIR",3,ToDateTime("1/31/1997"),ToDateTime("2/14/1997"),ToDateTime("2/7/1997"),2,4.34m, "Split Rail Beer & Ale","P.O. Box 555","Lander", "WY","82520","USA"), + NorthwindFactory.Order(10433,"PRINI",3,ToDateTime("2/3/1997"),ToDateTime("3/3/1997"),ToDateTime("3/4/1997"),3,73.83m, "Princesa Isabel Vinhos","Estrada da sade n. 58","Lisboa", null,"1756","Portugal"), + NorthwindFactory.Order(10434,"FOLKO",3,ToDateTime("2/3/1997"),ToDateTime("3/3/1997"),ToDateTime("2/13/1997"),2,17.92m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10435,"CONSH",8,ToDateTime("2/4/1997"),ToDateTime("3/18/1997"),ToDateTime("2/7/1997"),2,9.21m, "Consolidated Holdings","Berkeley Gardens 12 Brewery","London", null,"WX1 6LT","UK"), + NorthwindFactory.Order(10436,"BLONP",3,ToDateTime("2/5/1997"),ToDateTime("3/5/1997"),ToDateTime("2/11/1997"),2,156.66m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10437,"WARTH",8,ToDateTime("2/5/1997"),ToDateTime("3/5/1997"),ToDateTime("2/12/1997"),1,19.97m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10438,"TOMSP",3,ToDateTime("2/6/1997"),ToDateTime("3/6/1997"),ToDateTime("2/14/1997"),2,8.24m, "Toms Spezialitten","Luisenstr. 48","Mnster", null,"44087","Germany"), + NorthwindFactory.Order(10439,"MEREP",6,ToDateTime("2/7/1997"),ToDateTime("3/7/1997"),ToDateTime("2/10/1997"),3,4.07m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10440,"SAVEA",4,ToDateTime("2/10/1997"),ToDateTime("3/10/1997"),ToDateTime("2/28/1997"),2,86.53m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10441,"OLDWO",3,ToDateTime("2/10/1997"),ToDateTime("3/24/1997"),ToDateTime("3/14/1997"),2,73.02m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(10442,"ERNSH",3,ToDateTime("2/11/1997"),ToDateTime("3/11/1997"),ToDateTime("2/18/1997"),2,47.94m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10443,"REGGC",8,ToDateTime("2/12/1997"),ToDateTime("3/12/1997"),ToDateTime("2/14/1997"),1,13.95m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10444,"BERGS",3,ToDateTime("2/12/1997"),ToDateTime("3/12/1997"),ToDateTime("2/21/1997"),3,3.50m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10445,"BERGS",3,ToDateTime("2/13/1997"),ToDateTime("3/13/1997"),ToDateTime("2/20/1997"),1,9.30m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10446,"TOMSP",6,ToDateTime("2/14/1997"),ToDateTime("3/14/1997"),ToDateTime("2/19/1997"),1,14.68m, "Toms Spezialitten","Luisenstr. 48","Mnster", null,"44087","Germany"), + NorthwindFactory.Order(10447,"RICAR",4,ToDateTime("2/14/1997"),ToDateTime("3/14/1997"),ToDateTime("3/7/1997"),2,68.66m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10448,"RANCH",4,ToDateTime("2/17/1997"),ToDateTime("3/17/1997"),ToDateTime("2/24/1997"),2,38.82m, "Rancho grande","Av. del Libertador 900","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10449,"BLONP",3,ToDateTime("2/18/1997"),ToDateTime("3/18/1997"),ToDateTime("2/27/1997"),2,53.30m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10450,"VICTE",8,ToDateTime("2/19/1997"),ToDateTime("3/19/1997"),ToDateTime("3/11/1997"),2,7.23m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10451,"QUICK",4,ToDateTime("2/19/1997"),ToDateTime("3/5/1997"),ToDateTime("3/12/1997"),3,189.09m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10452,"SAVEA",8,ToDateTime("2/20/1997"),ToDateTime("3/20/1997"),ToDateTime("2/26/1997"),1,140.26m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10453,"AROUT",1,ToDateTime("2/21/1997"),ToDateTime("3/21/1997"),ToDateTime("2/26/1997"),2,25.36m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10454,"LAMAI",4,ToDateTime("2/21/1997"),ToDateTime("3/21/1997"),ToDateTime("2/25/1997"),3,2.74m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10455,"WARTH",8,ToDateTime("2/24/1997"),ToDateTime("4/7/1997"),ToDateTime("3/3/1997"),2,180.45m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10456,"KOENE",8,ToDateTime("2/25/1997"),ToDateTime("4/8/1997"),ToDateTime("2/28/1997"),2,8.12m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10457,"KOENE",2,ToDateTime("2/25/1997"),ToDateTime("3/25/1997"),ToDateTime("3/3/1997"),1,11.57m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10458,"SUPRD",7,ToDateTime("2/26/1997"),ToDateTime("3/26/1997"),ToDateTime("3/4/1997"),3,147.06m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10459,"VICTE",4,ToDateTime("2/27/1997"),ToDateTime("3/27/1997"),ToDateTime("2/28/1997"),2,25.09m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10460,"FOLKO",8,ToDateTime("2/28/1997"),ToDateTime("3/28/1997"),ToDateTime("3/3/1997"),1,16.27m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10461,"LILAS",1,ToDateTime("2/28/1997"),ToDateTime("3/28/1997"),ToDateTime("3/5/1997"),3,148.61m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10462,"CONSH",2,ToDateTime("3/3/1997"),ToDateTime("3/31/1997"),ToDateTime("3/18/1997"),1,6.17m, "Consolidated Holdings","Berkeley Gardens 12 Brewery","London", null,"WX1 6LT","UK"), + NorthwindFactory.Order(10463,"SUPRD",5,ToDateTime("3/4/1997"),ToDateTime("4/1/1997"),ToDateTime("3/6/1997"),3,14.78m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10464,"FURIB",4,ToDateTime("3/4/1997"),ToDateTime("4/1/1997"),ToDateTime("3/14/1997"),2,89.00m, "Furia Bacalhau e Frutos do Mar","Jardim das rosas n. 32","Lisboa", null,"1675","Portugal"), + NorthwindFactory.Order(10465,"VAFFE",1,ToDateTime("3/5/1997"),ToDateTime("4/2/1997"),ToDateTime("3/14/1997"),3,145.04m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10466,"COMMI",4,ToDateTime("3/6/1997"),ToDateTime("4/3/1997"),ToDateTime("3/13/1997"),1,11.93m, "Comrcio Mineiro","Av. dos Lusadas, 23","Sao Paulo", "SP","05432-043","Brazil"), + NorthwindFactory.Order(10467,"MAGAA",8,ToDateTime("3/6/1997"),ToDateTime("4/3/1997"),ToDateTime("3/11/1997"),2,4.93m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10468,"KOENE",3,ToDateTime("3/7/1997"),ToDateTime("4/4/1997"),ToDateTime("3/12/1997"),3,44.12m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10469,"WHITC",1,ToDateTime("3/10/1997"),ToDateTime("4/7/1997"),ToDateTime("3/14/1997"),1,60.18m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10470,"BONAP",4,ToDateTime("3/11/1997"),ToDateTime("4/8/1997"),ToDateTime("3/14/1997"),2,64.56m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10471,"BSBEV",2,ToDateTime("3/11/1997"),ToDateTime("4/8/1997"),ToDateTime("3/18/1997"),3,45.59m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(10472,"SEVES",8,ToDateTime("3/12/1997"),ToDateTime("4/9/1997"),ToDateTime("3/19/1997"),1,4.20m, "Seven Seas Imports","90 Wadhurst Rd.","London", null,"OX15 4NB","UK"), + NorthwindFactory.Order(10473,"ISLAT",1,ToDateTime("3/13/1997"),ToDateTime("3/27/1997"),ToDateTime("3/21/1997"),3,16.37m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10474,"PERIC",5,ToDateTime("3/13/1997"),ToDateTime("4/10/1997"),ToDateTime("3/21/1997"),2,83.49m, "Pericles Comidas clsicas","Calle Dr. Jorge Cash 321","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10475,"SUPRD",9,ToDateTime("3/14/1997"),ToDateTime("4/11/1997"),ToDateTime("4/4/1997"),1,68.52m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10476,"HILAA",8,ToDateTime("3/17/1997"),ToDateTime("4/14/1997"),ToDateTime("3/24/1997"),3,4.41m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10477,"PRINI",5,ToDateTime("3/17/1997"),ToDateTime("4/14/1997"),ToDateTime("3/25/1997"),2,13.02m, "Princesa Isabel Vinhos","Estrada da sade n. 58","Lisboa", null,"1756","Portugal"), + NorthwindFactory.Order(10478,"VICTE",2,ToDateTime("3/18/1997"),ToDateTime("4/1/1997"),ToDateTime("3/26/1997"),3,4.81m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10479,"RATTC",3,ToDateTime("3/19/1997"),ToDateTime("4/16/1997"),ToDateTime("3/21/1997"),3,708.95m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10480,"FOLIG",6,ToDateTime("3/20/1997"),ToDateTime("4/17/1997"),ToDateTime("3/24/1997"),2,1.35m, "Folies gourmandes","184, chausse de Tournai","Lille", null,"59000","France"), + NorthwindFactory.Order(10481,"RICAR",8,ToDateTime("3/20/1997"),ToDateTime("4/17/1997"),ToDateTime("3/25/1997"),2,64.33m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10482,"LAZYK",1,ToDateTime("3/21/1997"),ToDateTime("4/18/1997"),ToDateTime("4/10/1997"),3,7.48m, "Lazy K Kountry Store","12 Orchestra Terrace","Walla Walla", "WA","99362","USA"), + NorthwindFactory.Order(10483,"WHITC",7,ToDateTime("3/24/1997"),ToDateTime("4/21/1997"),ToDateTime("4/25/1997"),2,15.28m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10484,"BSBEV",3,ToDateTime("3/24/1997"),ToDateTime("4/21/1997"),ToDateTime("4/1/1997"),3,6.88m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(10485,"LINOD",4,ToDateTime("3/25/1997"),ToDateTime("4/8/1997"),ToDateTime("3/31/1997"),2,64.45m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10486,"HILAA",1,ToDateTime("3/26/1997"),ToDateTime("4/23/1997"),ToDateTime("4/2/1997"),2,30.53m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10487,"QUEEN",2,ToDateTime("3/26/1997"),ToDateTime("4/23/1997"),ToDateTime("3/28/1997"),2,71.07m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10488,"FRANK",8,ToDateTime("3/27/1997"),ToDateTime("4/24/1997"),ToDateTime("4/2/1997"),2,4.93m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10489,"PICCO",6,ToDateTime("3/28/1997"),ToDateTime("4/25/1997"),ToDateTime("4/9/1997"),2,5.29m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(10490,"HILAA",7,ToDateTime("3/31/1997"),ToDateTime("4/28/1997"),ToDateTime("4/3/1997"),2,210.19m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10491,"FURIB",8,ToDateTime("3/31/1997"),ToDateTime("4/28/1997"),ToDateTime("4/8/1997"),3,16.96m, "Furia Bacalhau e Frutos do Mar","Jardim das rosas n. 32","Lisboa", null,"1675","Portugal"), + NorthwindFactory.Order(10492,"BOTTM",3,ToDateTime("4/1/1997"),ToDateTime("4/29/1997"),ToDateTime("4/11/1997"),1,62.89m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10493,"LAMAI",4,ToDateTime("4/2/1997"),ToDateTime("4/30/1997"),ToDateTime("4/10/1997"),3,10.64m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10494,"COMMI",4,ToDateTime("4/2/1997"),ToDateTime("4/30/1997"),ToDateTime("4/9/1997"),2,65.99m, "Comrcio Mineiro","Av. dos Lusadas, 23","Sao Paulo", "SP","05432-043","Brazil"), + NorthwindFactory.Order(10495,"LAUGB",3,ToDateTime("4/3/1997"),ToDateTime("5/1/1997"),ToDateTime("4/11/1997"),3,4.65m, "Laughing Bacchus Wine Cellars","2319 Elm St.","Vancouver", "BC","V3F 2K1","Canada"), + NorthwindFactory.Order(10496,"TRADH",7,ToDateTime("4/4/1997"),ToDateTime("5/2/1997"),ToDateTime("4/7/1997"),2,46.77m, "Tradiao Hipermercados","Av. Ins de Castro, 414","Sao Paulo", "SP","05634-030","Brazil"), + NorthwindFactory.Order(10497,"LEHMS",7,ToDateTime("4/4/1997"),ToDateTime("5/2/1997"),ToDateTime("4/7/1997"),1,36.21m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10498,"HILAA",8,ToDateTime("4/7/1997"),ToDateTime("5/5/1997"),ToDateTime("4/11/1997"),2,29.75m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10499,"LILAS",4,ToDateTime("4/8/1997"),ToDateTime("5/6/1997"),ToDateTime("4/16/1997"),2,102.02m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10500,"LAMAI",6,ToDateTime("4/9/1997"),ToDateTime("5/7/1997"),ToDateTime("4/17/1997"),1,42.68m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10501,"BLAUS",9,ToDateTime("4/9/1997"),ToDateTime("5/7/1997"),ToDateTime("4/16/1997"),3,8.85m, "Blauer See Delikatessen","Forsterstr. 57","Mannheim", null,"68306","Germany"), + NorthwindFactory.Order(10502,"PERIC",2,ToDateTime("4/10/1997"),ToDateTime("5/8/1997"),ToDateTime("4/29/1997"),1,69.32m, "Pericles Comidas clsicas","Calle Dr. Jorge Cash 321","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10503,"HUNGO",6,ToDateTime("4/11/1997"),ToDateTime("5/9/1997"),ToDateTime("4/16/1997"),2,16.74m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10504,"WHITC",4,ToDateTime("4/11/1997"),ToDateTime("5/9/1997"),ToDateTime("4/18/1997"),3,59.13m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10505,"MEREP",3,ToDateTime("4/14/1997"),ToDateTime("5/12/1997"),ToDateTime("4/21/1997"),3,7.13m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10506,"KOENE",9,ToDateTime("4/15/1997"),ToDateTime("5/13/1997"),ToDateTime("5/2/1997"),2,21.19m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10507,"ANTON",7,ToDateTime("4/15/1997"),ToDateTime("5/13/1997"),ToDateTime("4/22/1997"),1,47.45m, "Antonio Moreno Taquera","Mataderos 2312","Mxico D.F.", null,"05023","Mexico"), + NorthwindFactory.Order(10508,"OTTIK",1,ToDateTime("4/16/1997"),ToDateTime("5/14/1997"),ToDateTime("5/13/1997"),2,4.99m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(10509,"BLAUS",4,ToDateTime("4/17/1997"),ToDateTime("5/15/1997"),ToDateTime("4/29/1997"),1,0.15m, "Blauer See Delikatessen","Forsterstr. 57","Mannheim", null,"68306","Germany"), + NorthwindFactory.Order(10510,"SAVEA",6,ToDateTime("4/18/1997"),ToDateTime("5/16/1997"),ToDateTime("4/28/1997"),3,367.63m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10511,"BONAP",4,ToDateTime("4/18/1997"),ToDateTime("5/16/1997"),ToDateTime("4/21/1997"),3,350.64m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10512,"FAMIA",7,ToDateTime("4/21/1997"),ToDateTime("5/19/1997"),ToDateTime("4/24/1997"),2,3.53m, "Familia Arquibaldo","Rua Ors, 92","Sao Paulo", "SP","05442-030","Brazil"), + NorthwindFactory.Order(10513,"WANDK",7,ToDateTime("4/22/1997"),ToDateTime("6/3/1997"),ToDateTime("4/28/1997"),1,105.65m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(10514,"ERNSH",3,ToDateTime("4/22/1997"),ToDateTime("5/20/1997"),ToDateTime("5/16/1997"),2,789.95m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10515,"QUICK",2,ToDateTime("4/23/1997"),ToDateTime("5/7/1997"),ToDateTime("5/23/1997"),1,204.47m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10516,"HUNGO",2,ToDateTime("4/24/1997"),ToDateTime("5/22/1997"),ToDateTime("5/1/1997"),3,62.78m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10517,"NORTS",3,ToDateTime("4/24/1997"),ToDateTime("5/22/1997"),ToDateTime("4/29/1997"),3,32.07m, "North/South","South House 300 Queensbridge","London", null,"SW7 1RZ","UK"), + NorthwindFactory.Order(10518,"TORTU",4,ToDateTime("4/25/1997"),ToDateTime("5/9/1997"),ToDateTime("5/5/1997"),2,218.15m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10519,"CHOPS",6,ToDateTime("4/28/1997"),ToDateTime("5/26/1997"),ToDateTime("5/1/1997"),3,91.76m, "Chop-suey Chinese","Hauptstr. 31","Bern", null,"3012","Switzerland"), + NorthwindFactory.Order(10520,"SANTG",7,ToDateTime("4/29/1997"),ToDateTime("5/27/1997"),ToDateTime("5/1/1997"),1,13.37m, "Sant Gourmet","Erling Skakkes gate 78","Stavern", null,"4110","Norway"), + NorthwindFactory.Order(10521,"CACTU",8,ToDateTime("4/29/1997"),ToDateTime("5/27/1997"),ToDateTime("5/2/1997"),2,17.22m, "Cactus Comidas para llevar","Cerrito 333","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10522,"LEHMS",4,ToDateTime("4/30/1997"),ToDateTime("5/28/1997"),ToDateTime("5/6/1997"),1,45.33m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10523,"SEVES",7,ToDateTime("5/1/1997"),ToDateTime("5/29/1997"),ToDateTime("5/30/1997"),2,77.63m, "Seven Seas Imports","90 Wadhurst Rd.","London", null,"OX15 4NB","UK"), + NorthwindFactory.Order(10524,"BERGS",1,ToDateTime("5/1/1997"),ToDateTime("5/29/1997"),ToDateTime("5/7/1997"),2,244.79m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10525,"BONAP",1,ToDateTime("5/2/1997"),ToDateTime("5/30/1997"),ToDateTime("5/23/1997"),2,11.06m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10526,"WARTH",4,ToDateTime("5/5/1997"),ToDateTime("6/2/1997"),ToDateTime("5/15/1997"),2,58.59m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10527,"QUICK",7,ToDateTime("5/5/1997"),ToDateTime("6/2/1997"),ToDateTime("5/7/1997"),1,41.90m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10528,"GREAL",6,ToDateTime("5/6/1997"),ToDateTime("5/20/1997"),ToDateTime("5/9/1997"),2,3.35m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(10529,"MAISD",5,ToDateTime("5/7/1997"),ToDateTime("6/4/1997"),ToDateTime("5/9/1997"),2,66.69m, "Maison Dewey","Rue Joseph-Bens 532","Bruxelles", null,"B-1180","Belgium"), + NorthwindFactory.Order(10530,"PICCO",3,ToDateTime("5/8/1997"),ToDateTime("6/5/1997"),ToDateTime("5/12/1997"),2,339.22m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(10531,"OCEAN",7,ToDateTime("5/8/1997"),ToDateTime("6/5/1997"),ToDateTime("5/19/1997"),1,8.12m, "Ocano Atlntico Ltda.","Ing. Gustavo Moncada 8585 Piso 20-A","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10532,"EASTC",7,ToDateTime("5/9/1997"),ToDateTime("6/6/1997"),ToDateTime("5/12/1997"),3,74.46m, "Eastern Connection","35 King George","London", null,"WX3 6FW","UK"), + NorthwindFactory.Order(10533,"FOLKO",8,ToDateTime("5/12/1997"),ToDateTime("6/9/1997"),ToDateTime("5/22/1997"),1,188.04m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10534,"LEHMS",8,ToDateTime("5/12/1997"),ToDateTime("6/9/1997"),ToDateTime("5/14/1997"),2,27.94m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10535,"ANTON",4,ToDateTime("5/13/1997"),ToDateTime("6/10/1997"),ToDateTime("5/21/1997"),1,15.64m, "Antonio Moreno Taquera","Mataderos 2312","Mxico D.F.", null,"05023","Mexico"), + NorthwindFactory.Order(10536,"LEHMS",3,ToDateTime("5/14/1997"),ToDateTime("6/11/1997"),ToDateTime("6/6/1997"),2,58.88m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10537,"RICSU",1,ToDateTime("5/14/1997"),ToDateTime("5/28/1997"),ToDateTime("5/19/1997"),1,78.85m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(10538,"BSBEV",9,ToDateTime("5/15/1997"),ToDateTime("6/12/1997"),ToDateTime("5/16/1997"),3,4.87m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(10539,"BSBEV",6,ToDateTime("5/16/1997"),ToDateTime("6/13/1997"),ToDateTime("5/23/1997"),3,12.36m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(10540,"QUICK",3,ToDateTime("5/19/1997"),ToDateTime("6/16/1997"),ToDateTime("6/13/1997"),3,1007.64m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10541,"HANAR",2,ToDateTime("5/19/1997"),ToDateTime("6/16/1997"),ToDateTime("5/29/1997"),1,68.65m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10542,"KOENE",1,ToDateTime("5/20/1997"),ToDateTime("6/17/1997"),ToDateTime("5/26/1997"),3,10.95m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10543,"LILAS",8,ToDateTime("5/21/1997"),ToDateTime("6/18/1997"),ToDateTime("5/23/1997"),2,48.17m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10544,"LONEP",4,ToDateTime("5/21/1997"),ToDateTime("6/18/1997"),ToDateTime("5/30/1997"),1,24.91m, "Lonesome Pine Restaurant","89 Chiaroscuro Rd.","Portland", "OR","97219","USA"), + NorthwindFactory.Order(10545,"LAZYK",8,ToDateTime("5/22/1997"),ToDateTime("6/19/1997"),ToDateTime("6/26/1997"),2,11.92m, "Lazy K Kountry Store","12 Orchestra Terrace","Walla Walla", "WA","99362","USA"), + NorthwindFactory.Order(10546,"VICTE",1,ToDateTime("5/23/1997"),ToDateTime("6/20/1997"),ToDateTime("5/27/1997"),3,194.72m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10547,"SEVES",3,ToDateTime("5/23/1997"),ToDateTime("6/20/1997"),ToDateTime("6/2/1997"),2,178.43m, "Seven Seas Imports","90 Wadhurst Rd.","London", null,"OX15 4NB","UK"), + NorthwindFactory.Order(10548,"TOMSP",3,ToDateTime("5/26/1997"),ToDateTime("6/23/1997"),ToDateTime("6/2/1997"),2,1.43m, "Toms Spezialitten","Luisenstr. 48","Mnster", null,"44087","Germany"), + NorthwindFactory.Order(10549,"QUICK",5,ToDateTime("5/27/1997"),ToDateTime("6/10/1997"),ToDateTime("5/30/1997"),1,171.24m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10550,"GODOS",7,ToDateTime("5/28/1997"),ToDateTime("6/25/1997"),ToDateTime("6/6/1997"),3,4.32m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(10551,"FURIB",4,ToDateTime("5/28/1997"),ToDateTime("7/9/1997"),ToDateTime("6/6/1997"),3,72.95m, "Furia Bacalhau e Frutos do Mar","Jardim das rosas n. 32","Lisboa", null,"1675","Portugal"), + NorthwindFactory.Order(10552,"HILAA",2,ToDateTime("5/29/1997"),ToDateTime("6/26/1997"),ToDateTime("6/5/1997"),1,83.22m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10553,"WARTH",2,ToDateTime("5/30/1997"),ToDateTime("6/27/1997"),ToDateTime("6/3/1997"),2,149.49m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10554,"OTTIK",4,ToDateTime("5/30/1997"),ToDateTime("6/27/1997"),ToDateTime("6/5/1997"),3,120.97m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(10555,"SAVEA",6,ToDateTime("6/2/1997"),ToDateTime("6/30/1997"),ToDateTime("6/4/1997"),3,252.49m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10556,"SIMOB",2,ToDateTime("6/3/1997"),ToDateTime("7/15/1997"),ToDateTime("6/13/1997"),1,9.80m, "Simons bistro","Vinbltet 34","Kobenhavn", null,"1734","Denmark"), + NorthwindFactory.Order(10557,"LEHMS",9,ToDateTime("6/3/1997"),ToDateTime("6/17/1997"),ToDateTime("6/6/1997"),2,96.72m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10558,"AROUT",1,ToDateTime("6/4/1997"),ToDateTime("7/2/1997"),ToDateTime("6/10/1997"),2,72.97m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10559,"BLONP",6,ToDateTime("6/5/1997"),ToDateTime("7/3/1997"),ToDateTime("6/13/1997"),1,8.05m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10560,"FRANK",8,ToDateTime("6/6/1997"),ToDateTime("7/4/1997"),ToDateTime("6/9/1997"),1,36.65m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10561,"FOLKO",2,ToDateTime("6/6/1997"),ToDateTime("7/4/1997"),ToDateTime("6/9/1997"),2,242.21m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10562,"REGGC",1,ToDateTime("6/9/1997"),ToDateTime("7/7/1997"),ToDateTime("6/12/1997"),1,22.95m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10563,"RICAR",2,ToDateTime("6/10/1997"),ToDateTime("7/22/1997"),ToDateTime("6/24/1997"),2,60.43m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10564,"RATTC",4,ToDateTime("6/10/1997"),ToDateTime("7/8/1997"),ToDateTime("6/16/1997"),3,13.75m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10565,"MEREP",8,ToDateTime("6/11/1997"),ToDateTime("7/9/1997"),ToDateTime("6/18/1997"),2,7.15m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10566,"BLONP",9,ToDateTime("6/12/1997"),ToDateTime("7/10/1997"),ToDateTime("6/18/1997"),1,88.40m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10567,"HUNGO",1,ToDateTime("6/12/1997"),ToDateTime("7/10/1997"),ToDateTime("6/17/1997"),1,33.97m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10568,"GALED",3,ToDateTime("6/13/1997"),ToDateTime("7/11/1997"),ToDateTime("7/9/1997"),3,6.54m, "Galera del gastronmo","Rambla de Catalua, 23","Barcelona", null,"8022","Spain"), + NorthwindFactory.Order(10569,"RATTC",5,ToDateTime("6/16/1997"),ToDateTime("7/14/1997"),ToDateTime("7/11/1997"),1,58.98m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10570,"MEREP",3,ToDateTime("6/17/1997"),ToDateTime("7/15/1997"),ToDateTime("6/19/1997"),3,188.99m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10571,"ERNSH",8,ToDateTime("6/17/1997"),ToDateTime("7/29/1997"),ToDateTime("7/4/1997"),3,26.06m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10572,"BERGS",3,ToDateTime("6/18/1997"),ToDateTime("7/16/1997"),ToDateTime("6/25/1997"),2,116.43m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10573,"ANTON",7,ToDateTime("6/19/1997"),ToDateTime("7/17/1997"),ToDateTime("6/20/1997"),3,84.84m, "Antonio Moreno Taquera","Mataderos 2312","Mxico D.F.", null,"05023","Mexico"), + NorthwindFactory.Order(10574,"TRAIH",4,ToDateTime("6/19/1997"),ToDateTime("7/17/1997"),ToDateTime("6/30/1997"),2,37.60m, "Trail's Head Gourmet Provisioners","722 DaVinci Blvd.","Kirkland", "WA","98034","USA"), + NorthwindFactory.Order(10575,"MORGK",5,ToDateTime("6/20/1997"),ToDateTime("7/4/1997"),ToDateTime("6/30/1997"),1,127.34m, "Morgenstern Gesundkost","Heerstr. 22","Leipzig", null,"04179","Germany"), + NorthwindFactory.Order(10576,"TORTU",3,ToDateTime("6/23/1997"),ToDateTime("7/7/1997"),ToDateTime("6/30/1997"),3,18.56m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10577,"TRAIH",9,ToDateTime("6/23/1997"),ToDateTime("8/4/1997"),ToDateTime("6/30/1997"),2,25.41m, "Trail's Head Gourmet Provisioners","722 DaVinci Blvd.","Kirkland", "WA","98034","USA"), + NorthwindFactory.Order(10578,"BSBEV",4,ToDateTime("6/24/1997"),ToDateTime("7/22/1997"),ToDateTime("7/25/1997"),3,29.60m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(10579,"LETSS",1,ToDateTime("6/25/1997"),ToDateTime("7/23/1997"),ToDateTime("7/4/1997"),2,13.73m, "Let's Stop N Shop","87 Polk St. Suite 5","San Francisco", "CA","94117","USA"), + NorthwindFactory.Order(10580,"OTTIK",4,ToDateTime("6/26/1997"),ToDateTime("7/24/1997"),ToDateTime("7/1/1997"),3,75.89m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(10581,"FAMIA",3,ToDateTime("6/26/1997"),ToDateTime("7/24/1997"),ToDateTime("7/2/1997"),1,3.01m, "Familia Arquibaldo","Rua Ors, 92","Sao Paulo", "SP","05442-030","Brazil"), + NorthwindFactory.Order(10582,"BLAUS",3,ToDateTime("6/27/1997"),ToDateTime("7/25/1997"),ToDateTime("7/14/1997"),2,27.71m, "Blauer See Delikatessen","Forsterstr. 57","Mannheim", null,"68306","Germany"), + NorthwindFactory.Order(10583,"WARTH",2,ToDateTime("6/30/1997"),ToDateTime("7/28/1997"),ToDateTime("7/4/1997"),2,7.28m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10584,"BLONP",4,ToDateTime("6/30/1997"),ToDateTime("7/28/1997"),ToDateTime("7/4/1997"),1,59.14m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10585,"WELLI",7,ToDateTime("7/1/1997"),ToDateTime("7/29/1997"),ToDateTime("7/10/1997"),1,13.41m, "Wellington Importadora","Rua do Mercado, 12","Resende", "SP","08737-363","Brazil"), + NorthwindFactory.Order(10586,"REGGC",9,ToDateTime("7/2/1997"),ToDateTime("7/30/1997"),ToDateTime("7/9/1997"),1,0.48m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10587,"QUEDE",1,ToDateTime("7/2/1997"),ToDateTime("7/30/1997"),ToDateTime("7/9/1997"),1,62.52m, "Que Delcia","Rua da Panificadora, 12","Rio de Janeiro", "RJ","02389-673","Brazil"), + NorthwindFactory.Order(10588,"QUICK",2,ToDateTime("7/3/1997"),ToDateTime("7/31/1997"),ToDateTime("7/10/1997"),3,194.67m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10589,"GREAL",8,ToDateTime("7/4/1997"),ToDateTime("8/1/1997"),ToDateTime("7/14/1997"),2,4.42m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(10590,"MEREP",4,ToDateTime("7/7/1997"),ToDateTime("8/4/1997"),ToDateTime("7/14/1997"),3,44.77m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10591,"VAFFE",1,ToDateTime("7/7/1997"),ToDateTime("7/21/1997"),ToDateTime("7/16/1997"),1,55.92m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10592,"LEHMS",3,ToDateTime("7/8/1997"),ToDateTime("8/5/1997"),ToDateTime("7/16/1997"),1,32.10m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10593,"LEHMS",7,ToDateTime("7/9/1997"),ToDateTime("8/6/1997"),ToDateTime("8/13/1997"),2,174.20m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10594,"OLDWO",3,ToDateTime("7/9/1997"),ToDateTime("8/6/1997"),ToDateTime("7/16/1997"),2,5.24m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(10595,"ERNSH",2,ToDateTime("7/10/1997"),ToDateTime("8/7/1997"),ToDateTime("7/14/1997"),1,96.78m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10596,"WHITC",8,ToDateTime("7/11/1997"),ToDateTime("8/8/1997"),ToDateTime("8/12/1997"),1,16.34m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10597,"PICCO",7,ToDateTime("7/11/1997"),ToDateTime("8/8/1997"),ToDateTime("7/18/1997"),3,35.12m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(10598,"RATTC",1,ToDateTime("7/14/1997"),ToDateTime("8/11/1997"),ToDateTime("7/18/1997"),3,44.42m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10599,"BSBEV",6,ToDateTime("7/15/1997"),ToDateTime("8/26/1997"),ToDateTime("7/21/1997"),3,29.98m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(10600,"HUNGC",4,ToDateTime("7/16/1997"),ToDateTime("8/13/1997"),ToDateTime("7/21/1997"),1,45.13m, "Hungry Coyote Import Store","City Center Plaza 516 Main St.","Elgin", "OR","97827","USA"), + NorthwindFactory.Order(10601,"HILAA",7,ToDateTime("7/16/1997"),ToDateTime("8/27/1997"),ToDateTime("7/22/1997"),1,58.30m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10602,"VAFFE",8,ToDateTime("7/17/1997"),ToDateTime("8/14/1997"),ToDateTime("7/22/1997"),2,2.92m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10603,"SAVEA",8,ToDateTime("7/18/1997"),ToDateTime("8/15/1997"),ToDateTime("8/8/1997"),2,48.77m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10604,"FURIB",1,ToDateTime("7/18/1997"),ToDateTime("8/15/1997"),ToDateTime("7/29/1997"),1,7.46m, "Furia Bacalhau e Frutos do Mar","Jardim das rosas n. 32","Lisboa", null,"1675","Portugal"), + NorthwindFactory.Order(10605,"MEREP",1,ToDateTime("7/21/1997"),ToDateTime("8/18/1997"),ToDateTime("7/29/1997"),2,379.13m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10606,"TRADH",4,ToDateTime("7/22/1997"),ToDateTime("8/19/1997"),ToDateTime("7/31/1997"),3,79.40m, "Tradiao Hipermercados","Av. Ins de Castro, 414","Sao Paulo", "SP","05634-030","Brazil"), + NorthwindFactory.Order(10607,"SAVEA",5,ToDateTime("7/22/1997"),ToDateTime("8/19/1997"),ToDateTime("7/25/1997"),1,200.24m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10608,"TOMSP",4,ToDateTime("7/23/1997"),ToDateTime("8/20/1997"),ToDateTime("8/1/1997"),2,27.79m, "Toms Spezialitten","Luisenstr. 48","Mnster", null,"44087","Germany"), + NorthwindFactory.Order(10609,"DUMON",7,ToDateTime("7/24/1997"),ToDateTime("8/21/1997"),ToDateTime("7/30/1997"),2,1.85m, "Du monde entier","67, rue des Cinquante Otages","Nantes", null,"44000","France"), + NorthwindFactory.Order(10610,"LAMAI",8,ToDateTime("7/25/1997"),ToDateTime("8/22/1997"),ToDateTime("8/6/1997"),1,26.78m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10611,"WOLZA",6,ToDateTime("7/25/1997"),ToDateTime("8/22/1997"),ToDateTime("8/1/1997"),2,80.65m, "Wolski Zajazd","ul. Filtrowa 68","Warszawa", null,"01-012","Poland"), + NorthwindFactory.Order(10612,"SAVEA",1,ToDateTime("7/28/1997"),ToDateTime("8/25/1997"),ToDateTime("8/1/1997"),2,544.08m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10613,"HILAA",4,ToDateTime("7/29/1997"),ToDateTime("8/26/1997"),ToDateTime("8/1/1997"),2,8.11m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10614,"BLAUS",8,ToDateTime("7/29/1997"),ToDateTime("8/26/1997"),ToDateTime("8/1/1997"),3,1.93m, "Blauer See Delikatessen","Forsterstr. 57","Mannheim", null,"68306","Germany"), + NorthwindFactory.Order(10615,"WILMK",2,ToDateTime("7/30/1997"),ToDateTime("8/27/1997"),ToDateTime("8/6/1997"),3,0.75m, "Wilman Kala","Keskuskatu 45","Helsinki", null,"21240","Finland"), + NorthwindFactory.Order(10616,"GREAL",1,ToDateTime("7/31/1997"),ToDateTime("8/28/1997"),ToDateTime("8/5/1997"),2,116.53m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(10617,"GREAL",4,ToDateTime("7/31/1997"),ToDateTime("8/28/1997"),ToDateTime("8/4/1997"),2,18.53m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(10618,"MEREP",1,ToDateTime("8/1/1997"),ToDateTime("9/12/1997"),ToDateTime("8/8/1997"),1,154.68m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10619,"MEREP",3,ToDateTime("8/4/1997"),ToDateTime("9/1/1997"),ToDateTime("8/7/1997"),3,91.05m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10620,"LAUGB",2,ToDateTime("8/5/1997"),ToDateTime("9/2/1997"),ToDateTime("8/14/1997"),3,0.94m, "Laughing Bacchus Wine Cellars","2319 Elm St.","Vancouver", "BC","V3F 2K1","Canada"), + NorthwindFactory.Order(10621,"ISLAT",4,ToDateTime("8/5/1997"),ToDateTime("9/2/1997"),ToDateTime("8/11/1997"),2,23.73m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10622,"RICAR",4,ToDateTime("8/6/1997"),ToDateTime("9/3/1997"),ToDateTime("8/11/1997"),3,50.97m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10623,"FRANK",8,ToDateTime("8/7/1997"),ToDateTime("9/4/1997"),ToDateTime("8/12/1997"),2,97.18m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10624,"THECR",4,ToDateTime("8/7/1997"),ToDateTime("9/4/1997"),ToDateTime("8/19/1997"),2,94.80m, "The Cracker Box","55 Grizzly Peak Rd.","Butte", "MT","59801","USA"), + NorthwindFactory.Order(10625,"ANATR",3,ToDateTime("8/8/1997"),ToDateTime("9/5/1997"),ToDateTime("8/14/1997"),1,43.90m, "Ana Trujillo Emparedados y helados","Avda. de la Constitucin 2222","Mxico D.F.", null,"05021","Mexico"), + NorthwindFactory.Order(10626,"BERGS",1,ToDateTime("8/11/1997"),ToDateTime("9/8/1997"),ToDateTime("8/20/1997"),2,138.69m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10627,"SAVEA",8,ToDateTime("8/11/1997"),ToDateTime("9/22/1997"),ToDateTime("8/21/1997"),3,107.46m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10628,"BLONP",4,ToDateTime("8/12/1997"),ToDateTime("9/9/1997"),ToDateTime("8/20/1997"),3,30.36m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10629,"GODOS",4,ToDateTime("8/12/1997"),ToDateTime("9/9/1997"),ToDateTime("8/20/1997"),3,85.46m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(10630,"KOENE",1,ToDateTime("8/13/1997"),ToDateTime("9/10/1997"),ToDateTime("8/19/1997"),2,32.35m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10631,"LAMAI",8,ToDateTime("8/14/1997"),ToDateTime("9/11/1997"),ToDateTime("8/15/1997"),1,0.87m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10632,"WANDK",8,ToDateTime("8/14/1997"),ToDateTime("9/11/1997"),ToDateTime("8/19/1997"),1,41.38m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(10633,"ERNSH",7,ToDateTime("8/15/1997"),ToDateTime("9/12/1997"),ToDateTime("8/18/1997"),3,477.90m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10634,"FOLIG",4,ToDateTime("8/15/1997"),ToDateTime("9/12/1997"),ToDateTime("8/21/1997"),3,487.38m, "Folies gourmandes","184, chausse de Tournai","Lille", null,"59000","France"), + NorthwindFactory.Order(10635,"MAGAA",8,ToDateTime("8/18/1997"),ToDateTime("9/15/1997"),ToDateTime("8/21/1997"),3,47.46m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10636,"WARTH",4,ToDateTime("8/19/1997"),ToDateTime("9/16/1997"),ToDateTime("8/26/1997"),1,1.15m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10637,"QUEEN",6,ToDateTime("8/19/1997"),ToDateTime("9/16/1997"),ToDateTime("8/26/1997"),1,201.29m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10638,"LINOD",3,ToDateTime("8/20/1997"),ToDateTime("9/17/1997"),ToDateTime("9/1/1997"),1,158.44m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10639,"SANTG",7,ToDateTime("8/20/1997"),ToDateTime("9/17/1997"),ToDateTime("8/27/1997"),3,38.64m, "Sant Gourmet","Erling Skakkes gate 78","Stavern", null,"4110","Norway"), + NorthwindFactory.Order(10640,"WANDK",4,ToDateTime("8/21/1997"),ToDateTime("9/18/1997"),ToDateTime("8/28/1997"),1,23.55m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(10641,"HILAA",4,ToDateTime("8/22/1997"),ToDateTime("9/19/1997"),ToDateTime("8/26/1997"),2,179.61m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10642,"SIMOB",7,ToDateTime("8/22/1997"),ToDateTime("9/19/1997"),ToDateTime("9/5/1997"),3,41.89m, "Simons bistro","Vinbltet 34","Kobenhavn", null,"1734","Denmark"), + NorthwindFactory.Order(10643,"ALFKI",6,ToDateTime("8/25/1997"),ToDateTime("9/22/1997"),ToDateTime("9/2/1997"),1,29.46m, "Alfreds Futterkiste","Obere Str. 57","Berlin", null,"12209","Germany"), + NorthwindFactory.Order(10644,"WELLI",3,ToDateTime("8/25/1997"),ToDateTime("9/22/1997"),ToDateTime("9/1/1997"),2,0.14m, "Wellington Importadora","Rua do Mercado, 12","Resende", "SP","08737-363","Brazil"), + NorthwindFactory.Order(10645,"HANAR",4,ToDateTime("8/26/1997"),ToDateTime("9/23/1997"),ToDateTime("9/2/1997"),1,12.41m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10646,"HUNGO",9,ToDateTime("8/27/1997"),ToDateTime("10/8/1997"),ToDateTime("9/3/1997"),3,142.33m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10647,"QUEDE",4,ToDateTime("8/27/1997"),ToDateTime("9/10/1997"),ToDateTime("9/3/1997"),2,45.54m, "Que Delcia","Rua da Panificadora, 12","Rio de Janeiro", "RJ","02389-673","Brazil"), + NorthwindFactory.Order(10648,"RICAR",5,ToDateTime("8/28/1997"),ToDateTime("10/9/1997"),ToDateTime("9/9/1997"),2,14.25m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10649,"MAISD",5,ToDateTime("8/28/1997"),ToDateTime("9/25/1997"),ToDateTime("8/29/1997"),3,6.20m, "Maison Dewey","Rue Joseph-Bens 532","Bruxelles", null,"B-1180","Belgium"), + NorthwindFactory.Order(10650,"FAMIA",5,ToDateTime("8/29/1997"),ToDateTime("9/26/1997"),ToDateTime("9/3/1997"),3,176.81m, "Familia Arquibaldo","Rua Ors, 92","Sao Paulo", "SP","05442-030","Brazil"), + NorthwindFactory.Order(10651,"WANDK",8,ToDateTime("9/1/1997"),ToDateTime("9/29/1997"),ToDateTime("9/11/1997"),2,20.60m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(10652,"GOURL",4,ToDateTime("9/1/1997"),ToDateTime("9/29/1997"),ToDateTime("9/8/1997"),2,7.14m, "Gourmet Lanchonetes","Av. Brasil, 442","Campinas", "SP","04876-786","Brazil"), + NorthwindFactory.Order(10653,"FRANK",1,ToDateTime("9/2/1997"),ToDateTime("9/30/1997"),ToDateTime("9/19/1997"),1,93.25m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10654,"BERGS",5,ToDateTime("9/2/1997"),ToDateTime("9/30/1997"),ToDateTime("9/11/1997"),1,55.26m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10655,"REGGC",1,ToDateTime("9/3/1997"),ToDateTime("10/1/1997"),ToDateTime("9/11/1997"),2,4.41m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10656,"GREAL",6,ToDateTime("9/4/1997"),ToDateTime("10/2/1997"),ToDateTime("9/10/1997"),1,57.15m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(10657,"SAVEA",2,ToDateTime("9/4/1997"),ToDateTime("10/2/1997"),ToDateTime("9/15/1997"),2,352.69m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10658,"QUICK",4,ToDateTime("9/5/1997"),ToDateTime("10/3/1997"),ToDateTime("9/8/1997"),1,364.15m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10659,"QUEEN",7,ToDateTime("9/5/1997"),ToDateTime("10/3/1997"),ToDateTime("9/10/1997"),2,105.81m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10660,"HUNGC",8,ToDateTime("9/8/1997"),ToDateTime("10/6/1997"),ToDateTime("10/15/1997"),1,111.29m, "Hungry Coyote Import Store","City Center Plaza 516 Main St.","Elgin", "OR","97827","USA"), + NorthwindFactory.Order(10661,"HUNGO",7,ToDateTime("9/9/1997"),ToDateTime("10/7/1997"),ToDateTime("9/15/1997"),3,17.55m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10662,"LONEP",3,ToDateTime("9/9/1997"),ToDateTime("10/7/1997"),ToDateTime("9/18/1997"),2,1.28m, "Lonesome Pine Restaurant","89 Chiaroscuro Rd.","Portland", "OR","97219","USA"), + NorthwindFactory.Order(10663,"BONAP",2,ToDateTime("9/10/1997"),ToDateTime("9/24/1997"),ToDateTime("10/3/1997"),2,113.15m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10664,"FURIB",1,ToDateTime("9/10/1997"),ToDateTime("10/8/1997"),ToDateTime("9/19/1997"),3,1.27m, "Furia Bacalhau e Frutos do Mar","Jardim das rosas n. 32","Lisboa", null,"1675","Portugal"), + NorthwindFactory.Order(10665,"LONEP",1,ToDateTime("9/11/1997"),ToDateTime("10/9/1997"),ToDateTime("9/17/1997"),2,26.31m, "Lonesome Pine Restaurant","89 Chiaroscuro Rd.","Portland", "OR","97219","USA"), + NorthwindFactory.Order(10666,"RICSU",7,ToDateTime("9/12/1997"),ToDateTime("10/10/1997"),ToDateTime("9/22/1997"),2,232.42m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(10667,"ERNSH",7,ToDateTime("9/12/1997"),ToDateTime("10/10/1997"),ToDateTime("9/19/1997"),1,78.09m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10668,"WANDK",1,ToDateTime("9/15/1997"),ToDateTime("10/13/1997"),ToDateTime("9/23/1997"),2,47.22m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(10669,"SIMOB",2,ToDateTime("9/15/1997"),ToDateTime("10/13/1997"),ToDateTime("9/22/1997"),1,24.39m, "Simons bistro","Vinbltet 34","Kobenhavn", null,"1734","Denmark"), + NorthwindFactory.Order(10670,"FRANK",4,ToDateTime("9/16/1997"),ToDateTime("10/14/1997"),ToDateTime("9/18/1997"),1,203.48m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10671,"FRANR",1,ToDateTime("9/17/1997"),ToDateTime("10/15/1997"),ToDateTime("9/24/1997"),1,30.34m, "France restauration","54, rue Royale","Nantes", null,"44000","France"), + NorthwindFactory.Order(10672,"BERGS",9,ToDateTime("9/17/1997"),ToDateTime("10/1/1997"),ToDateTime("9/26/1997"),2,95.75m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10673,"WILMK",2,ToDateTime("9/18/1997"),ToDateTime("10/16/1997"),ToDateTime("9/19/1997"),1,22.76m, "Wilman Kala","Keskuskatu 45","Helsinki", null,"21240","Finland"), + NorthwindFactory.Order(10674,"ISLAT",4,ToDateTime("9/18/1997"),ToDateTime("10/16/1997"),ToDateTime("9/30/1997"),2,0.90m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10675,"FRANK",5,ToDateTime("9/19/1997"),ToDateTime("10/17/1997"),ToDateTime("9/23/1997"),2,31.85m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10676,"TORTU",2,ToDateTime("9/22/1997"),ToDateTime("10/20/1997"),ToDateTime("9/29/1997"),2,2.01m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10677,"ANTON",1,ToDateTime("9/22/1997"),ToDateTime("10/20/1997"),ToDateTime("9/26/1997"),3,4.03m, "Antonio Moreno Taquera","Mataderos 2312","Mxico D.F.", null,"05023","Mexico"), + NorthwindFactory.Order(10678,"SAVEA",7,ToDateTime("9/23/1997"),ToDateTime("10/21/1997"),ToDateTime("10/16/1997"),3,388.98m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10679,"BLONP",8,ToDateTime("9/23/1997"),ToDateTime("10/21/1997"),ToDateTime("9/30/1997"),3,27.94m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10680,"OLDWO",1,ToDateTime("9/24/1997"),ToDateTime("10/22/1997"),ToDateTime("9/26/1997"),1,26.61m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(10681,"GREAL",3,ToDateTime("9/25/1997"),ToDateTime("10/23/1997"),ToDateTime("9/30/1997"),3,76.13m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(10682,"ANTON",3,ToDateTime("9/25/1997"),ToDateTime("10/23/1997"),ToDateTime("10/1/1997"),2,36.13m, "Antonio Moreno Taquera","Mataderos 2312","Mxico D.F.", null,"05023","Mexico"), + NorthwindFactory.Order(10683,"DUMON",2,ToDateTime("9/26/1997"),ToDateTime("10/24/1997"),ToDateTime("10/1/1997"),1,4.40m, "Du monde entier","67, rue des Cinquante Otages","Nantes", null,"44000","France"), + NorthwindFactory.Order(10684,"OTTIK",3,ToDateTime("9/26/1997"),ToDateTime("10/24/1997"),ToDateTime("9/30/1997"),1,145.63m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(10685,"GOURL",4,ToDateTime("9/29/1997"),ToDateTime("10/13/1997"),ToDateTime("10/3/1997"),2,33.75m, "Gourmet Lanchonetes","Av. Brasil, 442","Campinas", "SP","04876-786","Brazil"), + NorthwindFactory.Order(10686,"PICCO",2,ToDateTime("9/30/1997"),ToDateTime("10/28/1997"),ToDateTime("10/8/1997"),1,96.50m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(10687,"HUNGO",9,ToDateTime("9/30/1997"),ToDateTime("10/28/1997"),ToDateTime("10/30/1997"),2,296.43m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10688,"VAFFE",4,ToDateTime("10/1/1997"),ToDateTime("10/15/1997"),ToDateTime("10/7/1997"),2,299.09m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10689,"BERGS",1,ToDateTime("10/1/1997"),ToDateTime("10/29/1997"),ToDateTime("10/7/1997"),2,13.42m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10690,"HANAR",1,ToDateTime("10/2/1997"),ToDateTime("10/30/1997"),ToDateTime("10/3/1997"),1,15.80m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10691,"QUICK",2,ToDateTime("10/3/1997"),ToDateTime("11/14/1997"),ToDateTime("10/22/1997"),2,810.05m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10692,"ALFKI",4,ToDateTime("10/3/1997"),ToDateTime("10/31/1997"),ToDateTime("10/13/1997"),2,61.02m, "Alfred's Futterkiste","Obere Str. 57","Berlin", null,"12209","Germany"), + NorthwindFactory.Order(10693,"WHITC",3,ToDateTime("10/6/1997"),ToDateTime("10/20/1997"),ToDateTime("10/10/1997"),3,139.34m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10694,"QUICK",8,ToDateTime("10/6/1997"),ToDateTime("11/3/1997"),ToDateTime("10/9/1997"),3,398.36m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10695,"WILMK",7,ToDateTime("10/7/1997"),ToDateTime("11/18/1997"),ToDateTime("10/14/1997"),1,16.72m, "Wilman Kala","Keskuskatu 45","Helsinki", null,"21240","Finland"), + NorthwindFactory.Order(10696,"WHITC",8,ToDateTime("10/8/1997"),ToDateTime("11/19/1997"),ToDateTime("10/14/1997"),3,102.55m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10697,"LINOD",3,ToDateTime("10/8/1997"),ToDateTime("11/5/1997"),ToDateTime("10/14/1997"),1,45.52m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10698,"ERNSH",4,ToDateTime("10/9/1997"),ToDateTime("11/6/1997"),ToDateTime("10/17/1997"),1,272.47m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10699,"MORGK",3,ToDateTime("10/9/1997"),ToDateTime("11/6/1997"),ToDateTime("10/13/1997"),3,0.58m, "Morgenstern Gesundkost","Heerstr. 22","Leipzig", null,"04179","Germany"), + NorthwindFactory.Order(10700,"SAVEA",3,ToDateTime("10/10/1997"),ToDateTime("11/7/1997"),ToDateTime("10/16/1997"),1,65.10m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10701,"HUNGO",6,ToDateTime("10/13/1997"),ToDateTime("10/27/1997"),ToDateTime("10/15/1997"),3,220.31m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10702,"ALFKI",4,ToDateTime("10/13/1997"),ToDateTime("11/24/1997"),ToDateTime("10/21/1997"),1,23.94m, "Alfred's Futterkiste","Obere Str. 57","Berlin", null,"12209","Germany"), + NorthwindFactory.Order(10703,"FOLKO",6,ToDateTime("10/14/1997"),ToDateTime("11/11/1997"),ToDateTime("10/20/1997"),2,152.30m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10704,"QUEEN",6,ToDateTime("10/14/1997"),ToDateTime("11/11/1997"),ToDateTime("11/7/1997"),1,4.78m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10705,"HILAA",9,ToDateTime("10/15/1997"),ToDateTime("11/12/1997"),ToDateTime("11/18/1997"),2,3.52m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10706,"OLDWO",8,ToDateTime("10/16/1997"),ToDateTime("11/13/1997"),ToDateTime("10/21/1997"),3,135.63m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(10707,"AROUT",4,ToDateTime("10/16/1997"),ToDateTime("10/30/1997"),ToDateTime("10/23/1997"),3,21.74m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10708,"THEBI",6,ToDateTime("10/17/1997"),ToDateTime("11/28/1997"),ToDateTime("11/5/1997"),2,2.96m, "The Big Cheese","89 Jefferson Way Suite 2","Portland", "OR","97201","USA"), + NorthwindFactory.Order(10709,"GOURL",1,ToDateTime("10/17/1997"),ToDateTime("11/14/1997"),ToDateTime("11/20/1997"),3,210.80m, "Gourmet Lanchonetes","Av. Brasil, 442","Campinas", "SP","04876-786","Brazil"), + NorthwindFactory.Order(10710,"FRANS",1,ToDateTime("10/20/1997"),ToDateTime("11/17/1997"),ToDateTime("10/23/1997"),1,4.98m, "Franchi S.p.A.","Via Monte Bianco 34","Torino", null,"10100","Italy"), + NorthwindFactory.Order(10711,"SAVEA",5,ToDateTime("10/21/1997"),ToDateTime("12/2/1997"),ToDateTime("10/29/1997"),2,52.41m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10712,"HUNGO",3,ToDateTime("10/21/1997"),ToDateTime("11/18/1997"),ToDateTime("10/31/1997"),1,89.93m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10713,"SAVEA",1,ToDateTime("10/22/1997"),ToDateTime("11/19/1997"),ToDateTime("10/24/1997"),1,167.05m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10714,"SAVEA",5,ToDateTime("10/22/1997"),ToDateTime("11/19/1997"),ToDateTime("10/27/1997"),3,24.49m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10715,"BONAP",3,ToDateTime("10/23/1997"),ToDateTime("11/6/1997"),ToDateTime("10/29/1997"),1,63.20m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10716,"RANCH",4,ToDateTime("10/24/1997"),ToDateTime("11/21/1997"),ToDateTime("10/27/1997"),2,22.57m, "Rancho grande","Av. del Libertador 900","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10717,"FRANK",1,ToDateTime("10/24/1997"),ToDateTime("11/21/1997"),ToDateTime("10/29/1997"),2,59.25m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10718,"KOENE",1,ToDateTime("10/27/1997"),ToDateTime("11/24/1997"),ToDateTime("10/29/1997"),3,170.88m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10719,"LETSS",8,ToDateTime("10/27/1997"),ToDateTime("11/24/1997"),ToDateTime("11/5/1997"),2,51.44m, "Let's Stop N Shop","87 Polk St. Suite 5","San Francisco", "CA","94117","USA"), + NorthwindFactory.Order(10720,"QUEDE",8,ToDateTime("10/28/1997"),ToDateTime("11/11/1997"),ToDateTime("11/5/1997"),2,9.53m, "Que Delcia","Rua da Panificadora, 12","Rio de Janeiro", "RJ","02389-673","Brazil"), + NorthwindFactory.Order(10721,"QUICK",5,ToDateTime("10/29/1997"),ToDateTime("11/26/1997"),ToDateTime("10/31/1997"),3,48.92m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10722,"SAVEA",8,ToDateTime("10/29/1997"),ToDateTime("12/10/1997"),ToDateTime("11/4/1997"),1,74.58m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10723,"WHITC",3,ToDateTime("10/30/1997"),ToDateTime("11/27/1997"),ToDateTime("11/25/1997"),1,21.72m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10724,"MEREP",8,ToDateTime("10/30/1997"),ToDateTime("12/11/1997"),ToDateTime("11/5/1997"),2,57.75m, "Mre Paillarde","43 rue St. Laurent","Montral", "Qubec","H1J 1C3","Canada"), + NorthwindFactory.Order(10725,"FAMIA",4,ToDateTime("10/31/1997"),ToDateTime("11/28/1997"),ToDateTime("11/5/1997"),3,10.83m, "Familia Arquibaldo","Rua Ors, 92","Sao Paulo", "SP","05442-030","Brazil"), + NorthwindFactory.Order(10726,"EASTC",4,ToDateTime("11/3/1997"),ToDateTime("11/17/1997"),ToDateTime("12/5/1997"),1,16.56m, "Eastern Connection","35 King George","London", null,"WX3 6FW","UK"), + NorthwindFactory.Order(10727,"REGGC",2,ToDateTime("11/3/1997"),ToDateTime("12/1/1997"),ToDateTime("12/5/1997"),1,89.90m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10728,"QUEEN",4,ToDateTime("11/4/1997"),ToDateTime("12/2/1997"),ToDateTime("11/11/1997"),2,58.33m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10729,"LINOD",8,ToDateTime("11/4/1997"),ToDateTime("12/16/1997"),ToDateTime("11/14/1997"),3,141.06m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10730,"BONAP",5,ToDateTime("11/5/1997"),ToDateTime("12/3/1997"),ToDateTime("11/14/1997"),1,20.12m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10731,"CHOPS",7,ToDateTime("11/6/1997"),ToDateTime("12/4/1997"),ToDateTime("11/14/1997"),1,96.65m, "Chop-suey Chinese","Hauptstr. 31","Bern", null,"3012","Switzerland"), + NorthwindFactory.Order(10732,"BONAP",3,ToDateTime("11/6/1997"),ToDateTime("12/4/1997"),ToDateTime("11/7/1997"),1,16.97m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10733,"BERGS",1,ToDateTime("11/7/1997"),ToDateTime("12/5/1997"),ToDateTime("11/10/1997"),3,110.11m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10734,"GOURL",2,ToDateTime("11/7/1997"),ToDateTime("12/5/1997"),ToDateTime("11/12/1997"),3,1.63m, "Gourmet Lanchonetes","Av. Brasil, 442","Campinas", "SP","04876-786","Brazil"), + NorthwindFactory.Order(10735,"LETSS",6,ToDateTime("11/10/1997"),ToDateTime("12/8/1997"),ToDateTime("11/21/1997"),2,45.97m, "Let's Stop N Shop","87 Polk St. Suite 5","San Francisco", "CA","94117","USA"), + NorthwindFactory.Order(10736,"HUNGO",9,ToDateTime("11/11/1997"),ToDateTime("12/9/1997"),ToDateTime("11/21/1997"),2,44.10m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10737,"VINET",2,ToDateTime("11/11/1997"),ToDateTime("12/9/1997"),ToDateTime("11/18/1997"),2,7.79m, "Vins et alcools Chevalier","59 rue de l'Abbaye","Reims", null,"51100","France"), + NorthwindFactory.Order(10738,"SPECD",2,ToDateTime("11/12/1997"),ToDateTime("12/10/1997"),ToDateTime("11/18/1997"),1,2.91m, "Spcialits du monde","25, rue Lauriston","Paris", null,"75016","France"), + NorthwindFactory.Order(10739,"VINET",3,ToDateTime("11/12/1997"),ToDateTime("12/10/1997"),ToDateTime("11/17/1997"),3,11.08m, "Vins et alcools Chevalier","59 rue de l'Abbaye","Reims", null,"51100","France"), + NorthwindFactory.Order(10740,"WHITC",4,ToDateTime("11/13/1997"),ToDateTime("12/11/1997"),ToDateTime("11/25/1997"),2,81.88m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10741,"AROUT",4,ToDateTime("11/14/1997"),ToDateTime("11/28/1997"),ToDateTime("11/18/1997"),3,10.96m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10742,"BOTTM",3,ToDateTime("11/14/1997"),ToDateTime("12/12/1997"),ToDateTime("11/18/1997"),3,243.73m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10743,"AROUT",1,ToDateTime("11/17/1997"),ToDateTime("12/15/1997"),ToDateTime("11/21/1997"),2,23.72m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10744,"VAFFE",6,ToDateTime("11/17/1997"),ToDateTime("12/15/1997"),ToDateTime("11/24/1997"),1,69.19m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10745,"QUICK",9,ToDateTime("11/18/1997"),ToDateTime("12/16/1997"),ToDateTime("11/27/1997"),1,3.52m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10746,"CHOPS",1,ToDateTime("11/19/1997"),ToDateTime("12/17/1997"),ToDateTime("11/21/1997"),3,31.43m, "Chop-suey Chinese","Hauptstr. 31","Bern", null,"3012","Switzerland"), + NorthwindFactory.Order(10747,"PICCO",6,ToDateTime("11/19/1997"),ToDateTime("12/17/1997"),ToDateTime("11/26/1997"),1,117.33m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(10748,"SAVEA",3,ToDateTime("11/20/1997"),ToDateTime("12/18/1997"),ToDateTime("11/28/1997"),1,232.55m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10749,"ISLAT",4,ToDateTime("11/20/1997"),ToDateTime("12/18/1997"),ToDateTime("12/19/1997"),2,61.53m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10750,"WARTH",9,ToDateTime("11/21/1997"),ToDateTime("12/19/1997"),ToDateTime("11/24/1997"),1,79.30m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10751,"RICSU",3,ToDateTime("11/24/1997"),ToDateTime("12/22/1997"),ToDateTime("12/3/1997"),3,130.79m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(10752,"NORTS",2,ToDateTime("11/24/1997"),ToDateTime("12/22/1997"),ToDateTime("11/28/1997"),3,1.39m, "North/South","South House 300 Queensbridge","London", null,"SW7 1RZ","UK"), + NorthwindFactory.Order(10753,"FRANS",3,ToDateTime("11/25/1997"),ToDateTime("12/23/1997"),ToDateTime("11/27/1997"),1,7.70m, "Franchi S.p.A.","Via Monte Bianco 34","Torino", null,"10100","Italy"), + NorthwindFactory.Order(10754,"MAGAA",6,ToDateTime("11/25/1997"),ToDateTime("12/23/1997"),ToDateTime("11/27/1997"),3,2.38m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10755,"BONAP",4,ToDateTime("11/26/1997"),ToDateTime("12/24/1997"),ToDateTime("11/28/1997"),2,16.71m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10756,"SPLIR",8,ToDateTime("11/27/1997"),ToDateTime("12/25/1997"),ToDateTime("12/2/1997"),2,73.21m, "Split Rail Beer & Ale","P.O. Box 555","Lander", "WY","82520","USA"), + NorthwindFactory.Order(10757,"SAVEA",6,ToDateTime("11/27/1997"),ToDateTime("12/25/1997"),ToDateTime("12/15/1997"),1,8.19m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10758,"RICSU",3,ToDateTime("11/28/1997"),ToDateTime("12/26/1997"),ToDateTime("12/4/1997"),3,138.17m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(10759,"ANATR",3,ToDateTime("11/28/1997"),ToDateTime("12/26/1997"),ToDateTime("12/12/1997"),3,11.99m, "Ana Trujillo Emparedados y helados","Avda. de la Constitucin 2222","Mxico D.F.", null,"05021","Mexico"), + NorthwindFactory.Order(10760,"MAISD",4,ToDateTime("12/1/1997"),ToDateTime("12/29/1997"),ToDateTime("12/10/1997"),1,155.64m, "Maison Dewey","Rue Joseph-Bens 532","Bruxelles", null,"B-1180","Belgium"), + NorthwindFactory.Order(10761,"RATTC",5,ToDateTime("12/2/1997"),ToDateTime("12/30/1997"),ToDateTime("12/8/1997"),2,18.66m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10762,"FOLKO",3,ToDateTime("12/2/1997"),ToDateTime("12/30/1997"),ToDateTime("12/9/1997"),1,328.74m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10763,"FOLIG",3,ToDateTime("12/3/1997"),ToDateTime("12/31/1997"),ToDateTime("12/8/1997"),3,37.35m, "Folies gourmandes","184, chausse de Tournai","Lille", null,"59000","France"), + NorthwindFactory.Order(10764,"ERNSH",6,ToDateTime("12/3/1997"),ToDateTime("12/31/1997"),ToDateTime("12/8/1997"),3,145.45m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10765,"QUICK",3,ToDateTime("12/4/1997"),ToDateTime("1/1/1998"),ToDateTime("12/9/1997"),3,42.74m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10766,"OTTIK",4,ToDateTime("12/5/1997"),ToDateTime("1/2/1998"),ToDateTime("12/9/1997"),1,157.55m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(10767,"SUPRD",4,ToDateTime("12/5/1997"),ToDateTime("1/2/1998"),ToDateTime("12/15/1997"),3,1.59m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10768,"AROUT",3,ToDateTime("12/8/1997"),ToDateTime("1/5/1998"),ToDateTime("12/15/1997"),2,146.32m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10769,"VAFFE",3,ToDateTime("12/8/1997"),ToDateTime("1/5/1998"),ToDateTime("12/12/1997"),1,65.06m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10770,"HANAR",8,ToDateTime("12/9/1997"),ToDateTime("1/6/1998"),ToDateTime("12/17/1997"),3,5.32m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10771,"ERNSH",9,ToDateTime("12/10/1997"),ToDateTime("1/7/1998"),ToDateTime("1/2/1998"),2,11.19m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10772,"LEHMS",3,ToDateTime("12/10/1997"),ToDateTime("1/7/1998"),ToDateTime("12/19/1997"),2,91.28m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10773,"ERNSH",1,ToDateTime("12/11/1997"),ToDateTime("1/8/1998"),ToDateTime("12/16/1997"),3,96.43m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10774,"FOLKO",4,ToDateTime("12/11/1997"),ToDateTime("12/25/1997"),ToDateTime("12/12/1997"),1,48.20m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10775,"THECR",7,ToDateTime("12/12/1997"),ToDateTime("1/9/1998"),ToDateTime("12/26/1997"),1,20.25m, "The Cracker Box","55 Grizzly Peak Rd.","Butte", "MT","59801","USA"), + NorthwindFactory.Order(10776,"ERNSH",1,ToDateTime("12/15/1997"),ToDateTime("1/12/1998"),ToDateTime("12/18/1997"),3,351.53m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10777,"GOURL",7,ToDateTime("12/15/1997"),ToDateTime("12/29/1997"),ToDateTime("1/21/1998"),2,3.01m, "Gourmet Lanchonetes","Av. Brasil, 442","Campinas", "SP","04876-786","Brazil"), + NorthwindFactory.Order(10778,"BERGS",3,ToDateTime("12/16/1997"),ToDateTime("1/13/1998"),ToDateTime("12/24/1997"),1,6.79m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10779,"MORGK",3,ToDateTime("12/16/1997"),ToDateTime("1/13/1998"),ToDateTime("1/14/1998"),2,58.13m, "Morgenstern Gesundkost","Heerstr. 22","Leipzig", null,"04179","Germany"), + NorthwindFactory.Order(10780,"LILAS",2,ToDateTime("12/16/1997"),ToDateTime("12/30/1997"),ToDateTime("12/25/1997"),1,42.13m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10781,"WARTH",2,ToDateTime("12/17/1997"),ToDateTime("1/14/1998"),ToDateTime("12/19/1997"),3,73.16m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(10782,"CACTU",9,ToDateTime("12/17/1997"),ToDateTime("1/14/1998"),ToDateTime("12/22/1997"),3,1.10m, "Cactus Comidas para llevar","Cerrito 333","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10783,"HANAR",4,ToDateTime("12/18/1997"),ToDateTime("1/15/1998"),ToDateTime("12/19/1997"),2,124.98m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10784,"MAGAA",4,ToDateTime("12/18/1997"),ToDateTime("1/15/1998"),ToDateTime("12/22/1997"),3,70.09m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10785,"GROSR",1,ToDateTime("12/18/1997"),ToDateTime("1/15/1998"),ToDateTime("12/24/1997"),3,1.51m, "GROSELLA-Restaurante","5 Ave. Los Palos Grandes","Caracas", "DF","1081","Venezuela"), + NorthwindFactory.Order(10786,"QUEEN",8,ToDateTime("12/19/1997"),ToDateTime("1/16/1998"),ToDateTime("12/23/1997"),1,110.87m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10787,"LAMAI",2,ToDateTime("12/19/1997"),ToDateTime("1/2/1998"),ToDateTime("12/26/1997"),1,249.93m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10788,"QUICK",1,ToDateTime("12/22/1997"),ToDateTime("1/19/1998"),ToDateTime("1/19/1998"),2,42.70m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10789,"FOLIG",1,ToDateTime("12/22/1997"),ToDateTime("1/19/1998"),ToDateTime("12/31/1997"),2,100.60m, "Folies gourmandes","184, chausse de Tournai","Lille", null,"59000","France"), + NorthwindFactory.Order(10790,"GOURL",6,ToDateTime("12/22/1997"),ToDateTime("1/19/1998"),ToDateTime("12/26/1997"),1,28.23m, "Gourmet Lanchonetes","Av. Brasil, 442","Campinas", "SP","04876-786","Brazil"), + NorthwindFactory.Order(10791,"FRANK",6,ToDateTime("12/23/1997"),ToDateTime("1/20/1998"),ToDateTime("1/1/1998"),2,16.85m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10792,"WOLZA",1,ToDateTime("12/23/1997"),ToDateTime("1/20/1998"),ToDateTime("12/31/1997"),3,23.79m, "Wolski Zajazd","ul. Filtrowa 68","Warszawa", null,"01-012","Poland"), + NorthwindFactory.Order(10793,"AROUT",3,ToDateTime("12/24/1997"),ToDateTime("1/21/1998"),ToDateTime("1/8/1998"),3,4.52m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10794,"QUEDE",6,ToDateTime("12/24/1997"),ToDateTime("1/21/1998"),ToDateTime("1/2/1998"),1,21.49m, "Que Delcia","Rua da Panificadora, 12","Rio de Janeiro", "RJ","02389-673","Brazil"), + NorthwindFactory.Order(10795,"ERNSH",8,ToDateTime("12/24/1997"),ToDateTime("1/21/1998"),ToDateTime("1/20/1998"),2,126.66m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10796,"HILAA",3,ToDateTime("12/25/1997"),ToDateTime("1/22/1998"),ToDateTime("1/14/1998"),1,26.52m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10797,"DRACD",7,ToDateTime("12/25/1997"),ToDateTime("1/22/1998"),ToDateTime("1/5/1998"),2,33.35m, "Drachenblut Delikatessen","Walserweg 21","Aachen", null,"52066","Germany"), + NorthwindFactory.Order(10798,"ISLAT",2,ToDateTime("12/26/1997"),ToDateTime("1/23/1998"),ToDateTime("1/5/1998"),1,2.33m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10799,"KOENE",9,ToDateTime("12/26/1997"),ToDateTime("2/6/1998"),ToDateTime("1/5/1998"),3,30.76m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10800,"SEVES",1,ToDateTime("12/26/1997"),ToDateTime("1/23/1998"),ToDateTime("1/5/1998"),3,137.44m, "Seven Seas Imports","90 Wadhurst Rd.","London", null,"OX15 4NB","UK"), + NorthwindFactory.Order(10801,"BOLID",4,ToDateTime("12/29/1997"),ToDateTime("1/26/1998"),ToDateTime("12/31/1997"),2,97.09m, "Blido Comidas preparadas","C/ Araquil, 67","Madrid", null,"28023","Spain"), + NorthwindFactory.Order(10802,"SIMOB",4,ToDateTime("12/29/1997"),ToDateTime("1/26/1998"),ToDateTime("1/2/1998"),2,257.26m, "Simons bistro","Vinbltet 34","Kobenhavn", null,"1734","Denmark"), + NorthwindFactory.Order(10803,"WELLI",4,ToDateTime("12/30/1997"),ToDateTime("1/27/1998"),ToDateTime("1/6/1998"),1,55.23m, "Wellington Importadora","Rua do Mercado, 12","Resende", "SP","08737-363","Brazil"), + NorthwindFactory.Order(10804,"SEVES",6,ToDateTime("12/30/1997"),ToDateTime("1/27/1998"),ToDateTime("1/7/1998"),2,27.33m, "Seven Seas Imports","90 Wadhurst Rd.","London", null,"OX15 4NB","UK"), + NorthwindFactory.Order(10805,"THEBI",2,ToDateTime("12/30/1997"),ToDateTime("1/27/1998"),ToDateTime("1/9/1998"),3,237.34m, "The Big Cheese","89 Jefferson Way Suite 2","Portland", "OR","97201","USA"), + NorthwindFactory.Order(10806,"VICTE",3,ToDateTime("12/31/1997"),ToDateTime("1/28/1998"),ToDateTime("1/5/1998"),2,22.11m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10807,"FRANS",4,ToDateTime("12/31/1997"),ToDateTime("1/28/1998"),ToDateTime("1/30/1998"),1,1.36m, "Franchi S.p.A.","Via Monte Bianco 34","Torino", null,"10100","Italy"), + NorthwindFactory.Order(10808,"OLDWO",2,ToDateTime("1/1/1998"),ToDateTime("1/29/1998"),ToDateTime("1/9/1998"),3,45.53m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(10809,"WELLI",7,ToDateTime("1/1/1998"),ToDateTime("1/29/1998"),ToDateTime("1/7/1998"),1,4.87m, "Wellington Importadora","Rua do Mercado, 12","Resende", "SP","08737-363","Brazil"), + NorthwindFactory.Order(10810,"LAUGB",2,ToDateTime("1/1/1998"),ToDateTime("1/29/1998"),ToDateTime("1/7/1998"),3,4.33m, "Laughing Bacchus Wine Cellars","2319 Elm St.","Vancouver", "BC","V3F 2K1","Canada"), + NorthwindFactory.Order(10811,"LINOD",8,ToDateTime("1/2/1998"),ToDateTime("1/30/1998"),ToDateTime("1/8/1998"),1,31.22m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10812,"REGGC",5,ToDateTime("1/2/1998"),ToDateTime("1/30/1998"),ToDateTime("1/12/1998"),1,59.78m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10813,"RICAR",1,ToDateTime("1/5/1998"),ToDateTime("2/2/1998"),ToDateTime("1/9/1998"),1,47.38m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10814,"VICTE",3,ToDateTime("1/5/1998"),ToDateTime("2/2/1998"),ToDateTime("1/14/1998"),3,130.94m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10815,"SAVEA",2,ToDateTime("1/5/1998"),ToDateTime("2/2/1998"),ToDateTime("1/14/1998"),3,14.62m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10816,"GREAL",4,ToDateTime("1/6/1998"),ToDateTime("2/3/1998"),ToDateTime("2/4/1998"),2,719.78m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(10817,"KOENE",3,ToDateTime("1/6/1998"),ToDateTime("1/20/1998"),ToDateTime("1/13/1998"),2,306.07m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10818,"MAGAA",7,ToDateTime("1/7/1998"),ToDateTime("2/4/1998"),ToDateTime("1/12/1998"),3,65.48m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10819,"CACTU",2,ToDateTime("1/7/1998"),ToDateTime("2/4/1998"),ToDateTime("1/16/1998"),3,19.76m, "Cactus Comidas para llevar","Cerrito 333","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10820,"RATTC",3,ToDateTime("1/7/1998"),ToDateTime("2/4/1998"),ToDateTime("1/13/1998"),2,37.52m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10821,"SPLIR",1,ToDateTime("1/8/1998"),ToDateTime("2/5/1998"),ToDateTime("1/15/1998"),1,36.68m, "Split Rail Beer & Ale","P.O. Box 555","Lander", "WY","82520","USA"), + NorthwindFactory.Order(10822,"TRAIH",6,ToDateTime("1/8/1998"),ToDateTime("2/5/1998"),ToDateTime("1/16/1998"),3,7.00m, "Trail's Head Gourmet Provisioners","722 DaVinci Blvd.","Kirkland", "WA","98034","USA"), + NorthwindFactory.Order(10823,"LILAS",5,ToDateTime("1/9/1998"),ToDateTime("2/6/1998"),ToDateTime("1/13/1998"),2,163.97m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10824,"FOLKO",8,ToDateTime("1/9/1998"),ToDateTime("2/6/1998"),ToDateTime("1/30/1998"),1,1.23m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10825,"DRACD",1,ToDateTime("1/9/1998"),ToDateTime("2/6/1998"),ToDateTime("1/14/1998"),1,79.25m, "Drachenblut Delikatessen","Walserweg 21","Aachen", null,"52066","Germany"), + NorthwindFactory.Order(10826,"BLONP",6,ToDateTime("1/12/1998"),ToDateTime("2/9/1998"),ToDateTime("2/6/1998"),1,7.09m, "Blondel pre et fils","24, place Klber","Strasbourg", null,"67000","France"), + NorthwindFactory.Order(10827,"BONAP",1,ToDateTime("1/12/1998"),ToDateTime("1/26/1998"),ToDateTime("2/6/1998"),2,63.54m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10828,"RANCH",9,ToDateTime("1/13/1998"),ToDateTime("1/27/1998"),ToDateTime("2/4/1998"),1,90.85m, "Rancho grande","Av. del Libertador 900","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10829,"ISLAT",9,ToDateTime("1/13/1998"),ToDateTime("2/10/1998"),ToDateTime("1/23/1998"),1,154.72m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10830,"TRADH",4,ToDateTime("1/13/1998"),ToDateTime("2/24/1998"),ToDateTime("1/21/1998"),2,81.83m, "Tradiao Hipermercados","Av. Ins de Castro, 414","Sao Paulo", "SP","05634-030","Brazil"), + NorthwindFactory.Order(10831,"SANTG",3,ToDateTime("1/14/1998"),ToDateTime("2/11/1998"),ToDateTime("1/23/1998"),2,72.19m, "Sant Gourmet","Erling Skakkes gate 78","Stavern", null,"4110","Norway"), + NorthwindFactory.Order(10832,"LAMAI",2,ToDateTime("1/14/1998"),ToDateTime("2/11/1998"),ToDateTime("1/19/1998"),2,43.26m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10833,"OTTIK",6,ToDateTime("1/15/1998"),ToDateTime("2/12/1998"),ToDateTime("1/23/1998"),2,71.49m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(10834,"TRADH",1,ToDateTime("1/15/1998"),ToDateTime("2/12/1998"),ToDateTime("1/19/1998"),3,29.78m, "Tradiao Hipermercados","Av. Ins de Castro, 414","Sao Paulo", "SP","05634-030","Brazil"), + NorthwindFactory.Order(10835,"ALFKI",1,ToDateTime("1/15/1998"),ToDateTime("2/12/1998"),ToDateTime("1/21/1998"),3,69.53m, "Alfred's Futterkiste","Obere Str. 57","Berlin", null,"12209","Germany"), + NorthwindFactory.Order(10836,"ERNSH",7,ToDateTime("1/16/1998"),ToDateTime("2/13/1998"),ToDateTime("1/21/1998"),1,411.88m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10837,"BERGS",9,ToDateTime("1/16/1998"),ToDateTime("2/13/1998"),ToDateTime("1/23/1998"),3,13.32m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10838,"LINOD",3,ToDateTime("1/19/1998"),ToDateTime("2/16/1998"),ToDateTime("1/23/1998"),3,59.28m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10839,"TRADH",3,ToDateTime("1/19/1998"),ToDateTime("2/16/1998"),ToDateTime("1/22/1998"),3,35.43m, "Tradiao Hipermercados","Av. Ins de Castro, 414","Sao Paulo", "SP","05634-030","Brazil"), + NorthwindFactory.Order(10840,"LINOD",4,ToDateTime("1/19/1998"),ToDateTime("3/2/1998"),ToDateTime("2/16/1998"),2,2.71m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10841,"SUPRD",5,ToDateTime("1/20/1998"),ToDateTime("2/17/1998"),ToDateTime("1/29/1998"),2,424.30m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10842,"TORTU",1,ToDateTime("1/20/1998"),ToDateTime("2/17/1998"),ToDateTime("1/29/1998"),3,54.42m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10843,"VICTE",4,ToDateTime("1/21/1998"),ToDateTime("2/18/1998"),ToDateTime("1/26/1998"),2,9.26m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10844,"PICCO",8,ToDateTime("1/21/1998"),ToDateTime("2/18/1998"),ToDateTime("1/26/1998"),2,25.22m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(10845,"QUICK",8,ToDateTime("1/21/1998"),ToDateTime("2/4/1998"),ToDateTime("1/30/1998"),1,212.98m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10846,"SUPRD",2,ToDateTime("1/22/1998"),ToDateTime("3/5/1998"),ToDateTime("1/23/1998"),3,56.46m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10847,"SAVEA",4,ToDateTime("1/22/1998"),ToDateTime("2/5/1998"),ToDateTime("2/10/1998"),3,487.57m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10848,"CONSH",7,ToDateTime("1/23/1998"),ToDateTime("2/20/1998"),ToDateTime("1/29/1998"),2,38.24m, "Consolidated Holdings","Berkeley Gardens 12 Brewery","London", null,"WX1 6LT","UK"), + NorthwindFactory.Order(10849,"KOENE",9,ToDateTime("1/23/1998"),ToDateTime("2/20/1998"),ToDateTime("1/30/1998"),2,0.56m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10850,"VICTE",1,ToDateTime("1/23/1998"),ToDateTime("3/6/1998"),ToDateTime("1/30/1998"),1,49.19m, "Victuailles en stock","2, rue du Commerce","Lyon", null,"69004","France"), + NorthwindFactory.Order(10851,"RICAR",5,ToDateTime("1/26/1998"),ToDateTime("2/23/1998"),ToDateTime("2/2/1998"),1,160.55m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10852,"RATTC",8,ToDateTime("1/26/1998"),ToDateTime("2/9/1998"),ToDateTime("1/30/1998"),1,174.05m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10853,"BLAUS",9,ToDateTime("1/27/1998"),ToDateTime("2/24/1998"),ToDateTime("2/3/1998"),2,53.83m, "Blauer See Delikatessen","Forsterstr. 57","Mannheim", null,"68306","Germany"), + NorthwindFactory.Order(10854,"ERNSH",3,ToDateTime("1/27/1998"),ToDateTime("2/24/1998"),ToDateTime("2/5/1998"),2,100.22m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10855,"OLDWO",3,ToDateTime("1/27/1998"),ToDateTime("2/24/1998"),ToDateTime("2/4/1998"),1,170.97m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(10856,"ANTON",3,ToDateTime("1/28/1998"),ToDateTime("2/25/1998"),ToDateTime("2/10/1998"),2,58.43m, "Antonio Moreno Taquera","Mataderos 2312","Mxico D.F.", null,"05023","Mexico"), + NorthwindFactory.Order(10857,"BERGS",8,ToDateTime("1/28/1998"),ToDateTime("2/25/1998"),ToDateTime("2/6/1998"),2,188.85m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10858,"LACOR",2,ToDateTime("1/29/1998"),ToDateTime("2/26/1998"),ToDateTime("2/3/1998"),1,52.51m, "La corne d'abondance","67, avenue de l'Europe","Versailles", null,"78000","France"), + NorthwindFactory.Order(10859,"FRANK",1,ToDateTime("1/29/1998"),ToDateTime("2/26/1998"),ToDateTime("2/2/1998"),2,76.10m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10860,"FRANR",3,ToDateTime("1/29/1998"),ToDateTime("2/26/1998"),ToDateTime("2/4/1998"),3,19.26m, "France restauration","54, rue Royale","Nantes", null,"44000","France"), + NorthwindFactory.Order(10861,"WHITC",4,ToDateTime("1/30/1998"),ToDateTime("2/27/1998"),ToDateTime("2/17/1998"),2,14.93m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10862,"LEHMS",8,ToDateTime("1/30/1998"),ToDateTime("3/13/1998"),ToDateTime("2/2/1998"),2,53.23m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10863,"HILAA",4,ToDateTime("2/2/1998"),ToDateTime("3/2/1998"),ToDateTime("2/17/1998"),2,30.26m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10864,"AROUT",4,ToDateTime("2/2/1998"),ToDateTime("3/2/1998"),ToDateTime("2/9/1998"),2,3.04m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10865,"QUICK",2,ToDateTime("2/2/1998"),ToDateTime("2/16/1998"),ToDateTime("2/12/1998"),1,348.14m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10866,"BERGS",5,ToDateTime("2/3/1998"),ToDateTime("3/3/1998"),ToDateTime("2/12/1998"),1,109.11m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10867,"LONEP",6,ToDateTime("2/3/1998"),ToDateTime("3/17/1998"),ToDateTime("2/11/1998"),1,1.93m, "Lonesome Pine Restaurant","89 Chiaroscuro Rd.","Portland", "OR","97219","USA"), + NorthwindFactory.Order(10868,"QUEEN",7,ToDateTime("2/4/1998"),ToDateTime("3/4/1998"),ToDateTime("2/23/1998"),2,191.27m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10869,"SEVES",5,ToDateTime("2/4/1998"),ToDateTime("3/4/1998"),ToDateTime("2/9/1998"),1,143.28m, "Seven Seas Imports","90 Wadhurst Rd.","London", null,"OX15 4NB","UK"), + NorthwindFactory.Order(10870,"WOLZA",5,ToDateTime("2/4/1998"),ToDateTime("3/4/1998"),ToDateTime("2/13/1998"),3,12.04m, "Wolski Zajazd","ul. Filtrowa 68","Warszawa", null,"01-012","Poland"), + NorthwindFactory.Order(10871,"BONAP",9,ToDateTime("2/5/1998"),ToDateTime("3/5/1998"),ToDateTime("2/10/1998"),2,112.27m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10872,"GODOS",5,ToDateTime("2/5/1998"),ToDateTime("3/5/1998"),ToDateTime("2/9/1998"),2,175.32m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(10873,"WILMK",4,ToDateTime("2/6/1998"),ToDateTime("3/6/1998"),ToDateTime("2/9/1998"),1,0.82m, "Wilman Kala","Keskuskatu 45","Helsinki", null,"21240","Finland"), + NorthwindFactory.Order(10874,"GODOS",5,ToDateTime("2/6/1998"),ToDateTime("3/6/1998"),ToDateTime("2/11/1998"),2,19.58m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(10875,"BERGS",4,ToDateTime("2/6/1998"),ToDateTime("3/6/1998"),ToDateTime("3/3/1998"),2,32.37m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10876,"BONAP",7,ToDateTime("2/9/1998"),ToDateTime("3/9/1998"),ToDateTime("2/12/1998"),3,60.42m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10877,"RICAR",1,ToDateTime("2/9/1998"),ToDateTime("3/9/1998"),ToDateTime("2/19/1998"),1,38.06m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(10878,"QUICK",4,ToDateTime("2/10/1998"),ToDateTime("3/10/1998"),ToDateTime("2/12/1998"),1,46.69m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10879,"WILMK",3,ToDateTime("2/10/1998"),ToDateTime("3/10/1998"),ToDateTime("2/12/1998"),3,8.50m, "Wilman Kala","Keskuskatu 45","Helsinki", null,"21240","Finland"), + NorthwindFactory.Order(10880,"FOLKO",7,ToDateTime("2/10/1998"),ToDateTime("3/24/1998"),ToDateTime("2/18/1998"),1,88.01m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10881,"CACTU",4,ToDateTime("2/11/1998"),ToDateTime("3/11/1998"),ToDateTime("2/18/1998"),1,2.84m, "Cactus Comidas para llevar","Cerrito 333","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10882,"SAVEA",4,ToDateTime("2/11/1998"),ToDateTime("3/11/1998"),ToDateTime("2/20/1998"),3,23.10m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10883,"LONEP",8,ToDateTime("2/12/1998"),ToDateTime("3/12/1998"),ToDateTime("2/20/1998"),3,0.53m, "Lonesome Pine Restaurant","89 Chiaroscuro Rd.","Portland", "OR","97219","USA"), + NorthwindFactory.Order(10884,"LETSS",4,ToDateTime("2/12/1998"),ToDateTime("3/12/1998"),ToDateTime("2/13/1998"),2,90.97m, "Let's Stop N Shop","87 Polk St. Suite 5","San Francisco", "CA","94117","USA"), + NorthwindFactory.Order(10885,"SUPRD",6,ToDateTime("2/12/1998"),ToDateTime("3/12/1998"),ToDateTime("2/18/1998"),3,5.64m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10886,"HANAR",1,ToDateTime("2/13/1998"),ToDateTime("3/13/1998"),ToDateTime("3/2/1998"),1,4.99m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10887,"GALED",8,ToDateTime("2/13/1998"),ToDateTime("3/13/1998"),ToDateTime("2/16/1998"),3,1.25m, "Galera del gastronmo","Rambla de Catalua, 23","Barcelona", null,"8022","Spain"), + NorthwindFactory.Order(10888,"GODOS",1,ToDateTime("2/16/1998"),ToDateTime("3/16/1998"),ToDateTime("2/23/1998"),2,51.87m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(10889,"RATTC",9,ToDateTime("2/16/1998"),ToDateTime("3/16/1998"),ToDateTime("2/23/1998"),3,280.61m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10890,"DUMON",7,ToDateTime("2/16/1998"),ToDateTime("3/16/1998"),ToDateTime("2/18/1998"),1,32.76m, "Du monde entier","67, rue des Cinquante Otages","Nantes", null,"44000","France"), + NorthwindFactory.Order(10891,"LEHMS",7,ToDateTime("2/17/1998"),ToDateTime("3/17/1998"),ToDateTime("2/19/1998"),2,20.37m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10892,"MAISD",4,ToDateTime("2/17/1998"),ToDateTime("3/17/1998"),ToDateTime("2/19/1998"),2,120.27m, "Maison Dewey","Rue Joseph-Bens 532","Bruxelles", null,"B-1180","Belgium"), + NorthwindFactory.Order(10893,"KOENE",9,ToDateTime("2/18/1998"),ToDateTime("3/18/1998"),ToDateTime("2/20/1998"),2,77.78m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(10894,"SAVEA",1,ToDateTime("2/18/1998"),ToDateTime("3/18/1998"),ToDateTime("2/20/1998"),1,116.13m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10895,"ERNSH",3,ToDateTime("2/18/1998"),ToDateTime("3/18/1998"),ToDateTime("2/23/1998"),1,162.75m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10896,"MAISD",7,ToDateTime("2/19/1998"),ToDateTime("3/19/1998"),ToDateTime("2/27/1998"),3,32.45m, "Maison Dewey","Rue Joseph-Bens 532","Bruxelles", null,"B-1180","Belgium"), + NorthwindFactory.Order(10897,"HUNGO",3,ToDateTime("2/19/1998"),ToDateTime("3/19/1998"),ToDateTime("2/25/1998"),2,603.54m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10898,"OCEAN",4,ToDateTime("2/20/1998"),ToDateTime("3/20/1998"),ToDateTime("3/6/1998"),2,1.27m, "Ocano Atlntico Ltda.","Ing. Gustavo Moncada 8585 Piso 20-A","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10899,"LILAS",5,ToDateTime("2/20/1998"),ToDateTime("3/20/1998"),ToDateTime("2/26/1998"),3,1.21m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10900,"WELLI",1,ToDateTime("2/20/1998"),ToDateTime("3/20/1998"),ToDateTime("3/4/1998"),2,1.66m, "Wellington Importadora","Rua do Mercado, 12","Resende", "SP","08737-363","Brazil"), + NorthwindFactory.Order(10901,"HILAA",4,ToDateTime("2/23/1998"),ToDateTime("3/23/1998"),ToDateTime("2/26/1998"),1,62.09m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10902,"FOLKO",1,ToDateTime("2/23/1998"),ToDateTime("3/23/1998"),ToDateTime("3/3/1998"),1,44.15m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10903,"HANAR",3,ToDateTime("2/24/1998"),ToDateTime("3/24/1998"),ToDateTime("3/4/1998"),3,36.71m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10904,"WHITC",3,ToDateTime("2/24/1998"),ToDateTime("3/24/1998"),ToDateTime("2/27/1998"),3,162.95m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(10905,"WELLI",9,ToDateTime("2/24/1998"),ToDateTime("3/24/1998"),ToDateTime("3/6/1998"),2,13.72m, "Wellington Importadora","Rua do Mercado, 12","Resende", "SP","08737-363","Brazil"), + NorthwindFactory.Order(10906,"WOLZA",4,ToDateTime("2/25/1998"),ToDateTime("3/11/1998"),ToDateTime("3/3/1998"),3,26.29m, "Wolski Zajazd","ul. Filtrowa 68","Warszawa", null,"01-012","Poland"), + NorthwindFactory.Order(10907,"SPECD",6,ToDateTime("2/25/1998"),ToDateTime("3/25/1998"),ToDateTime("2/27/1998"),3,9.19m, "Spcialits du monde","25, rue Lauriston","Paris", null,"75016","France"), + NorthwindFactory.Order(10908,"REGGC",4,ToDateTime("2/26/1998"),ToDateTime("3/26/1998"),ToDateTime("3/6/1998"),2,32.96m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10909,"SANTG",1,ToDateTime("2/26/1998"),ToDateTime("3/26/1998"),ToDateTime("3/10/1998"),2,53.05m, "Sant Gourmet","Erling Skakkes gate 78","Stavern", null,"4110","Norway"), + NorthwindFactory.Order(10910,"WILMK",1,ToDateTime("2/26/1998"),ToDateTime("3/26/1998"),ToDateTime("3/4/1998"),3,38.11m, "Wilman Kala","Keskuskatu 45","Helsinki", null,"21240","Finland"), + NorthwindFactory.Order(10911,"GODOS",3,ToDateTime("2/26/1998"),ToDateTime("3/26/1998"),ToDateTime("3/5/1998"),1,38.19m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(10912,"HUNGO",2,ToDateTime("2/26/1998"),ToDateTime("3/26/1998"),ToDateTime("3/18/1998"),2,580.91m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10913,"QUEEN",4,ToDateTime("2/26/1998"),ToDateTime("3/26/1998"),ToDateTime("3/4/1998"),1,33.05m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10914,"QUEEN",6,ToDateTime("2/27/1998"),ToDateTime("3/27/1998"),ToDateTime("3/2/1998"),1,21.19m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10915,"TORTU",2,ToDateTime("2/27/1998"),ToDateTime("3/27/1998"),ToDateTime("3/2/1998"),2,3.51m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10916,"RANCH",1,ToDateTime("2/27/1998"),ToDateTime("3/27/1998"),ToDateTime("3/9/1998"),2,63.77m, "Rancho grande","Av. del Libertador 900","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10917,"ROMEY",4,ToDateTime("3/2/1998"),ToDateTime("3/30/1998"),ToDateTime("3/11/1998"),2,8.29m, "Romero y tomillo","Gran Va, 1","Madrid", null,"28001","Spain"), + NorthwindFactory.Order(10918,"BOTTM",3,ToDateTime("3/2/1998"),ToDateTime("3/30/1998"),ToDateTime("3/11/1998"),3,48.83m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10919,"LINOD",2,ToDateTime("3/2/1998"),ToDateTime("3/30/1998"),ToDateTime("3/4/1998"),2,19.80m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10920,"AROUT",4,ToDateTime("3/3/1998"),ToDateTime("3/31/1998"),ToDateTime("3/9/1998"),2,29.61m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10921,"VAFFE",1,ToDateTime("3/3/1998"),ToDateTime("4/14/1998"),ToDateTime("3/9/1998"),1,176.48m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10922,"HANAR",5,ToDateTime("3/3/1998"),ToDateTime("3/31/1998"),ToDateTime("3/5/1998"),3,62.74m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10923,"LAMAI",7,ToDateTime("3/3/1998"),ToDateTime("4/14/1998"),ToDateTime("3/13/1998"),3,68.26m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(10924,"BERGS",3,ToDateTime("3/4/1998"),ToDateTime("4/1/1998"),ToDateTime("4/8/1998"),2,151.52m, "Berglunds snabbkp","Berguvsvgen 8","Lule", null,"S-958 22","Sweden"), + NorthwindFactory.Order(10925,"HANAR",3,ToDateTime("3/4/1998"),ToDateTime("4/1/1998"),ToDateTime("3/13/1998"),1,2.27m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10926,"ANATR",4,ToDateTime("3/4/1998"),ToDateTime("4/1/1998"),ToDateTime("3/11/1998"),3,39.92m, "Ana Trujillo Emparedados y helados","Avda. de la Constitucin 2222","Mxico D.F.", null,"05021","Mexico"), + NorthwindFactory.Order(10927,"LACOR",4,ToDateTime("3/5/1998"),ToDateTime("4/2/1998"),ToDateTime("4/8/1998"),1,19.79m, "La corne d'abondance","67, avenue de l'Europe","Versailles", null,"78000","France"), + NorthwindFactory.Order(10928,"GALED",1,ToDateTime("3/5/1998"),ToDateTime("4/2/1998"),ToDateTime("3/18/1998"),1,1.36m, "Galera del gastronmo","Rambla de Catalua, 23","Barcelona", null,"8022","Spain"), + NorthwindFactory.Order(10929,"FRANK",6,ToDateTime("3/5/1998"),ToDateTime("4/2/1998"),ToDateTime("3/12/1998"),1,33.93m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(10930,"SUPRD",4,ToDateTime("3/6/1998"),ToDateTime("4/17/1998"),ToDateTime("3/18/1998"),3,15.55m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(10931,"RICSU",4,ToDateTime("3/6/1998"),ToDateTime("3/20/1998"),ToDateTime("3/19/1998"),2,13.60m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(10932,"BONAP",8,ToDateTime("3/6/1998"),ToDateTime("4/3/1998"),ToDateTime("3/24/1998"),1,134.64m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10933,"ISLAT",6,ToDateTime("3/6/1998"),ToDateTime("4/3/1998"),ToDateTime("3/16/1998"),3,54.15m, "Island Trading","Garden House Crowther Way","Cowes", "Isle of Wight","PO31 7PJ","UK"), + NorthwindFactory.Order(10934,"LEHMS",3,ToDateTime("3/9/1998"),ToDateTime("4/6/1998"),ToDateTime("3/12/1998"),3,32.01m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(10935,"WELLI",4,ToDateTime("3/9/1998"),ToDateTime("4/6/1998"),ToDateTime("3/18/1998"),3,47.59m, "Wellington Importadora","Rua do Mercado, 12","Resende", "SP","08737-363","Brazil"), + NorthwindFactory.Order(10936,"GREAL",3,ToDateTime("3/9/1998"),ToDateTime("4/6/1998"),ToDateTime("3/18/1998"),2,33.68m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(10937,"CACTU",7,ToDateTime("3/10/1998"),ToDateTime("3/24/1998"),ToDateTime("3/13/1998"),3,31.51m, "Cactus Comidas para llevar","Cerrito 333","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10938,"QUICK",3,ToDateTime("3/10/1998"),ToDateTime("4/7/1998"),ToDateTime("3/16/1998"),2,31.89m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10939,"MAGAA",2,ToDateTime("3/10/1998"),ToDateTime("4/7/1998"),ToDateTime("3/13/1998"),2,76.33m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10940,"BONAP",8,ToDateTime("3/11/1998"),ToDateTime("4/8/1998"),ToDateTime("3/23/1998"),3,19.77m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(10941,"SAVEA",7,ToDateTime("3/11/1998"),ToDateTime("4/8/1998"),ToDateTime("3/20/1998"),2,400.81m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10942,"REGGC",9,ToDateTime("3/11/1998"),ToDateTime("4/8/1998"),ToDateTime("3/18/1998"),3,17.95m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(10943,"BSBEV",4,ToDateTime("3/11/1998"),ToDateTime("4/8/1998"),ToDateTime("3/19/1998"),2,2.17m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(10944,"BOTTM",6,ToDateTime("3/12/1998"),ToDateTime("3/26/1998"),ToDateTime("3/13/1998"),3,52.92m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10945,"MORGK",4,ToDateTime("3/12/1998"),ToDateTime("4/9/1998"),ToDateTime("3/18/1998"),1,10.22m, "Morgenstern Gesundkost","Heerstr. 22","Leipzig", null,"04179","Germany"), + NorthwindFactory.Order(10946,"VAFFE",1,ToDateTime("3/12/1998"),ToDateTime("4/9/1998"),ToDateTime("3/19/1998"),2,27.20m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10947,"BSBEV",3,ToDateTime("3/13/1998"),ToDateTime("4/10/1998"),ToDateTime("3/16/1998"),2,3.26m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(10948,"GODOS",3,ToDateTime("3/13/1998"),ToDateTime("4/10/1998"),ToDateTime("3/19/1998"),3,23.39m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(10949,"BOTTM",2,ToDateTime("3/13/1998"),ToDateTime("4/10/1998"),ToDateTime("3/17/1998"),3,74.44m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10950,"MAGAA",1,ToDateTime("3/16/1998"),ToDateTime("4/13/1998"),ToDateTime("3/23/1998"),2,2.50m, "Magazzini Alimentari Riuniti","Via Ludovico il Moro 22","Bergamo", null,"24100","Italy"), + NorthwindFactory.Order(10951,"RICSU",9,ToDateTime("3/16/1998"),ToDateTime("4/27/1998"),ToDateTime("4/7/1998"),2,30.85m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(10952,"ALFKI",1,ToDateTime("3/16/1998"),ToDateTime("4/27/1998"),ToDateTime("3/24/1998"),1,40.42m, "Alfred's Futterkiste","Obere Str. 57","Berlin", null,"12209","Germany"), + NorthwindFactory.Order(10953,"AROUT",9,ToDateTime("3/16/1998"),ToDateTime("3/30/1998"),ToDateTime("3/25/1998"),2,23.72m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(10954,"LINOD",5,ToDateTime("3/17/1998"),ToDateTime("4/28/1998"),ToDateTime("3/20/1998"),1,27.91m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(10955,"FOLKO",8,ToDateTime("3/17/1998"),ToDateTime("4/14/1998"),ToDateTime("3/20/1998"),2,3.26m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10956,"BLAUS",6,ToDateTime("3/17/1998"),ToDateTime("4/28/1998"),ToDateTime("3/20/1998"),2,44.65m, "Blauer See Delikatessen","Forsterstr. 57","Mannheim", null,"68306","Germany"), + NorthwindFactory.Order(10957,"HILAA",8,ToDateTime("3/18/1998"),ToDateTime("4/15/1998"),ToDateTime("3/27/1998"),3,105.36m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10958,"OCEAN",7,ToDateTime("3/18/1998"),ToDateTime("4/15/1998"),ToDateTime("3/27/1998"),2,49.56m, "Ocano Atlntico Ltda.","Ing. Gustavo Moncada 8585 Piso 20-A","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10959,"GOURL",6,ToDateTime("3/18/1998"),ToDateTime("4/29/1998"),ToDateTime("3/23/1998"),2,4.98m, "Gourmet Lanchonetes","Av. Brasil, 442","Campinas", "SP","04876-786","Brazil"), + NorthwindFactory.Order(10960,"HILAA",3,ToDateTime("3/19/1998"),ToDateTime("4/2/1998"),ToDateTime("4/8/1998"),1,2.08m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10961,"QUEEN",8,ToDateTime("3/19/1998"),ToDateTime("4/16/1998"),ToDateTime("3/30/1998"),1,104.47m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(10962,"QUICK",8,ToDateTime("3/19/1998"),ToDateTime("4/16/1998"),ToDateTime("3/23/1998"),2,275.79m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10963,"FURIB",9,ToDateTime("3/19/1998"),ToDateTime("4/16/1998"),ToDateTime("3/26/1998"),3,2.70m, "Furia Bacalhau e Frutos do Mar","Jardim das rosas n. 32","Lisboa", null,"1675","Portugal"), + NorthwindFactory.Order(10964,"SPECD",3,ToDateTime("3/20/1998"),ToDateTime("4/17/1998"),ToDateTime("3/24/1998"),2,87.38m, "Spcialits du monde","25, rue Lauriston","Paris", null,"75016","France"), + NorthwindFactory.Order(10965,"OLDWO",6,ToDateTime("3/20/1998"),ToDateTime("4/17/1998"),ToDateTime("3/30/1998"),3,144.38m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(10966,"CHOPS",4,ToDateTime("3/20/1998"),ToDateTime("4/17/1998"),ToDateTime("4/8/1998"),1,27.19m, "Chop-suey Chinese","Hauptstr. 31","Bern", null,"3012","Switzerland"), + NorthwindFactory.Order(10967,"TOMSP",2,ToDateTime("3/23/1998"),ToDateTime("4/20/1998"),ToDateTime("4/2/1998"),2,62.22m, "Toms Spezialitten","Luisenstr. 48","Mnster", null,"44087","Germany"), + NorthwindFactory.Order(10968,"ERNSH",1,ToDateTime("3/23/1998"),ToDateTime("4/20/1998"),ToDateTime("4/1/1998"),3,74.60m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10969,"COMMI",1,ToDateTime("3/23/1998"),ToDateTime("4/20/1998"),ToDateTime("3/30/1998"),2,0.21m, "Comrcio Mineiro","Av. dos Lusadas, 23","Sao Paulo", "SP","05432-043","Brazil"), + NorthwindFactory.Order(10970,"BOLID",9,ToDateTime("3/24/1998"),ToDateTime("4/7/1998"),ToDateTime("4/24/1998"),1,16.16m, "Blido Comidas preparadas","C/ Araquil, 67","Madrid", null,"28023","Spain"), + NorthwindFactory.Order(10971,"FRANR",2,ToDateTime("3/24/1998"),ToDateTime("4/21/1998"),ToDateTime("4/2/1998"),2,121.82m, "France restauration","54, rue Royale","Nantes", null,"44000","France"), + NorthwindFactory.Order(10972,"LACOR",4,ToDateTime("3/24/1998"),ToDateTime("4/21/1998"),ToDateTime("3/26/1998"),2,0.02m, "La corne d'abondance","67, avenue de l'Europe","Versailles", null,"78000","France"), + NorthwindFactory.Order(10973,"LACOR",6,ToDateTime("3/24/1998"),ToDateTime("4/21/1998"),ToDateTime("3/27/1998"),2,15.17m, "La corne d'abondance","67, avenue de l'Europe","Versailles", null,"78000","France"), + NorthwindFactory.Order(10974,"SPLIR",3,ToDateTime("3/25/1998"),ToDateTime("4/8/1998"),ToDateTime("4/3/1998"),3,12.96m, "Split Rail Beer & Ale","P.O. Box 555","Lander", "WY","82520","USA"), + NorthwindFactory.Order(10975,"BOTTM",1,ToDateTime("3/25/1998"),ToDateTime("4/22/1998"),ToDateTime("3/27/1998"),3,32.27m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10976,"HILAA",1,ToDateTime("3/25/1998"),ToDateTime("5/6/1998"),ToDateTime("4/3/1998"),1,37.97m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(10977,"FOLKO",8,ToDateTime("3/26/1998"),ToDateTime("4/23/1998"),ToDateTime("4/10/1998"),3,208.50m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10978,"MAISD",9,ToDateTime("3/26/1998"),ToDateTime("4/23/1998"),ToDateTime("4/23/1998"),2,32.82m, "Maison Dewey","Rue Joseph-Bens 532","Bruxelles", null,"B-1180","Belgium"), + NorthwindFactory.Order(10979,"ERNSH",8,ToDateTime("3/26/1998"),ToDateTime("4/23/1998"),ToDateTime("3/31/1998"),2,353.07m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10980,"FOLKO",4,ToDateTime("3/27/1998"),ToDateTime("5/8/1998"),ToDateTime("4/17/1998"),1,1.26m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10981,"HANAR",1,ToDateTime("3/27/1998"),ToDateTime("4/24/1998"),ToDateTime("4/2/1998"),2,193.37m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(10982,"BOTTM",2,ToDateTime("3/27/1998"),ToDateTime("4/24/1998"),ToDateTime("4/8/1998"),1,14.01m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(10983,"SAVEA",2,ToDateTime("3/27/1998"),ToDateTime("4/24/1998"),ToDateTime("4/6/1998"),2,657.54m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10984,"SAVEA",1,ToDateTime("3/30/1998"),ToDateTime("4/27/1998"),ToDateTime("4/3/1998"),3,211.22m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(10985,"HUNGO",2,ToDateTime("3/30/1998"),ToDateTime("4/27/1998"),ToDateTime("4/2/1998"),1,91.51m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(10986,"OCEAN",8,ToDateTime("3/30/1998"),ToDateTime("4/27/1998"),ToDateTime("4/21/1998"),2,217.86m, "Ocano Atlntico Ltda.","Ing. Gustavo Moncada 8585 Piso 20-A","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(10987,"EASTC",8,ToDateTime("3/31/1998"),ToDateTime("4/28/1998"),ToDateTime("4/6/1998"),1,185.48m, "Eastern Connection","35 King George","London", null,"WX3 6FW","UK"), + NorthwindFactory.Order(10988,"RATTC",3,ToDateTime("3/31/1998"),ToDateTime("4/28/1998"),ToDateTime("4/10/1998"),2,61.14m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(10989,"QUEDE",2,ToDateTime("3/31/1998"),ToDateTime("4/28/1998"),ToDateTime("4/2/1998"),1,34.76m, "Que Delcia","Rua da Panificadora, 12","Rio de Janeiro", "RJ","02389-673","Brazil"), + NorthwindFactory.Order(10990,"ERNSH",2,ToDateTime("4/1/1998"),ToDateTime("5/13/1998"),ToDateTime("4/7/1998"),3,117.61m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(10991,"QUICK",1,ToDateTime("4/1/1998"),ToDateTime("4/29/1998"),ToDateTime("4/7/1998"),1,38.51m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10992,"THEBI",1,ToDateTime("4/1/1998"),ToDateTime("4/29/1998"),ToDateTime("4/3/1998"),3,4.27m, "The Big Cheese","89 Jefferson Way Suite 2","Portland", "OR","97201","USA"), + NorthwindFactory.Order(10993,"FOLKO",7,ToDateTime("4/1/1998"),ToDateTime("4/29/1998"),ToDateTime("4/10/1998"),3,8.81m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(10994,"VAFFE",2,ToDateTime("4/2/1998"),ToDateTime("4/16/1998"),ToDateTime("4/9/1998"),3,65.53m, "Vaffeljernet","Smagsloget 45","rhus", null,"8200","Denmark"), + NorthwindFactory.Order(10995,"PERIC",1,ToDateTime("4/2/1998"),ToDateTime("4/30/1998"),ToDateTime("4/6/1998"),3,46.00m, "Pericles Comidas clsicas","Calle Dr. Jorge Cash 321","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(10996,"QUICK",4,ToDateTime("4/2/1998"),ToDateTime("4/30/1998"),ToDateTime("4/10/1998"),2,1.12m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(10997,"LILAS",8,ToDateTime("4/3/1998"),ToDateTime("5/15/1998"),ToDateTime("4/13/1998"),2,73.91m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(10998,"WOLZA",8,ToDateTime("4/3/1998"),ToDateTime("4/17/1998"),ToDateTime("4/17/1998"),2,20.31m, "Wolski Zajazd","ul. Filtrowa 68","Warszawa", null,"01-012","Poland"), + NorthwindFactory.Order(10999,"OTTIK",6,ToDateTime("4/3/1998"),ToDateTime("5/1/1998"),ToDateTime("4/10/1998"),2,96.35m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(11000,"RATTC",2,ToDateTime("4/6/1998"),ToDateTime("5/4/1998"),ToDateTime("4/14/1998"),3,55.12m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + NorthwindFactory.Order(11001,"FOLKO",2,ToDateTime("4/6/1998"),ToDateTime("5/4/1998"),ToDateTime("4/14/1998"),2,197.30m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(11002,"SAVEA",4,ToDateTime("4/6/1998"),ToDateTime("5/4/1998"),ToDateTime("4/16/1998"),1,141.16m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(11003,"THECR",3,ToDateTime("4/6/1998"),ToDateTime("5/4/1998"),ToDateTime("4/8/1998"),3,14.91m, "The Cracker Box","55 Grizzly Peak Rd.","Butte", "MT","59801","USA"), + NorthwindFactory.Order(11004,"MAISD",3,ToDateTime("4/7/1998"),ToDateTime("5/5/1998"),ToDateTime("4/20/1998"),1,44.84m, "Maison Dewey","Rue Joseph-Bens 532","Bruxelles", null,"B-1180","Belgium"), + NorthwindFactory.Order(11005,"WILMK",2,ToDateTime("4/7/1998"),ToDateTime("5/5/1998"),ToDateTime("4/10/1998"),1,0.75m, "Wilman Kala","Keskuskatu 45","Helsinki", null,"21240","Finland"), + NorthwindFactory.Order(11006,"GREAL",3,ToDateTime("4/7/1998"),ToDateTime("5/5/1998"),ToDateTime("4/15/1998"),2,25.19m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(11007,"PRINI",8,ToDateTime("4/8/1998"),ToDateTime("5/6/1998"),ToDateTime("4/13/1998"),2,202.24m, "Princesa Isabel Vinhos","Estrada da sade n. 58","Lisboa", null,"1756","Portugal"), + NorthwindFactory.Order(11008,"ERNSH",7,ToDateTime("4/8/1998"),ToDateTime("5/6/1998"),null,3,79.46m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(11009,"GODOS",2,ToDateTime("4/8/1998"),ToDateTime("5/6/1998"),ToDateTime("4/10/1998"),1,59.11m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(11010,"REGGC",2,ToDateTime("4/9/1998"),ToDateTime("5/7/1998"),ToDateTime("4/21/1998"),2,28.71m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(11011,"ALFKI",3,ToDateTime("4/9/1998"),ToDateTime("5/7/1998"),ToDateTime("4/13/1998"),1,1.21m, "Alfred's Futterkiste","Obere Str. 57","Berlin", null,"12209","Germany"), + NorthwindFactory.Order(11012,"FRANK",1,ToDateTime("4/9/1998"),ToDateTime("4/23/1998"),ToDateTime("4/17/1998"),3,242.95m, "Frankenversand","Berliner Platz 43","Mnchen", null,"80805","Germany"), + NorthwindFactory.Order(11013,"ROMEY",2,ToDateTime("4/9/1998"),ToDateTime("5/7/1998"),ToDateTime("4/10/1998"),1,32.99m, "Romero y tomillo","Gran Va, 1","Madrid", null,"28001","Spain"), + NorthwindFactory.Order(11014,"LINOD",2,ToDateTime("4/10/1998"),ToDateTime("5/8/1998"),ToDateTime("4/15/1998"),3,23.60m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(11015,"SANTG",2,ToDateTime("4/10/1998"),ToDateTime("4/24/1998"),ToDateTime("4/20/1998"),2,4.62m, "Sant Gourmet","Erling Skakkes gate 78","Stavern", null,"4110","Norway"), + NorthwindFactory.Order(11016,"AROUT",9,ToDateTime("4/10/1998"),ToDateTime("5/8/1998"),ToDateTime("4/13/1998"),2,33.80m, "Around the Horn","Brook Farm Stratford St. Mary","Colchester", "Essex","CO7 6JX","UK"), + NorthwindFactory.Order(11017,"ERNSH",9,ToDateTime("4/13/1998"),ToDateTime("5/11/1998"),ToDateTime("4/20/1998"),2,754.26m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(11018,"LONEP",4,ToDateTime("4/13/1998"),ToDateTime("5/11/1998"),ToDateTime("4/16/1998"),2,11.65m, "Lonesome Pine Restaurant","89 Chiaroscuro Rd.","Portland", "OR","97219","USA"), + NorthwindFactory.Order(11019,"RANCH",6,ToDateTime("4/13/1998"),ToDateTime("5/11/1998"),null,3,3.17m, "Rancho grande","Av. del Libertador 900","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(11020,"OTTIK",2,ToDateTime("4/14/1998"),ToDateTime("5/12/1998"),ToDateTime("4/16/1998"),2,43.30m, "Ottilies Kseladen","Mehrheimerstr. 369","Kln", null,"50739","Germany"), + NorthwindFactory.Order(11021,"QUICK",3,ToDateTime("4/14/1998"),ToDateTime("5/12/1998"),ToDateTime("4/21/1998"),1,297.18m, "QUICK-Stop","Taucherstrae 10","Cunewalde", null,"01307","Germany"), + NorthwindFactory.Order(11022,"HANAR",9,ToDateTime("4/14/1998"),ToDateTime("5/12/1998"),ToDateTime("5/4/1998"),2,6.27m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(11023,"BSBEV",1,ToDateTime("4/14/1998"),ToDateTime("4/28/1998"),ToDateTime("4/24/1998"),2,123.83m, "B's Beverages","Fauntleroy Circus","London", null,"EC2 5NT","UK"), + NorthwindFactory.Order(11024,"EASTC",4,ToDateTime("4/15/1998"),ToDateTime("5/13/1998"),ToDateTime("4/20/1998"),1,74.36m, "Eastern Connection","35 King George","London", null,"WX3 6FW","UK"), + NorthwindFactory.Order(11025,"WARTH",6,ToDateTime("4/15/1998"),ToDateTime("5/13/1998"),ToDateTime("4/24/1998"),3,29.17m, "Wartian Herkku","Torikatu 38","Oulu", null,"90110","Finland"), + NorthwindFactory.Order(11026,"FRANS",4,ToDateTime("4/15/1998"),ToDateTime("5/13/1998"),ToDateTime("4/28/1998"),1,47.09m, "Franchi S.p.A.","Via Monte Bianco 34","Torino", null,"10100","Italy"), + NorthwindFactory.Order(11027,"BOTTM",1,ToDateTime("4/16/1998"),ToDateTime("5/14/1998"),ToDateTime("4/20/1998"),1,52.52m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(11028,"KOENE",2,ToDateTime("4/16/1998"),ToDateTime("5/14/1998"),ToDateTime("4/22/1998"),1,29.59m, "Kniglich Essen","Maubelstr. 90","Brandenburg", null,"14776","Germany"), + NorthwindFactory.Order(11029,"CHOPS",4,ToDateTime("4/16/1998"),ToDateTime("5/14/1998"),ToDateTime("4/27/1998"),1,47.84m, "Chop-suey Chinese","Hauptstr. 31","Bern", null,"3012","Switzerland"), + NorthwindFactory.Order(11030,"SAVEA",7,ToDateTime("4/17/1998"),ToDateTime("5/15/1998"),ToDateTime("4/27/1998"),2,830.75m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(11031,"SAVEA",6,ToDateTime("4/17/1998"),ToDateTime("5/15/1998"),ToDateTime("4/24/1998"),2,227.22m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(11032,"WHITC",2,ToDateTime("4/17/1998"),ToDateTime("5/15/1998"),ToDateTime("4/23/1998"),3,606.19m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(11033,"RICSU",7,ToDateTime("4/17/1998"),ToDateTime("5/15/1998"),ToDateTime("4/23/1998"),3,84.74m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(11034,"OLDWO",8,ToDateTime("4/20/1998"),ToDateTime("6/1/1998"),ToDateTime("4/27/1998"),1,40.32m, "Old World Delicatessen","2743 Bering St.","Anchorage", "AK","99508","USA"), + NorthwindFactory.Order(11035,"SUPRD",2,ToDateTime("4/20/1998"),ToDateTime("5/18/1998"),ToDateTime("4/24/1998"),2,0.17m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(11036,"DRACD",8,ToDateTime("4/20/1998"),ToDateTime("5/18/1998"),ToDateTime("4/22/1998"),3,149.47m, "Drachenblut Delikatessen","Walserweg 21","Aachen", null,"52066","Germany"), + NorthwindFactory.Order(11037,"GODOS",7,ToDateTime("4/21/1998"),ToDateTime("5/19/1998"),ToDateTime("4/27/1998"),1,3.20m, "Godos Cocina Tpica","C/ Romero, 33","Sevilla", null,"41101","Spain"), + NorthwindFactory.Order(11038,"SUPRD",1,ToDateTime("4/21/1998"),ToDateTime("5/19/1998"),ToDateTime("4/30/1998"),2,29.59m, "Suprmes dlices","Boulevard Tirou, 255","Charleroi", null,"B-6000","Belgium"), + NorthwindFactory.Order(11039,"LINOD",1,ToDateTime("4/21/1998"),ToDateTime("5/19/1998"),null,2,65.00m, "LINO-Delicateses","Ave. 5 de Mayo Porlamar","I. de Margarita", "Nueva Esparta","4980","Venezuela"), + NorthwindFactory.Order(11040,"GREAL",4,ToDateTime("4/22/1998"),ToDateTime("5/20/1998"),null,3,18.84m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(11041,"CHOPS",3,ToDateTime("4/22/1998"),ToDateTime("5/20/1998"),ToDateTime("4/28/1998"),2,48.22m, "Chop-suey Chinese","Hauptstr. 31","Bern", null,"3012","Switzerland"), + NorthwindFactory.Order(11042,"COMMI",2,ToDateTime("4/22/1998"),ToDateTime("5/6/1998"),ToDateTime("5/1/1998"),1,29.99m, "Comrcio Mineiro","Av. dos Lusadas, 23","Sao Paulo", "SP","05432-043","Brazil"), + NorthwindFactory.Order(11043,"SPECD",5,ToDateTime("4/22/1998"),ToDateTime("5/20/1998"),ToDateTime("4/29/1998"),2,8.80m, "Spcialits du monde","25, rue Lauriston","Paris", null,"75016","France"), + NorthwindFactory.Order(11044,"WOLZA",4,ToDateTime("4/23/1998"),ToDateTime("5/21/1998"),ToDateTime("5/1/1998"),1,8.72m, "Wolski Zajazd","ul. Filtrowa 68","Warszawa", null,"01-012","Poland"), + NorthwindFactory.Order(11045,"BOTTM",6,ToDateTime("4/23/1998"),ToDateTime("5/21/1998"),null,2,70.58m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(11046,"WANDK",8,ToDateTime("4/23/1998"),ToDateTime("5/21/1998"),ToDateTime("4/24/1998"),2,71.64m, "Die Wandernde Kuh","Adenauerallee 900","Stuttgart", null,"70563","Germany"), + NorthwindFactory.Order(11047,"EASTC",7,ToDateTime("4/24/1998"),ToDateTime("5/22/1998"),ToDateTime("5/1/1998"),3,46.62m, "Eastern Connection","35 King George","London", null,"WX3 6FW","UK"), + NorthwindFactory.Order(11048,"BOTTM",7,ToDateTime("4/24/1998"),ToDateTime("5/22/1998"),ToDateTime("4/30/1998"),3,24.12m, "Bottom-Dollar Markets","23 Tsawassen Blvd.","Tsawassen", "BC","T2F 8M4","Canada"), + NorthwindFactory.Order(11049,"GOURL",3,ToDateTime("4/24/1998"),ToDateTime("5/22/1998"),ToDateTime("5/4/1998"),1,8.34m, "Gourmet Lanchonetes","Av. Brasil, 442","Campinas", "SP","04876-786","Brazil"), + NorthwindFactory.Order(11050,"FOLKO",8,ToDateTime("4/27/1998"),ToDateTime("5/25/1998"),ToDateTime("5/5/1998"),2,59.41m, "Folk och f HB","kergatan 24","Brcke", null,"S-844 67","Sweden"), + NorthwindFactory.Order(11051,"LAMAI",7,ToDateTime("4/27/1998"),ToDateTime("5/25/1998"),null,3,2.79m, "La maison d'Asie","1 rue Alsace-Lorraine","Toulouse", null,"31000","France"), + NorthwindFactory.Order(11052,"HANAR",3,ToDateTime("4/27/1998"),ToDateTime("5/25/1998"),ToDateTime("5/1/1998"),1,67.26m, "Hanari Carnes","Rua do Pao, 67","Rio de Janeiro", "RJ","05454-876","Brazil"), + NorthwindFactory.Order(11053,"PICCO",2,ToDateTime("4/27/1998"),ToDateTime("5/25/1998"),ToDateTime("4/29/1998"),2,53.05m, "Piccolo und mehr","Geislweg 14","Salzburg", null,"5020","Austria"), + NorthwindFactory.Order(11054,"CACTU",8,ToDateTime("4/28/1998"),ToDateTime("5/26/1998"),null,1,0.33m, "Cactus Comidas para llevar","Cerrito 333","Buenos Aires", null,"1010","Argentina"), + NorthwindFactory.Order(11055,"HILAA",7,ToDateTime("4/28/1998"),ToDateTime("5/26/1998"),ToDateTime("5/5/1998"),2,120.92m, "HILARION-Abastos","Carrera 22 con Ave. Carlos Soublette #8-35","San Cristbal", "Tchira","5022","Venezuela"), + NorthwindFactory.Order(11056,"EASTC",8,ToDateTime("4/28/1998"),ToDateTime("5/12/1998"),ToDateTime("5/1/1998"),2,278.96m, "Eastern Connection","35 King George","London", null,"WX3 6FW","UK"), + NorthwindFactory.Order(11057,"NORTS",3,ToDateTime("4/29/1998"),ToDateTime("5/27/1998"),ToDateTime("5/1/1998"),3,4.13m, "North/South","South House 300 Queensbridge","London", null,"SW7 1RZ","UK"), + NorthwindFactory.Order(11058,"BLAUS",9,ToDateTime("4/29/1998"),ToDateTime("5/27/1998"),null,3,31.14m, "Blauer See Delikatessen","Forsterstr. 57","Mannheim", null,"68306","Germany"), + NorthwindFactory.Order(11059,"RICAR",2,ToDateTime("4/29/1998"),ToDateTime("6/10/1998"),null,2,85.80m, "Ricardo Adocicados","Av. Copacabana, 267","Rio de Janeiro", "RJ","02389-890","Brazil"), + NorthwindFactory.Order(11060,"FRANS",2,ToDateTime("4/30/1998"),ToDateTime("5/28/1998"),ToDateTime("5/4/1998"),2,10.98m, "Franchi S.p.A.","Via Monte Bianco 34","Torino", null,"10100","Italy"), + NorthwindFactory.Order(11061,"GREAL",4,ToDateTime("4/30/1998"),ToDateTime("6/11/1998"),null,3,14.01m, "Great Lakes Food Market","2732 Baker Blvd.","Eugene", "OR","97403","USA"), + NorthwindFactory.Order(11062,"REGGC",4,ToDateTime("4/30/1998"),ToDateTime("5/28/1998"),null,2,29.93m, "Reggiani Caseifici","Strada Provinciale 124","Reggio Emilia", null,"42100","Italy"), + NorthwindFactory.Order(11063,"HUNGO",3,ToDateTime("4/30/1998"),ToDateTime("5/28/1998"),ToDateTime("5/6/1998"),2,81.73m, "Hungry Owl All-Night Grocers","8 Johnstown Road","Cork", "Co. Cork",null,"Ireland"), + NorthwindFactory.Order(11064,"SAVEA",1,ToDateTime("5/1/1998"),ToDateTime("5/29/1998"),ToDateTime("5/4/1998"),1,30.09m, "Save-a-lot Markets","187 Suffolk Ln.","Boise", "ID","83720","USA"), + NorthwindFactory.Order(11065,"LILAS",8,ToDateTime("5/1/1998"),ToDateTime("5/29/1998"),null,1,12.91m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(11066,"WHITC",7,ToDateTime("5/1/1998"),ToDateTime("5/29/1998"),ToDateTime("5/4/1998"),2,44.72m, "White Clover Markets","1029 - 12th Ave. S.","Seattle", "WA","98124","USA"), + NorthwindFactory.Order(11067,"DRACD",1,ToDateTime("5/4/1998"),ToDateTime("5/18/1998"),ToDateTime("5/6/1998"),2,7.98m, "Drachenblut Delikatessen","Walserweg 21","Aachen", null,"52066","Germany"), + NorthwindFactory.Order(11068,"QUEEN",8,ToDateTime("5/4/1998"),ToDateTime("6/1/1998"),null,2,81.75m, "Queen Cozinha","Alameda dos Canrios, 891","Sao Paulo", "SP","05487-020","Brazil"), + NorthwindFactory.Order(11069,"TORTU",1,ToDateTime("5/4/1998"),ToDateTime("6/1/1998"),ToDateTime("5/6/1998"),2,15.67m, "Tortuga Restaurante","Avda. Azteca 123","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(11070,"LEHMS",2,ToDateTime("5/5/1998"),ToDateTime("6/2/1998"),null,1,136.00m, "Lehmanns Marktstand","Magazinweg 7","Frankfurt a.M.", null,"60528","Germany"), + NorthwindFactory.Order(11071,"LILAS",1,ToDateTime("5/5/1998"),ToDateTime("6/2/1998"),null,1,0.93m, "LILA-Supermercado","Carrera 52 con Ave. Bolvar #65-98 Llano Largo","Barquisimeto", "Lara","3508","Venezuela"), + NorthwindFactory.Order(11072,"ERNSH",4,ToDateTime("5/5/1998"),ToDateTime("6/2/1998"),null,2,258.64m, "Ernst Handel","Kirchgasse 6","Graz", null,"8010","Austria"), + NorthwindFactory.Order(11073,"PERIC",2,ToDateTime("5/5/1998"),ToDateTime("6/2/1998"),null,2,24.95m, "Pericles Comidas clsicas","Calle Dr. Jorge Cash 321","Mxico D.F.", null,"05033","Mexico"), + NorthwindFactory.Order(11074,"SIMOB",7,ToDateTime("5/6/1998"),ToDateTime("6/3/1998"),null,2,18.44m, "Simons bistro","Vinbltet 34","Kobenhavn", null,"1734","Denmark"), + NorthwindFactory.Order(11075,"RICSU",8,ToDateTime("5/6/1998"),ToDateTime("6/3/1998"),null,2,6.19m, "Richter Supermarkt","Starenweg 5","Genve", null,"1204","Switzerland"), + NorthwindFactory.Order(11076,"BONAP",4,ToDateTime("5/6/1998"),ToDateTime("6/3/1998"),null,2,38.28m, "Bon app'","12, rue des Bouchers","Marseille", null,"13008","France"), + NorthwindFactory.Order(11077,"RATTC",1,ToDateTime("5/6/1998"),ToDateTime("6/3/1998"),null,2,8.53m, "Rattlesnake Canyon Grocery","2817 Milton Dr.","Albuquerque", "NM","87110","USA"), + }; + + Products = new List { + NorthwindFactory.Product(1,"Chai",1,1,"10 boxes x 20 bags",18,39,0,10,false), + NorthwindFactory.Product(2,"Chang",1,1,"24 - 12 oz bottles",19,17,40,25,false), + NorthwindFactory.Product(3,"Aniseed Syrup",1,2,"12 - 550 ml bottles",10,13,70,25,false), + NorthwindFactory.Product(4,"Chef Anton's Cajun Seasoning",2,2,"48 - 6 oz jars",22,53,0,0,false), + NorthwindFactory.Product(5,"Chef Anton's Gumbo Mix",2,2,"36 boxes",21.35m,0,0,0,true), + NorthwindFactory.Product(6,"Grandma's Boysenberry Spread",3,2,"12 - 8 oz jars",25,120,0,25,false), + NorthwindFactory.Product(7,"Uncle Bob's Organic Dried Pears",3,7,"12 - 1 lb pkgs.",30,15,0,10,false), + NorthwindFactory.Product(8,"Northwoods Cranberry Sauce",3,2,"12 - 12 oz jars",40,6,0,0,false), + NorthwindFactory.Product(9,"Mishi Kobe Niku",4,6,"18 - 500 g pkgs.",97,29,0,0,true), + NorthwindFactory.Product(10,"Ikura",4,8,"12 - 200 ml jars",31,31,0,0,false), + + NorthwindFactory.Product(11,"Queso Cabrales",5,4,"1 kg pkg.",21,22,30,30,false), + NorthwindFactory.Product(12,"Queso Manchego La Pastora",5,4,"10 - 500 g pkgs.",38,86,0,0,false), + NorthwindFactory.Product(13,"Konbu",6,8,"2 kg box",6,24,0,5,false), + NorthwindFactory.Product(14,"Tofu",6,7,"40 - 100 g pkgs.",23.25m,35,0,0,false), + NorthwindFactory.Product(15,"Genen Shouyu",6,2,"24 - 250 ml bottles",15.5m,39,0,5,false), + NorthwindFactory.Product(16,"Pavlova",7,3,"32 - 500 g boxes",17.45m,29,0,10,false), + NorthwindFactory.Product(17,"Alice Mutton",7,6,"20 - 1 kg tins",39,0,0,0,true), + NorthwindFactory.Product(18,"Carnarvon Tigers",7,8,"16 kg pkg.",62.5m,42,0,0,false), + NorthwindFactory.Product(19,"Teatime Chocolate Biscuits",8,3,"10 boxes x 12 pieces",9.2m,25,0,5,false), + NorthwindFactory.Product(20,"Sir Rodney's Marmalade",8,3,"30 gift boxes",81,40,0,0,false), + + NorthwindFactory.Product(21,"Sir Rodney's Scones",8,3,"24 pkgs. x 4 pieces",10,3,40,5,false), + NorthwindFactory.Product(22,"Gustaf's Knckebrd",9,5,"24 - 500 g pkgs.",21,104,0,25,false), + NorthwindFactory.Product(23,"Tunnbrd",9,5,"12 - 250 g pkgs.",9,61,0,25,false), + NorthwindFactory.Product(24,"Guaran Fantstica",10,1,"12 - 355 ml cans",4.5m,20,0,0,true), + NorthwindFactory.Product(25,"NuNuCa Nu-Nougat-Creme",11,3,"20 - 450 g glasses",14,76,0,30,false), + NorthwindFactory.Product(26,"Gumbr Gummibrchen",11,3,"100 - 250 g bags",31.23m,15,0,0,false), + NorthwindFactory.Product(27,"Schoggi Schokolade",11,3,"100 - 100 g pieces",43.9m,49,0,30,false), + NorthwindFactory.Product(28,"Rssle Sauerkraut",12,7,"25 - 825 g cans",45.6m,26,0,0,true), + NorthwindFactory.Product(29,"Thringer Rostbratwurst",12,6,"50 bags x 30 sausgs.",123.79m,0,0,0,true), + NorthwindFactory.Product(30,"Nord-Ost Matjeshering",13,8,"10 - 200 g glasses",25.89m,10,0,15,false), + + NorthwindFactory.Product(31,"Gorgonzola Telino",14,4,"12 - 100 g pkgs",12.5m,0,70,20,false), + NorthwindFactory.Product(32,"Mascarpone Fabioli",14,4,"24 - 200 g pkgs.",32,9,40,25,false), + NorthwindFactory.Product(33,"Geitost",15,4,"500 g",2.5m,112,0,20,false), + NorthwindFactory.Product(34,"Sasquatch Ale",16,1,"24 - 12 oz bottles",14,111,0,15,false), + NorthwindFactory.Product(35,"Steeleye Stout",16,1,"24 - 12 oz bottles",18,20,0,15,false), + NorthwindFactory.Product(36,"Inlagd Sill",17,8,"24 - 250 g jars",19,112,0,20,false), + NorthwindFactory.Product(37,"Gravad lax",17,8,"12 - 500 g pkgs.",26,11,50,25,false), + NorthwindFactory.Product(38,"Cte de Blaye",18,1,"12 - 75 cl bottles",263.5m,17,0,15,false), + NorthwindFactory.Product(39,"Chartreuse verte",18,1,"750 cc per bottle",18,69,0,5,false), + NorthwindFactory.Product(40,"Boston Crab Meat",19,8,"24 - 4 oz tins",18.4m,123,0,30,false), + + NorthwindFactory.Product(41,"Jack's New England Clam Chowder",19,8,"12 - 12 oz cans",9.65m,85,0,10,false), + NorthwindFactory.Product(42,"Singaporean Hokkien Fried Mee",20,5,"32 - 1 kg pkgs.",14,26,0,0,true), + NorthwindFactory.Product(43,"Ipoh Coffee",20,1,"16 - 500 g tins",46,17,10,25,false), + NorthwindFactory.Product(44,"Gula Malacca",20,2,"20 - 2 kg bags",19.45m,27,0,15,false), + NorthwindFactory.Product(45,"Rogede sild",21,8,"1k pkg.",9.5m,5,70,15,false), + NorthwindFactory.Product(46,"Spegesild",21,8,"4 - 450 g glasses",12,95,0,0,false), + NorthwindFactory.Product(47,"Zaanse koeken",22,3,"10 - 4 oz boxes",9.5m,36,0,0,false), + NorthwindFactory.Product(48,"Chocolade",22,3,"10 pkgs.",12.75m,15,70,25,false), + NorthwindFactory.Product(49,"Maxilaku",23,3,"24 - 50 g pkgs.",20,10,60,15,false), + NorthwindFactory.Product(50,"Valkoinen suklaa",23,3,"12 - 100 g bars",16.25m,65,0,30,false), + + NorthwindFactory.Product(51,"Manjimup Dried Apples",24,7,"50 - 300 g pkgs.",53,20,0,10,false), + NorthwindFactory.Product(52,"Filo Mix",24,5,"16 - 2 kg boxes",7,38,0,25,false), + NorthwindFactory.Product(53,"Perth Pasties",24,6,"48 pieces",32.8m,0,0,0,true), + NorthwindFactory.Product(54,"Tourtire",25,6,"16 pies",7.45m,21,0,10,false), + NorthwindFactory.Product(55,"Pt chinois",25,6,"24 boxes x 2 pies",24,115,0,20,false), + NorthwindFactory.Product(56,"Gnocchi di nonna Alice",26,5,"24 - 250 g pkgs.",38,21,10,30,false), + NorthwindFactory.Product(57,"Ravioli Angelo",26,5,"24 - 250 g pkgs.",19.5m,36,0,20,false), + NorthwindFactory.Product(58,"Escargots de Bourgogne",27,8,"24 pieces",13.25m,62,0,20,false), + NorthwindFactory.Product(59,"Raclette Courdavault",28,4,"5 kg pkg.",55,79,0,0,false), + NorthwindFactory.Product(60,"Camembert Pierrot",28,4,"15 - 300 g rounds",34,19,0,0,false), + + NorthwindFactory.Product(61,"Sirop d'rable",29,2,"24 - 500 ml bottles",28.5m,113,0,25,false), + NorthwindFactory.Product(62,"Tarte au sucre",29,3,"48 pies",49.3m,17,0,0,false), + NorthwindFactory.Product(63,"Vegie-spread",7,2,"15 - 625 g jars",43.9m,24,0,5,false), + NorthwindFactory.Product(64,"Wimmers gute Semmelkndel",12,5,"20 bags x 4 pieces",33.25m,22,80,30,false), + NorthwindFactory.Product(65,"Louisiana Fiery Hot Pepper Sauce",2,2,"32 - 8 oz bottles",21.05m,76,0,0,false), + NorthwindFactory.Product(66,"Louisiana Hot Spiced Okra",2,2,"24 - 8 oz jars",17,4,100,20,false), + NorthwindFactory.Product(67,"Laughing Lumberjack Lager",16,1,"24 - 12 oz bottles",14,52,0,10,false), + NorthwindFactory.Product(68,"Scottish Longbreads",8,3,"10 boxes x 8 pieces",12.5m,6,10,15,false), + NorthwindFactory.Product(69,"Gudbrandsdalsost",15,4,"10 kg pkg.",36,26,0,15,false), + NorthwindFactory.Product(70,"Outback Lager",7,1,"24 - 355 ml bottles",15,15,10,30,false), + + NorthwindFactory.Product(71,"Flotemysost",15,4,"10 - 500 g pkgs.",21.5m,26,0,0,false), + NorthwindFactory.Product(72,"Mozzarella di Giovanni",14,4,"24 - 200 g pkgs.",34.8m,14,0,0,false), + NorthwindFactory.Product(73,"Rd Kaviar",17,8,"24 - 150 g jars",15,101,0,5,false), + NorthwindFactory.Product(74,"Longlife Tofu",4,7,"5 kg pkg.",10,4,20,5,false), + NorthwindFactory.Product(75,"Rhnbru Klosterbier",12,1,"24 - 0.5 l bottles",7.75m,125,0,25,false), + NorthwindFactory.Product(76,"Lakkalikri",23,1,"500 ml",18,57,0,20,false), + NorthwindFactory.Product(77,"Original Frankfurter grne Soe",12,2,"12 boxes",13,32,0,15,false), + }; + + Shippers = new List { + NorthwindFactory.Shipper(1,"Speedy Express","(503) 555-9831"), + NorthwindFactory.Shipper(2,"United Package","(503) 555-3199"), + NorthwindFactory.Shipper(3,"Federal Shipping","(503) 555-9931"), + }; + + Suppliers = new List { + NorthwindFactory.Supplier(1,"Exotic Liquids","Charlotte Cooper","Purchasing Manager","49 Gilbert St.","London",null,"EC1 4SD","UK","(171) 555-2222",null,null), + NorthwindFactory.Supplier(2,"New Orleans Cajun Delights","Shelley Burke","Order Administrator","P.O. Box 78934","New Orleans","LA","70117","USA","(100) 555-4822",null,"#CAJUN.HTM#"), + NorthwindFactory.Supplier(3,"Grandma Kelly's Homestead","Regina Murphy","Sales Representative","707 Oxford Rd.","Ann Arbor","MI","48104","USA","(313) 555-5735","(313) 555-3349",null), + NorthwindFactory.Supplier(4,"Tokyo Traders","Yoshi Nagase","Marketing Manager","9-8 Sekimai Musashino-shi","Tokyo",null,"100","Japan","(03) 3555-5011",null,null), + NorthwindFactory.Supplier(5,"Cooperativa de Quesos 'Las Cabras'","Antonio del Valle Saavedra","Export Administrator","Calle del Rosal 4","Oviedo","Asturias","33007","Spain","(98) 598 76 54",null,null), + NorthwindFactory.Supplier(6,"Mayumi's","Mayumi Ohno","Marketing Representative","92 Setsuko Chuo-ku","Osaka",null,"545","Japan","(06) 431-7877",null,"Mayumi's (on the World Wide Web)#http://www.microsoft.com/accessdev/sampleapps/mayumi.htm#"), + NorthwindFactory.Supplier(7,"Pavlova, Ltd.","Ian Devling","Marketing Manager","74 Rose St. Moonie Ponds","Melbourne","Victoria","3058","Australia","(03) 444-2343","(03) 444-6588",null), + NorthwindFactory.Supplier(8,"Specialty Biscuits, Ltd.","Peter Wilson","Sales Representative","29 King's Way","Manchester",null,"M14 GSD","UK","(161) 555-4448",null,null), + NorthwindFactory.Supplier(9,"PB Knckebrd AB","Lars Peterson","Sales Agent","Kaloadagatan 13","Gteborg",null,"S-345 67","Sweden","031-987 65 43","031-987 65 91",null), + NorthwindFactory.Supplier(10,"Refrescos Americanas LTDA","Carlos Diaz","Marketing Manager","Av. das Americanas 12.890","Sao Paulo",null,"5442","Brazil","(11) 555 4640",null,null), + + NorthwindFactory.Supplier(11,"Heli Swaren GmbH & Co. KG","Petra Winkler","Sales Manager","Tiergartenstrae 5","Berlin",null,"10785","Germany","(010) 9984510",null,null), + NorthwindFactory.Supplier(12,"Plutzer Lebensmittelgromrkte AG","Martin Bein","International Marketing Mgr.","Bogenallee 51","Frankfurt",null,"60439","Germany","(069) 992755",null,"Plutzer (on the World Wide Web)#http://www.microsoft.com/accessdev/sampleapps/plutzer.htm#"), + NorthwindFactory.Supplier(13,"Nord-Ost-Fisch Handelsgesellschaft mbH","Sven Petersen","Coordinator Foreign Markets","Frahmredder 112a","Cuxhaven",null,"27478","Germany","(04721) 8713","(04721) 8714",null), + NorthwindFactory.Supplier(14,"Formaggi Fortini s.r.l.","Elio Rossi","Sales Representative","Viale Dante, 75","Ravenna",null,"48100","Italy","(0544) 60323","(0544) 60603","#FORMAGGI.HTM#"), + NorthwindFactory.Supplier(15,"Norske Meierier","Beate Vileid","Marketing Manager","Hatlevegen 5","Sandvika",null,"1320","Norway","(0)2-953010",null,null), + NorthwindFactory.Supplier(16,"Bigfoot Breweries","Cheryl Saylor","Regional Account Rep.","3400 - 8th Avenue Suite 210","Bend","OR","97101","USA","(503) 555-9931",null,null), + NorthwindFactory.Supplier(17,"Svensk Sjfda AB","Michael Bjrn","Sales Representative","Brovallavgen 231","Stockholm",null,"S-123 45","Sweden","08-123 45 67",null,null), + NorthwindFactory.Supplier(18,"Aux joyeux ecclsiastiques","Guylne Nodier","Sales Manager","203, Rue des Francs-Bourgeois","Paris",null,"75004","France","(1) 03.83.00.68","(1) 03.83.00.62",null), + NorthwindFactory.Supplier(19,"New England Seafood Cannery","Robb Merchant","Wholesale Account Agent","Order Processing Dept. 2100 Paul Revere Blvd.","Boston","MA","02134","USA","(617) 555-3267","(617) 555-3389",null), + NorthwindFactory.Supplier(20,"Leka Trading","Chandra Leka","Owner","471 Serangoon Loop, Suite #402","Singapore",null,"0512","Singapore","555-8787",null,null), + + NorthwindFactory.Supplier(21,"Lyngbysild","Niels Petersen","Sales Manager","Lyngbysild Fiskebakken 10","Lyngby",null,"2800","Denmark","43844108","43844115",null), + NorthwindFactory.Supplier(22,"Zaanse Snoepfabriek","Dirk Luchte","Accounting Manager","Verkoop Rijnweg 22","Zaandam",null,"9999 ZZ","Netherlands","(12345) 1212","(12345) 1210",null), + NorthwindFactory.Supplier(23,"Karkki Oy","Anne Heikkonen","Product Manager","Valtakatu 12","Lappeenranta",null,"53120","Finland","(953) 10956",null,null), + NorthwindFactory.Supplier(24,"G'day, Mate","Wendy Mackenzie","Sales Representative","170 Prince Edward Parade Hunter's Hill","Sydney","NSW","2042","Australia","(02) 555-5914","(02) 555-4873","G'day Mate (on the World Wide Web)#http://www.microsoft.com/accessdev/sampleapps/gdaymate.htm#"), + NorthwindFactory.Supplier(25,"Ma Maison","Jean-Guy Lauzon","Marketing Manager","2960 Rue St. Laurent","Montral","Qubec","H1J 1C3","Canada","(514) 555-9022",null,null), + NorthwindFactory.Supplier(26,"Pasta Buttini s.r.l.","Giovanni Giudici","Order Administrator","Via dei Gelsomini, 153","Salerno",null,"84100","Italy","(089) 6547665","(089) 6547667",null), + NorthwindFactory.Supplier(27,"Escargots Nouveaux","Marie Delamare","Sales Manager","22, rue H. Voiron","Montceau",null,"71300","France","85.57.00.07",null,null), + NorthwindFactory.Supplier(28,"Gai pturage","Eliane Noz","Sales Representative","Bat. B 3, rue des Alpes","Annecy",null,"74000","France","38.76.98.06","38.76.98.58",null), + NorthwindFactory.Supplier(29,"Forts d'rables","Chantal Goulet","Accounting Manager","148 rue Chasseur","Ste-Hyacinthe","Qubec","J2S 7S8","Canada","(514) 555-2955","(514) 555-2921",null), + }; + + Regions = new List { + NorthwindFactory.Region(1,"Eastern"), + NorthwindFactory.Region(2,"Western"), + NorthwindFactory.Region(3,"Northern"), + NorthwindFactory.Region(4,"Southern"), + }; + + Territories = new List { + NorthwindFactory.Territory("01581","Westboro",1), + NorthwindFactory.Territory("01730","Bedford",1), + NorthwindFactory.Territory("01833","Georgetow",1), + NorthwindFactory.Territory("02116","Boston",1), + NorthwindFactory.Territory("02139","Cambridge",1), + NorthwindFactory.Territory("02184","Braintree",1), + NorthwindFactory.Territory("02903","Providence",1), + NorthwindFactory.Territory("03049","Hollis",3), + NorthwindFactory.Territory("03801","Portsmouth",3), + NorthwindFactory.Territory("06897","Wilton",1), + NorthwindFactory.Territory("07960","Morristown",1), + NorthwindFactory.Territory("08837","Edison",1), + NorthwindFactory.Territory("10019","New York",1), + NorthwindFactory.Territory("10038","New York",1), + NorthwindFactory.Territory("11747","Mellvile",1), + NorthwindFactory.Territory("14450","Fairport",1), + NorthwindFactory.Territory("19428","Philadelphia",3), + NorthwindFactory.Territory("19713","Neward",1), + NorthwindFactory.Territory("20852","Rockville",1), + NorthwindFactory.Territory("27403","Greensboro",1), + NorthwindFactory.Territory("27511","Cary",1), + NorthwindFactory.Territory("29202","Columbia",4), + NorthwindFactory.Territory("30346","Atlanta",4), + NorthwindFactory.Territory("31406","Savannah",4), + NorthwindFactory.Territory("32859","Orlando",4), + NorthwindFactory.Territory("33607","Tampa",4), + NorthwindFactory.Territory("40222","Louisville",1), + NorthwindFactory.Territory("44122","Beachwood",3), + NorthwindFactory.Territory("45839","Findlay",3), + NorthwindFactory.Territory("48075","Southfield",3), + NorthwindFactory.Territory("48084","Troy",3), + NorthwindFactory.Territory("48304","Bloomfield Hills",3), + NorthwindFactory.Territory("53404","Racine",3), + NorthwindFactory.Territory("55113","Roseville",3), + NorthwindFactory.Territory("55439","Minneapolis",3), + NorthwindFactory.Territory("60179","Hoffman Estates",2), + NorthwindFactory.Territory("60601","Chicago",2), + NorthwindFactory.Territory("72716","Bentonville",4), + NorthwindFactory.Territory("75234","Dallas",4), + NorthwindFactory.Territory("78759","Austin",4), + NorthwindFactory.Territory("80202","Denver",2), + NorthwindFactory.Territory("80909","Colorado Springs",2), + NorthwindFactory.Territory("85014","Phoenix",2), + NorthwindFactory.Territory("85251","Scottsdale",2), + NorthwindFactory.Territory("90405","Santa Monica",2), + NorthwindFactory.Territory("94025","Menlo Park",2), + NorthwindFactory.Territory("94105","San Francisco",2), + NorthwindFactory.Territory("95008","Campbell",2), + NorthwindFactory.Territory("95054","Santa Clara",2), + NorthwindFactory.Territory("95060","Santa Cruz",2), + NorthwindFactory.Territory("98004","Bellevue",2), + NorthwindFactory.Territory("98052","Redmond",2), + NorthwindFactory.Territory("98104","Seattle",2), + }; + + EmployeeTerritories = new List { + NorthwindFactory.EmployeeTerritory(1,"06897"), + NorthwindFactory.EmployeeTerritory(1,"19713"), + NorthwindFactory.EmployeeTerritory(2,"01581"), + NorthwindFactory.EmployeeTerritory(2,"01730"), + NorthwindFactory.EmployeeTerritory(2,"01833"), + NorthwindFactory.EmployeeTerritory(2,"02116"), + NorthwindFactory.EmployeeTerritory(2,"02139"), + NorthwindFactory.EmployeeTerritory(2,"02184"), + NorthwindFactory.EmployeeTerritory(2,"40222"), + NorthwindFactory.EmployeeTerritory(3,"30346"), + NorthwindFactory.EmployeeTerritory(3,"31406"), + NorthwindFactory.EmployeeTerritory(3,"32859"), + NorthwindFactory.EmployeeTerritory(3,"33607"), + NorthwindFactory.EmployeeTerritory(4,"20852"), + NorthwindFactory.EmployeeTerritory(4,"27403"), + NorthwindFactory.EmployeeTerritory(4,"27511"), + NorthwindFactory.EmployeeTerritory(5,"02903"), + NorthwindFactory.EmployeeTerritory(5,"07960"), + NorthwindFactory.EmployeeTerritory(5,"08837"), + NorthwindFactory.EmployeeTerritory(5,"10019"), + NorthwindFactory.EmployeeTerritory(5,"10038"), + NorthwindFactory.EmployeeTerritory(5,"11747"), + NorthwindFactory.EmployeeTerritory(5,"14450"), + NorthwindFactory.EmployeeTerritory(6,"85014"), + NorthwindFactory.EmployeeTerritory(6,"85251"), + NorthwindFactory.EmployeeTerritory(6,"98004"), + NorthwindFactory.EmployeeTerritory(6,"98052"), + NorthwindFactory.EmployeeTerritory(6,"98104"), + NorthwindFactory.EmployeeTerritory(7,"60179"), + NorthwindFactory.EmployeeTerritory(7,"60601"), + NorthwindFactory.EmployeeTerritory(7,"80202"), + NorthwindFactory.EmployeeTerritory(7,"80909"), + NorthwindFactory.EmployeeTerritory(7,"90405"), + NorthwindFactory.EmployeeTerritory(7,"94025"), + NorthwindFactory.EmployeeTerritory(7,"94105"), + NorthwindFactory.EmployeeTerritory(7,"95008"), + NorthwindFactory.EmployeeTerritory(7,"95054"), + NorthwindFactory.EmployeeTerritory(7,"95060"), + NorthwindFactory.EmployeeTerritory(8,"19428"), + NorthwindFactory.EmployeeTerritory(8,"44122"), + NorthwindFactory.EmployeeTerritory(8,"45839"), + NorthwindFactory.EmployeeTerritory(8,"53404"), + NorthwindFactory.EmployeeTerritory(9,"03049"), + NorthwindFactory.EmployeeTerritory(9,"03801"), + NorthwindFactory.EmployeeTerritory(9,"48075"), + NorthwindFactory.EmployeeTerritory(9,"48084"), + NorthwindFactory.EmployeeTerritory(9,"48304"), + NorthwindFactory.EmployeeTerritory(9,"55113"), + NorthwindFactory.EmployeeTerritory(9,"55439"), + }; + + CustomerCustomerDemos = new List(); + } + + public static List Categories { get; set; } + public static List Customers { get; set; } + public static List Employees { get; set; } + public static List Shippers { get; set; } + public static List Suppliers { get; set; } + public static List Orders { get; set; } + public static List Products { get; set; } + public static List OrderDetails { get; set; } + public static List CustomerCustomerDemos { get; set; } + public static List Regions { get; set; } + public static List Territories { get; set; } + public static List EmployeeTerritories { get; set; } + + public static DateTime ToDateTime(string dateTime) + { + var dateParts = dateTime.Split('/'); + return new DateTime(int.Parse(dateParts[2]), int.Parse(dateParts[0]), int.Parse(dateParts[1])); + } + + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/DataModel/NorthwindDtoData.cs b/tests/Northwind.Common/DataModel/NorthwindDtoData.cs new file mode 100644 index 000000000..a1b65d81c --- /dev/null +++ b/tests/Northwind.Common/DataModel/NorthwindDtoData.cs @@ -0,0 +1,230 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using Northwind.Common.ServiceModel; + +namespace Northwind.Common.DataModel +{ + [DataContract] + public class NorthwindDtoData + { + public static NorthwindDtoData Instance = new NorthwindDtoData(); + + [DataMember] + public List Categories { get; set; } + [DataMember] + public List Customers { get; set; } + [DataMember] + public List Employees { get; set; } + [DataMember] + public List Shippers { get; set; } + [DataMember] + public List Suppliers { get; set; } + [DataMember] + public List Orders { get; set; } + [DataMember] + public List Products { get; set; } + [DataMember] + public List OrderDetails { get; set; } + [DataMember] + public List CustomerCustomerDemos { get; set; } + [DataMember] + public List Regions { get; set; } + [DataMember] + public List Territories { get; set; } + [DataMember] + public List EmployeeTerritories { get; set; } + + public static void LoadData(bool loadImages) + { + NorthwindData.LoadData(loadImages); + + Instance = new NorthwindDtoData + { + Categories = NorthwindData.Categories.ConvertAll(x => ToCategoryDto(x)), + Customers = NorthwindData.Customers.ConvertAll(x => ToCustomerDto(x)), + Employees = NorthwindData.Employees.ConvertAll(x => ToEmployeeDto(x)), + Shippers = NorthwindData.Shippers.ConvertAll(x => ToShipperDto(x)), + Suppliers = NorthwindData.Suppliers.ConvertAll(x => ToSupplierDto(x)), + Orders = NorthwindData.Orders.ConvertAll(x => ToOrderDto(x)), + Products = NorthwindData.Products.ConvertAll(x => ToProduct(x)), + OrderDetails = NorthwindData.OrderDetails.ConvertAll(x => ToOrderDetailDto(x)), + CustomerCustomerDemos = NorthwindData.CustomerCustomerDemos.ConvertAll(x => ToCustomerCustomerDemoDto(x)), + Regions = NorthwindData.Regions.ConvertAll(x => ToRegionDto(x)), + Territories = NorthwindData.Territories.ConvertAll(x => ToTerritoryDto(x)), + EmployeeTerritories = NorthwindData.EmployeeTerritories.ConvertAll(x => ToEmployeeTerritoryDto(x)), + }; + } + + public static CategoryDto ToCategoryDto(Category model) + { + return new CategoryDto + { + CategoryName = model.CategoryName, + Description = model.Description, + Id = model.Id, + Picture = model.Picture, + }; + } + + public static CustomerDto ToCustomerDto(Customer model) + { + return new CustomerDto + { + Id = model.Id, + Picture = model.Picture, + CompanyName = model.CompanyName, + ContactName = model.ContactName, + ContactTitle = model.ContactTitle, + Fax = model.Fax, + Phone = model.Phone, + Address = model.Address, + City = model.City, + Country = model.Country, + PostalCode = model.PostalCode, + Region = model.Region, + }; + } + + public static EmployeeDto ToEmployeeDto(Employee model) + { + return new EmployeeDto + { + Id = model.Id, + Address = model.Address, + City = model.City, + Country = model.Country, + PostalCode = model.PostalCode, + Region = model.Region, + BirthDate = model.BirthDate, + Extension = model.Extension, + FirstName = model.FirstName, + HireDate = model.HireDate, + HomePhone = model.HomePhone, + LastName = model.LastName, + Notes = model.Notes, + Photo = model.Photo, + PhotoPath = model.PhotoPath, + ReportsTo = model.ReportsTo, + Title = model.Title, + TitleOfCourtesy = model.TitleOfCourtesy, + }; + } + + public static ShipperDto ToShipperDto(Shipper model) + { + return new ShipperDto + { + Id = model.Id, + CompanyName = model.CompanyName, + Phone = model.Phone, + }; + } + + public static SupplierDto ToSupplierDto(Supplier model) + { + return new SupplierDto + { + Id = model.Id, + CompanyName = model.CompanyName, + ContactName = model.ContactName, + ContactTitle = model.ContactTitle, + Fax = model.Fax, + Phone = model.Phone, + Address = model.Address, + City = model.City, + Country = model.Country, + PostalCode = model.PostalCode, + Region = model.Region, + HomePage = model.HomePage, + }; + } + + public static OrderDto ToOrderDto(Order model) + { + return new OrderDto + { + Id = model.Id, + CustomerId = model.CustomerId, + EmployeeId = model.EmployeeId, + Freight = model.Freight, + OrderDate = model.OrderDate, + RequiredDate = model.RequiredDate, + ShipAddress = model.ShipAddress, + ShipCity = model.ShipCity, + ShipCountry = model.ShipCountry, + ShipName = model.ShipName, + ShippedDate = model.ShippedDate, + ShipPostalCode = model.ShipPostalCode, + ShipRegion = model.ShipRegion, + ShipVia = model.ShipVia, + }; + } + + public static ProductDto ToProduct(Product model) + { + return new ProductDto + { + Id = model.Id, + CategoryId = model.CategoryId, + Discontinued = model.Discontinued, + ProductName = model.ProductName, + QuantityPerUnit = model.QuantityPerUnit, + ReorderLevel = model.ReorderLevel, + SupplierId = model.SupplierId, + UnitPrice = model.UnitPrice, + UnitsInStock = model.UnitsInStock, + UnitsOnOrder = model.UnitsOnOrder, + }; + } + + public static OrderDetailDto ToOrderDetailDto(OrderDetail model) + { + return new OrderDetailDto + { + Discount = model.Discount, + OrderId = model.OrderId, + ProductId = model.ProductId, + Quantity = model.Quantity, + UnitPrice = model.UnitPrice, + }; + } + + public static CustomerCustomerDemoDto ToCustomerCustomerDemoDto(CustomerCustomerDemo model) + { + return new CustomerCustomerDemoDto + { + Id = model.Id, + CustomerTypeId = model.CustomerTypeId, + }; + } + + public static RegionDto ToRegionDto(Region model) + { + return new RegionDto + { + Id = model.Id, + RegionDescription = model.RegionDescription, + }; + } + + public static TerritoryDto ToTerritoryDto(Territory model) + { + return new TerritoryDto + { + Id = model.Id, + RegionId = model.RegionId, + TerritoryDescription = model.TerritoryDescription, + }; + } + + public static EmployeeTerritoryDto ToEmployeeTerritoryDto(EmployeeTerritory model) + { + return new EmployeeTerritoryDto + { + EmployeeId = model.EmployeeId, + TerritoryId = model.TerritoryId, + }; + } + + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/DataModel/NorthwindFactory.cs b/tests/Northwind.Common/DataModel/NorthwindFactory.cs new file mode 100644 index 000000000..9cbaea63f --- /dev/null +++ b/tests/Northwind.Common/DataModel/NorthwindFactory.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Northwind.Common.DataModel +{ + public static class NorthwindFactory + { + public static readonly List ModelTypes = new List { + typeof(Employee), + typeof(Category), + typeof(Customer), + typeof(Shipper), + typeof(Supplier), + typeof(Order), + typeof(Product), + typeof(OrderDetail), + typeof(CustomerCustomerDemo), + typeof(Category), + typeof(CustomerDemographic), + typeof(Region), + typeof(Territory), + typeof(EmployeeTerritory), + }; + + public static Category Category(int id, string categoryName, string description, byte[] picture) + { + return new Category + { + Id = id, + CategoryName = categoryName, + Description = description, + Picture = picture + }; + } + + public static Customer Customer( + string customerId, string companyName, string contactName, string contactTitle, + string address, string city, string region, string postalCode, string country, + string phoneNo, string faxNo, + byte[] picture) + { + return new Customer + { + Id = customerId, + CompanyName = companyName, + ContactName = contactName, + ContactTitle = contactTitle, + Address = address, + City = city, + Region = region, + PostalCode = postalCode, + Country = country, + Phone = phoneNo, + Fax = faxNo, + Picture = picture + }; + } + + public static Employee Employee( + int employeeId, string lastName, string firstName, string title, + string titleOfCourtesy, DateTime? birthDate, DateTime? hireDate, + string address, string city, string region, string postalCode, string country, + string homePhone, string extension, + byte[] photo, + string notes, int? reportsTo, string photoPath) + { + return new Employee + { + Id = employeeId, + LastName = lastName, + FirstName = firstName, + Title = title, + TitleOfCourtesy = titleOfCourtesy, + BirthDate = birthDate, + HireDate = hireDate, + Address = address, + City = city, + Region = region, + PostalCode = postalCode, + Country = country, + HomePhone = homePhone, + Extension = extension, + Photo = photo, + Notes = notes, + ReportsTo = reportsTo, + PhotoPath = photoPath, + }; + } + + public static Shipper Shipper(int id, string companyName, string phoneNo) + { + return new Shipper + { + Id = id, + CompanyName = companyName, + Phone = phoneNo, + }; + } + + public static Supplier Supplier( + int supplierId, string companyName, string contactName, string contactTitle, + string address, string city, string region, string postalCode, string country, + string phoneNo, string faxNo, + string homePage) + { + return new Supplier + { + Id = supplierId, + CompanyName = companyName, + ContactName = contactName, + ContactTitle = contactTitle, + Address = address, + City = city, + Region = region, + PostalCode = postalCode, + Country = country, + Phone = phoneNo, + Fax = faxNo, + HomePage = homePage + }; + } + + public static Order Order( + int orderId, string customerId, int employeeId, DateTime? orderDate, DateTime? requiredDate, + DateTime? shippedDate, int shipVia, decimal freight, string shipName, + string address, string city, string region, string postalCode, string country) + { + return new Order + { + Id = orderId, + CustomerId = customerId, + EmployeeId = employeeId, + OrderDate = orderDate, + RequiredDate = requiredDate, + ShippedDate = shippedDate, + ShipVia = shipVia, + Freight = freight, + ShipName = shipName, + ShipAddress = address, + ShipCity = city, + ShipRegion = region, + ShipPostalCode = postalCode, + ShipCountry = country, + }; + } + + public static Product Product( + int productId, string productName, int supplierId, int categoryId, + string qtyPerUnit, decimal unitPrice, short unitsInStock, + short unitsOnOrder, short reorderLevel, bool discontinued) + { + return new Product + { + Id = productId, + ProductName = productName, + SupplierId = supplierId, + CategoryId = categoryId, + QuantityPerUnit = qtyPerUnit, + UnitPrice = unitPrice, + UnitsInStock = unitsInStock, + UnitsOnOrder = unitsOnOrder, + ReorderLevel = reorderLevel, + Discontinued = discontinued, + }; + } + + public static OrderDetail OrderDetail( + int orderId, int productId, decimal unitPrice, short quantity, double discount) + { + return new OrderDetail + { + OrderId = orderId, + ProductId = productId, + UnitPrice = unitPrice, + Quantity = quantity, + Discount = discount, + }; + } + + public static CustomerCustomerDemo CustomerCustomerDemo( + string customerId, string customerTypeId) + { + return new CustomerCustomerDemo + { + Id = customerId, + CustomerTypeId = customerTypeId, + }; + } + + public static Region Region( + int regionId, string regionDescription) + { + return new Region + { + Id = regionId, + RegionDescription = regionDescription, + }; + } + + public static Territory Territory( + string territoryId, string territoryDescription, int regionId) + { + return new Territory + { + Id = territoryId, + TerritoryDescription = territoryDescription, + RegionId = regionId, + }; + } + + public static EmployeeTerritory EmployeeTerritory( + int employeeId, string territoryId) + { + return new EmployeeTerritory + { + EmployeeId = employeeId, + TerritoryId = territoryId, + }; + } + + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/DataModel/OrderBlob.cs b/tests/Northwind.Common/DataModel/OrderBlob.cs new file mode 100644 index 000000000..b1868d6c7 --- /dev/null +++ b/tests/Northwind.Common/DataModel/OrderBlob.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ServiceStack.DataAnnotations; +using ServiceStack.Model; + +namespace Northwind.Common.DataModel +{ + public class OrderBlob + : IHasIntId, IEquatable + { + public OrderBlob() + { + this.OrderDetails = new List(); + } + + [AutoIncrement] + public int Id { get; set; } + + public Customer Customer { get; set; } + + public Employee Employee { get; set; } + + public DateTime? OrderDate { get; set; } + + public DateTime? RequiredDate { get; set; } + + public DateTime? ShippedDate { get; set; } + + public int? ShipVia { get; set; } + + public decimal Freight { get; set; } + + public string ShipName { get; set; } + + public string ShipAddress { get; set; } + + public string ShipCity { get; set; } + + public string ShipRegion { get; set; } + + public string ShipPostalCode { get; set; } + + public string ShipCountry { get; set; } + + public List OrderDetails { get; set; } + + public List IntIds { get; set; } + + public Dictionary CharMap { get; set; } + + public static OrderBlob Create(int orderId) + { + return new OrderBlob + { + Id = orderId, + Customer = NorthwindFactory.Customer("ALFKI", "Alfreds Futterkiste", "Maria Anders", "Sales Representative", "Obere Str. 57", "Berlin", null, "12209", "Germany", "030-0074321", "030-0076545", null), + Employee = NorthwindFactory.Employee(1, "Davolio", "Nancy", "Sales Representative", "Ms.", NorthwindData.ToDateTime("12/08/1948"), NorthwindData.ToDateTime("05/01/1992"), "507 - 20th Ave. E. Apt. 2A", "Seattle", "WA", "98122", "USA", "(206) 555-9857", "5467", null, "Education includes a BA in psychology from Colorado State University in 1970. She also completed 'The Art of the Cold Call.' Nancy is a member of Toastmasters International.", 2, "http://accweb/emmployees/davolio.bmp"), + OrderDate = NorthwindData.ToDateTime("7/4/1996"), + RequiredDate = NorthwindData.ToDateTime("8/1/1996"), + ShippedDate = NorthwindData.ToDateTime("7/16/1996"), + ShipVia = 5, + Freight = 32.38m, + ShipName = "Vins et alcools Chevalier", + ShipAddress = "59 rue de l'Abbaye", + ShipCity = "Reims", + ShipRegion = null, + ShipPostalCode = "51100", + ShipCountry = "France", + OrderDetails = new List { + new OrderDetailBlob { ProductId = 11, UnitPrice = 11, Quantity = 14, Discount = 0}, + new OrderDetailBlob { ProductId = 42, UnitPrice = 9.8m, Quantity = 10, Discount = 0}, + new OrderDetailBlob { ProductId = 72, UnitPrice = 34.8m, Quantity = 5, Discount = 0}, + }, + IntIds = new List { 10, 20, 30 }, + CharMap = new Dictionary + { + {1,"A"}, + {2,"B"}, + {3,"C"}, + } + }; + } + + public bool Equals(OrderBlob other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && Equals(Customer, other.Customer) && Equals(Employee, other.Employee) && OrderDate.Equals(other.OrderDate) && RequiredDate.Equals(other.RequiredDate) && ShippedDate.Equals(other.ShippedDate) && ShipVia == other.ShipVia && Freight == other.Freight && string.Equals(ShipName, other.ShipName) && string.Equals(ShipAddress, other.ShipAddress) && string.Equals(ShipCity, other.ShipCity) && string.Equals(ShipRegion, other.ShipRegion) && string.Equals(ShipPostalCode, other.ShipPostalCode) && string.Equals(ShipCountry, other.ShipCountry) && Equals(OrderDetails, other.OrderDetails) && Equals(IntIds, other.IntIds) && Equals(CharMap, other.CharMap); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((OrderBlob) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode*397) ^ (Customer != null ? Customer.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Employee != null ? Employee.GetHashCode() : 0); + hashCode = (hashCode*397) ^ OrderDate.GetHashCode(); + hashCode = (hashCode*397) ^ RequiredDate.GetHashCode(); + hashCode = (hashCode*397) ^ ShippedDate.GetHashCode(); + hashCode = (hashCode*397) ^ ShipVia.GetHashCode(); + hashCode = (hashCode*397) ^ Freight.GetHashCode(); + hashCode = (hashCode*397) ^ (ShipName != null ? ShipName.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipAddress != null ? ShipAddress.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipCity != null ? ShipCity.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipRegion != null ? ShipRegion.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipPostalCode != null ? ShipPostalCode.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ShipCountry != null ? ShipCountry.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (OrderDetails != null ? OrderDetails.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (IntIds != null ? IntIds.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (CharMap != null ? CharMap.GetHashCode() : 0); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/DataModel/OrderDetailBlob.cs b/tests/Northwind.Common/DataModel/OrderDetailBlob.cs new file mode 100644 index 000000000..4f0b9c55e --- /dev/null +++ b/tests/Northwind.Common/DataModel/OrderDetailBlob.cs @@ -0,0 +1,42 @@ +using System; + +namespace Northwind.Common.DataModel +{ + public class OrderDetailBlob : IEquatable + { + public int ProductId { get; set; } + + public decimal UnitPrice { get; set; } + + public short Quantity { get; set; } + + public double Discount { get; set; } + + public bool Equals(OrderDetailBlob other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return ProductId == other.ProductId && UnitPrice == other.UnitPrice && Quantity == other.Quantity && Discount.Equals(other.Discount); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((OrderDetailBlob) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = ProductId; + hashCode = (hashCode*397) ^ UnitPrice.GetHashCode(); + hashCode = (hashCode*397) ^ Quantity.GetHashCode(); + hashCode = (hashCode*397) ^ Discount.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/Northwind.Common.csproj b/tests/Northwind.Common/Northwind.Common.csproj new file mode 100644 index 000000000..3d8f226f4 --- /dev/null +++ b/tests/Northwind.Common/Northwind.Common.csproj @@ -0,0 +1,46 @@ + + + + net472;net6.0 + Northwind.Common + Northwind.Common + false + false + false + false + false + false + false + false + false + + + + true + true + + + + portable + + + + + + + + + $(DefineConstants);NET45 + + + + + + + + + + $(DefineConstants);NETSTANDARD2_0 + + + \ No newline at end of file diff --git a/tests/Northwind.Common/NorthwindResources.cs b/tests/Northwind.Common/NorthwindResources.cs new file mode 100644 index 000000000..73fe0dc0c --- /dev/null +++ b/tests/Northwind.Common/NorthwindResources.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Northwind.Common.DataModel; + +namespace Northwind.Common +{ + public static class NorthwindResources + { + public static Dictionary Images { get; set; } + + static NorthwindResources() + { + Func catKey = GetImageKey; + Func empKey = GetImageKey; + + Images = new Dictionary { + {catKey(1), ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E5069637475726500010500000200000007000000504272757368000000000000000000A0290000424D98290000000000005600000028000000AC00000078000000010004000000000000000000880B0000880B00000800000008000000FFFFFF0000FFFF00FF00FF000000FF00FFFF000000FF0000FF00000000000000000010000000001000000001001000000000000001010100010000100100000001010001000000100100000100000010101000100000100010000000000000000000001000000001000000000000001000000000000000000000001001001000000000000000000001001001000012507070100000001000000001000100100000010000001000000000100100100000001001010101010000000000000000000001001000100000101010101000000000000000000000000000000000000101020525363777777777777753530100101000100000000001010001001000100100100000000000100000000000000000100010001001010001000000010010000000000000100000000000000000000000000000001014343577777777777777777777777777770100120001010100102000000000000000000000000100100010010000000000100010000000000000000010010000001001001010100000000000000000000000000000001021617777777775757535353525777777777777770150120000100010101000000000000000001000000000000001001001000000000010010000010010010000101001001000000100000001000000000000000000000001417777737617376377775767771707752777777776340161210300000000010000000000010000000000000000000000000000000000000000100000000000100000000000010000100000000000000000000000000100503677577571705014000000101070717775777777775334101400010101010001010010101000000000000000000000000000001000000000000000000000000000000001010001000001000000000000000000000000010357353000000000000000000000000000171777777777470612101000000001000001000000010000000000000000000000000000000100010703010101210000000000000000000000000000000000000000000000101034350000000010653435777570477474700000107757777171000000101001000101000101010000100000000000000000001041477110131035750020040477301000100000000000000000000000000000000000000000010004014176577707000067777777776560404007371734361701400001241000001000000001000020400000200000101747767777000101073777577777775372100000000100000000000000100100000000010000010000037676476700004000577470416717477421405000434140343000565120510100301014002107171370701437777777775777777112103177777777777705757771010000000100000000000000010000100000101000470440440671007477776777777614060600742524001777777747646776752060250003021716737777777777777774747727574777001016777777777767173317703000101010000000010000000001000000000000420460200077427477767777777775677777746474656606767777665746776757151253507166737777733746777577777777572776771100517777777767776015713141030020001000000001001000000000010100067440044047761477767776706767777674765673470657505767375664746757777271252167717733771737776777677567476577577121103775777777776717027753121010101000010000000000100001010000010767060004776747776776777756776777777777042477676067777467474747676777565256531311771177376477777576757777747710110701777777767777401717340000000000100001000000000001000000101004776540666050777677657777677470774777776747664607777376747476777777677717173735377717737747777777777774774770011271077777767777763171735210121010100000000000000000000010000000300406767757676775077006767477774774777774747770476777656706746777657777777777777777777737667777476574774777771001031777777776767741371771000000000010000000000000000000000101005707442567656176006767004770476707700767770000477747734656446745677676777777777777777777375667777777777777777773100010777777777777771753521010101010000000000100010000000010007777712152503004670077774767427047247776577564700076737747670474277777777777777777367777777765777777777777434777750757775377767676770172773100000000001000000000000001000101007170701614204046007746040676167470774167743656777740077776067407465677677777777777757717777737476775716161243410303077777777577775210000011350001001001000000101000100000100002100171525675070074340670005004076700706570757777767770077744746466567777677777777777777777773776777610000000137775350317777773777737750701000101021001000100000000000010100010010300067777761650604065047604760746404776406705656776770077764750474747677777777777777777773733747777773011735777777777777777757777777777767412041001001000001000001000000010001000577744140000607406706767676776777776477756767777447700774076646764777567777777777777737373737764677747753527777776777777777776365735353513010300120301010000000000000000001000107000210006147767674646040404040066667767677775476777046644644044456776767777777777733737373776777776774244777377717712777165357577534242040010010010000010001000000100010000100300050000146664000000101030734352101100065677767077770047604774377676777767777777777373737333756477657075377100770770177776525271673001012101210301001030000000100100000001000005000060046160004000125343510110101000000000007740000047744733737377757677777777777373737377737656757777777373101676770777717775671773001010300000021021010000000000000100000000100077400000414021414000000000000000000000000000300000777777773737377677677777777777373737333735677677777377710177777717774705271767340300000010101000100000100000100100000001014005660000000737560600000000000000000000000000004730777773733373737777747677777777737337353761666777777737737017771677077353777574735310012101000000010010100000100000000010004300065400000000400141254140404000000000000000000037737776773777373733777677777777777677646746565756747777773773017017710765654352735770017010303031010010000000100001001010030704000660000000000000040000000000000000000000000007777514673373373737777777476777777777474644764666776777777772711031076117307374357477373010341050043030012100100010100100012500000047000000000046742000000000000000000000000077776677777377377373733737767777777777767645676507574777657610057121101731611574777637735105270125213010050210100001070210301650000000640000000006776406776464000040641434177777767667614737337337373777777767677777776564767474664667477761775271112116101002331211101052721016120140161034106010173075617770000570047400047400446000000467770504777767173573756767776767737737773737377776777777777776564746765477576777176700774656474731010011000001250165214716170121012011070777173777400063770040000760467600000000740760600777067777777676767676767337333373737377747677777777776767747424676747677157701677677676131331213131301371317310312161525053073077777777700047577700000006006760000640400006474046740777777777676767676737737777373777777767777777777674746767467477777743670175305325352527135335353170143414371617130131211777177777777001737770006760476677047064466400047640077747777777767676767673773373333737373776746777777765467674704747674765375610731773573752534737417017035303130101010030001427777777737770047777770047460704644064400004640067004767077777777767676767673377377773737777776777777777766565665670767767775077007563153347370731013213617034343434307031417121177773777777740257737700027447000064000000000640064006760777777776767767767773373333373737777476777777777746765674464747767763477027172753717175777757757357171717171717433616163777777735737400737773400460660046000000000004000600676747777777776766766767377377777373737777747677777776756567467746765777117100537153353773777777777777777777777777737757737573777773773772047777350000474044600440000000000040047774007777700667677677633333333333737777766767777777777667476564657476760600007353375373177777777777777777777777777777737777377733753777740007177770000664024640640000000000004646700477777007767667666777777777773737777777777777776777446467565676777535373525317137177177777777777777777777777777777775377773753771737700076737350000000474664665646644400400464000077700067677677773333333333373737776676777777777767777766767765677771713175217037173777777777777777777777777775375377173753777377773700057777004007477667764766767667467600000004770000767667666677777777777337777775677777771777772604000404067761171613131535353717777777777777777777737753777777777753773717735374700000500670446677777776777776777776561004661000006767767777333131101100777777666567777567704040505140777716536353147173135371777777777777777777577577777777777777777353753777371700000001776040404040404606076767776170000470000071101100100000000000110157177776777776470124100002530004777111301313017535371777777777771771737377773777377753773717353252165376164464265700400000000000004040040076774000440000777500750000000000000000017347766777746564100000000400300652513530753303170737777775777777777777737777777773777753757035353134317137313533000046440000004400000000053770000000000077343100000000000000000004135777775676176000700000004044213052153115353371357777377737737775777777573757777777353213503161617163521657257000006700060042400000005273710000000000007577000000000000000000531117777665447405244000000040031501313030721353537737775777577753737717737777777777777035343343131303103171317337130100000567000200000031756000000000000000077771012100101101131117133375466747465707047000071502161011531534353517753773737353777737777777777737537713503353170717173561343105307030525370047014161717433700000000000000000000101011770000006402737373767456467777777773065773510137343531317073737773775777773777577375735737577777343375377373673071316352731717173137000007737352713574000000000000000000000000464000000046733737373446647777777777740007373737110310343537171773565373537577177375737777777777773353737717175357727753717163737357770000071735371677700000000000000000000000000460004004676173737374745777777777777004631713112031213131317337177737777777377777777777777777777775377737777377371717353773571747737377617771677773570000000000000000000000000400400000000406337333464673777777777774007733373311001013135317177737775377377777777777777777777777737777573777377777736771773773716717535343373525773700000000000000000000000000000000000000037337374433373777777777700007740010313133173137337357753777777777777777777777777777777737737775375737373717367171653735727367374753737174000000000000000000004600000000000000000373733643773373777777777404073000000000012137331737377777777777777777777777777777777777577773737773757575735317273353531757535737377576300000000000000000000424400000004000040007373375733337333377777770000700000000000000000070477777777777777777777777777777777777737773757753757373737777775357273673373773535737357000000000000000000004406000000000404004037337333773737737377777700400000000000004006404043777777773757777777777777777777777777773773737773777777717371737357171752573473721777340000000000000000000006446400000000004004337337333373337337337777100004705340100016503777747717717757777777777777777777777777773757757773577173577775777577377773777373757777177700000000000660000640047674000000004000003737337373377337373737774040077760004000000044004737777777777777777777777777777777777773773773577377777377377377377537177535757373537710000000000004040004640604600000000000400073733737337333737373777700000047477420000000000435777777777777777777777777777777777777757777777777777777777777777777777737737377577777000000000004600000460064600000004000000000373373337337373737373777600000000000550043617777777777777777777777777777777777777777773777777773777777777777777777777777777777737737777000000000000000000000406400000004040000003373373737337373737373770040000000002777357777777777777777777777777777777777777775777777777773777573717775777777577377777777777777757340400000000000000040004064000000000000000073373373337333373737377750000000000057777777777777777777777777777777177775737577737777777735777773777773773775377377735735735375737737000000000000000044600406060000000000000046337337337373777373733777007460000000377777777777777777777777777777777737737777377777377777737371775353753753777777777777777777737717750000000000000000000000444404400400000000063733737337333337373377774067400000000777777777777777777777777375773777757777177177377735777777777377777777777777777777777777777777777704000000000000000000006000666066000000004433733337337377333377777700676004004407777777777777777777777777777757357375377777775777737777777777777777777777777777777777777777777772010000000000000000000040004404440000000000373737337337337377777777704600674660077777777777777777777777777737777777777773773773777777777777777377377777737777753777777777777777750040000000000000000000000460460000000000463733733733733737777777770047464067000777777777777777777777777777777777777777777777777777771737177777757377377753777777777737757773737000000000000000000000644640000460000000000073373733733733777777773750660760400017777777777777777777777777777777777777777777777777777777777373773777357173775377735737777377757777240000000000000000000606400000000000000000373373773733777777777737604746400406057777777777777777777777777777777777777777775775771733735377757177175737753737537777757777777777750100000000000000000046540000000000000000007337333333777777777771771066067674767677777777777777777777777777777777377777777377737777775737573737736373717375773777373737377777371200400000000000000000046000000000000000000073737373777777777777737700656476464617777777777777777777777777775757777777575757735773735371737357737575357635733577377577777773777775000040000000000000440646000040000000000000733733377777777777777137106606476400077777777777777775777757357777777757577377375777775737777577735737377371735773757073737175777777370000000000000000046764656546400000000000007733737777537777777777774474407467005077777777777775777757377735737717737377777737777371773737373773577535373437073737757577737353777700500000000000004676474266640000000000000047333777074747777777777776567642766027777757537775777371735777777577777777577777775377777777577577777737777577737757757373737777775777000000400000000067407604040000000000000000077777103716173777777737676665646470577757377775777375777777177377777777777357357777773737777777371735737773735753737377777773577377370004000000000000666424604040000000000000000777777007677477777777767676767474003577777777773777777777777777773773573777377773777777577773777777777771775773777757353753577357777770010000000000040406404000244000000000000000777370141477567777777762476767660067777777773777777737773777753777777777777777777777777773777777777375367377375357367767767737673477140240000000000000446400004660000040000000007737520077772757777770040047667767177777757777777777777577737777757777717753717717777777757753535357777775775777777535753735757177357005004000000000000000040400476440464000000007773401616575777777006440004764256777377375775375735737777777737737737773777777777773777777777777771771777777777777773775777377577773000000040000000000400000000000067400000000077771425777367777700400060006765377777377777377737777777735735777777777777777777777757777777777777777777777777777777777777377377353770070040000000000000400000404000040000000000077770525765777777004004040440065773775717377777777377777777777777777777777777777777777777777777777777773737371775377773775657527777500004000000000000000000442424400064000000000777724077576777700400600007000373757373775775375375737777777777777777777777777777777777777737777377373577575777777573575373733771737300700004000000000000004646440000672440000000777507567657775000444040644047777377777773777777777757777777777777777777777777777777757377771777375773737373737373773377753575377577400004000000000000000000400000040440640000004777407757777700404246044604375777757737777777777777777777773777777777777777777777777377775773575737175717175717571757253372734372773007000040000000000000000000004600464000000007772525677777004704064240124373777377577777777777777773773777777573577777777777757377737373777373777737367363727373735356171737177175000400000400000000000000004600000400000000047710477777700676006564640577777777777777777777737773777777577177777777777777777377735775775377757173717535357174352537737373717717730070040000000000000000000040046000000000000077777711357047600446500072777777777737777777377777777573573777777777777777777777737777377377177377757773777377737777343574356773737710060040400400000000000000000400000000000000771571715356770446002470757775773777777377757735735773777777777777777777777777735777377777777777777737573577177535357773777371747527710160000000040000000000000006000000000000007771353777767600056440042735373775377375773777777777777777777777777777777777777777777777777777757377773777377737777735777537577373717700104004000000000000000000440000000000000077171357777674006064214357577775737757777777777777777777777777777777777777777777377777777777777777777777777777777737777373777737577777300424000400000000000000000000000000000000777174777756765404051425373735737777777777777777777777777777777777777777777777777777777777777377777577777777777777777375777737777353777100100400040400000000000000000000000000007717137577764767404061777777777777737737777777777777777377777777737537777777777777777577777777773773777737775377177577737353753737770737100400400000000000000000000000000000000077717177777467760030065377577777777777777777777777377777777777777777777777777777777373735371777775777177753777777737717757775375753573536100050040404040000000000000000000000000771717177720767000043737737737737757737773773777777777777777777777777777777777777577777777777737773777777777777777777773773737737377357753000004200000004040000000000000000000047773537777504004104375777573757777371777777777773777777777777777777777777777777373777777777777777777777777777777777777777757777777377373777200504040404000000400000000000000000077153577770000016075375373777737177777717717777777777777777777777777777777777777777777777777777777777777777777777777777777375373577177573535300100040104004000040400040040004000177353577770070007277377777537777753757777777777777777777777777777777777777777777777777777777757777777773777577777775377537727576377717252734120050040400404040000040000000400007735353777005006535357777737771773777377777777777777777777777777777777777777777777775737777377777717377777777773777777777753753735752771775173500007000040000004004000400400000477717177775004353777737377773777777777777777777777777777777777777777777777777777773737757377173717777773577737777773777773773777773771773136343700000561040405004000400400040400775317777700367771737577537757777777777777777777777777377777777777777777777777775757717777777777737177577377777775777773777353717773771776535353716000047000404004000500050010001735717777761717777573777777777777777777777777777757375777777777777777777777773737737773753777177577737777537537737777757777777771757372537737271717100005252004004040604004040077531717777177777777777777737777777775777777777777777777777777777777777777777757717753757775377737737773777777777777777777177173777737753770775363774320000416524100000400400004773717777777777737777777777777777377377777777777777777777777777777777777777777737773777773777777777577757377377777777377377777753737753771775375757377577600000106141410143405007757537777777777777377737777773777777777777777777777777777777777777777777777777753777737777777777777737777777777777777777777377777573777777377373775373735373000000000400010000077377717777737777757757571777777777777777777777777777777777777777777777777737773777777777777777777777777777777777777777777777777777777737775777777377775777777777161612161637777777101777777771771773777777777777777777777777777777777777777777177577377577757777777777777777777777777777777777737777777777777737737775773737717717771737737537777777777777777775717177777771777777777377773777777777777777777777777777777777777777777777777777777777777777777377377777377777777777377577177537777777373757737737735377735737737377737775773777377717177777777737777777777777777777377777777777777777777777777777777777777777777777357537537777577773775753573577577537377737753757357757357571753777171735735775357537737571777771717577777777777375777375735377377775377777777777777777777777777777777777777777777737777771773753757377377777737777777777773777377737737737377375377777737573537737753773777777777177777777775775737757737777777757377777777777777777777777777777777777777777357777777777777777777777777777777773777777777777777777777777777777777537717773777777777777577777717711737777173737377777377777777177377777777777777777777777777777777777777777777737377777777777777777777777773777777777737775777777777757777775373737777773777377377537737777777710101417777757757377777771735377777777777777777777777777777777777777777777777777777777377777777377377777777777775775775775737777717717371735377575735373757175365737777773737777777773617377373775737773777777777777777777777777777777777777777777777777777777377757177573737777577773575373573737737777773773737777777777777737373777175337637173573537777577717777753775777775377777777777777777777777777777777777777777777777777777777777777773737773777573573753777737777777777773773777577577737353717353577175217437753577377377771737373773777375377375377777777777777777777777777777777777777777777777777777777777777757153471773737373773771737771737377777777777773777737577777777777377737733717373717177737777777577777375377777777777777777777777777777777777777777777777777777777777777777777773737773771757577573577377717777575717377777777777377773717353717357175717577717753777175773773537777777777777777777777777777777777777777777777777777777777777777777777777777777753473535377373717353717171735373737777777777777777737777777777777737737737353735371737737777377777777777777777777777777777777777777777777777777777777777777777777777777777777777773777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777773535000000000000000000000105000000000000C7AD05FE")}, + {catKey(2), ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E5069637475726500010500000200000007000000504272757368000000000000000000A0290000424D98290000000000005600000028000000AC00000078000000010004000000000000000000880B0000880B00000800000008000000FFFFFF0000FFFF00FF00FF000000FF00FFFF000000FF0000FF00000000000000777777777737125353313731773543110105302502105313321714317343717135371373147317317171717121135301610131217777777770146765074747776567616774776565774040371737031611737710110100007777777777717435357353531713343030301103112161705353317353343717135371370317717737371734125031131352171777777714066544724767776774747657700577764774340735757100371507530210777777777777777731737317353731704311151303112110431731305317314171731717171354731713535373107131703011317177777770664576076567476404776147676777674174074740573312411002173611137777777777777353167171735337173531163125351615307173171737171707373173733537023177373737351611010113521737777775245006047474747407777777767657775747477416560075141200115351103077777777777777377143161735717353463107113131303343353171317373107177317173171477135353717370737312503173577777344760547061604760777777777777764677776007470774033001010035212100777777777777777173563535335371731053130707071351165343171701773417357717177130177173717717134101713353173777747640076047447000777777777777777775667570467760774040301010101053107777777777777773712531335337171735301531313134334135353361371350735331737137707137353731737433731717377777776040000407647604777777777777777777460547743565054776011001031213010077777777777777353561737534717337161352171712717103737335137137061373573171711073531737171710351171735373777704740460464746777777777777777777777040667746776007751300530101301300777777777777777373071713713717135241311030711317605117533517171075353357373734173173537373735373773777777770460464740406757777777777777777771777640577740457777000131035310701007777173777777775353431613717357131630731713735311637317173737171235353353535725377777777777777777773737777567404706425046777777777777777757777775246577777767711350131030311300177777357777777737350771731171337510531071351613735534317131717305737357377373077777777777777777777777777776540060405646777777777777374777377777774767777747771076035031110121000173735777777773535307131717373513243357317073171163353712571735073171733535735777777777777777777777777777704600564064077577777777777737777777777424577756771147741121161037100017357733577777773731603521725153251071335213317077071335373371732177373573737777777777777777737737777777776460464604046473777777777777777777737777567776657167647421121121103001035775737377777571711613531337353371435135351713131471731171735716171773753777777777737777777773777777777774740405674747777777777777777777377737176567757370470067070121121100010733531717577777371734173535353353107127713631735370371737173537107377373777777777773777377377171377777777444006640464677777777777777777777777777756777774747047741137112116100305737161373777737173107313531735352471713171173537017173717353731637535777777777777777737737737337177777770760650406047777777777777777077777373437777770567674776012101611210010131717135357777713253425343525353131031717373537171617171371717750537737777777777773737737733713537777777744404656440467777777777777777777377771577774764044774470717131071301210161335253077777757131035313137377534721717173537371637171733737343537777777777777777737737753713137377777764604646560457777777777777773777373001777574777764477611301121010001017135131314377773131716317353135313001717353353717165317371713573173777777777777777737573533373353717777777465404006400767777777777777377735000137776067664707640341216131300300035253521707577135271653531161773716173371375335373531717173757316773777777777777773773737377171313773777770460000440066757777777777777773700010577756764100674031311310100010103131313521073777731131052773171371310715377135737171773353353337717777777777777777773773737333131353353777776764007640456006777777777737371000013576644566565671341210131300010103521703170073735371730311173171371352735377335373725357353757177177777777777777777773737377173317173773777344564046466404444056477777537301000373405606746764011331352171001201013523152107177735303504373171357017005335217135307107317371337377777777777777777773377373137317133353353777706400004400676000640677471001000171464767444564031301052117100301001703117211617173531713035316127331710737171717731734071737171777737777777777777777777737377735333531737777717746654047046440044700465700016113000564440676653130171311303001010303152311340217173613530435313513531210717313613535312131713771777577777777777777777373735333337113713131377777344660240404740064000007003012446064000065641301430121217100303010117214341305030713521770035312153431340315251703537140713531737773573777777777777737373737171337311317171771777714540440064600464074764547407644764474661061711131171213001100121311331330433171353713107121713013170071631331353113013073173537752777777777777777773737337373131371731317707377776646600000000400464006460040000476461100121212163011710430103104341170510350307131714035353017317034353153417125240735317537717377373777777777737173537713137113133135371377737771404047400000000440040000046564612110016111211111303013012110331333130343135134352334031251210717107353213717131300131733737777577777777777777737173713737133713717131774353737777776646444600006000046442564670513430031611030301700012112131170552530043032531351307171335313137007153513035211071631535737716173777777777777737373733733531313133713707375737737757474604640746406546442411030301104111210110303104012112533130313134315113171371407135031707110712313253121520031173733537777777777777777737737373717373313533531177165373577777737777574746445652413513125110130012121121210110013152113152531725005303616343160335303521310243535161134112143537371777777173777777777777731737737313171731353137350737173717352573717737353737171343070110212100210130101013020210311612313171134121711311353134135311353531061303116113010013535373537370777777777777773777371737373331371335117340537153717352573737517340707317351130211011201712103103011001312531711725371124301253717135035215271212170171703130313030703535373777757377777777777773735733717171311371333173163537353707142570532717161352513307111211211401113502101211041713030371135363105331301212530431731135353107031100110411000713737177377377777777777773773733771733317131335353170143417217317073173535317071353250303071021120120301311303124330171711371133150435053537171703713107031316053160317031301071371717717734777777777777777377377337371331351733137124331335351700717053530700714351131501103112107111131030105001153012125363757312131303113121051707131716110210110101100300317137373737713777777777777777777777777377373331537174101170535321705713725353507331216121312110710003070125103130061213110133151317052521716161370213134310313514310303121310140307171717735653777777777777777777775341307071331313130060130305313003411310303014105310101012101214311130121103130131412130757377735213171213135105350311251212021030110101030035317337735731777777777777777777757171310101373535317100112535321610613161035110031310130103010131003030013112105007031301011317731730717031711612012135035335310503110212130104713535713737167377777777777777777737310010135153313530003011010511001212117121243001030012101103010051013100301130011030130077737771750731731631350717133031035302110211010121303533733753773177777777777777777777510100000017335331711043030312121041153010101001121031010102103010303100311012100121010010731737773731731711531300316153171307116111035031101433537533771774377777777777777753012110111000015617137200103110311121203103031210021110010030101010000103110121013000130131017771777771471352373053525313317037130612102103121312573713753777377773777777777735035355371731510001717701100314311430100101311011021102031211011010130010100312112100030012003037777737377335375317330131351631713150110311301535017353777377377177777777777773513513130111053351101771130070131303131053170161307050311101030102121000121211010010101013101101777777777716537131731570716331531352352311210713013343773777375377577777777777713171310135371315373103520010113161311032072113131110311212121012110110031101121213030003100100307737777773717137171731310315331707353014301311253353573573717377737377377777773177125353131735335357103131202521135271510113412163105211111113110121210003100111011100101301010177777377775637717331737071735213317317431734121314317373777777777757777777777317313113107173777531737150101013173031133043713353110631777377777373111001310312121030012121000210777777773733171316171611073135351731703101013171733525777777652104277777377731713535341717353537357571310100010351353250310351317377577525010505357730301031010112100210100101037377777777757335735331734353717371371707131343121753177050001040014077777737713713011331357777775337175000100010370351314771377775713400100000000000417531013130313051130100010077777777777345331735353125353310375313430521353531377770000400140014057777777351717351071353771775357331001111111353353211377777434001000000000000000003531051014110030100100210777777737735335731735217103341737137353413110313535377104700106756207747773537371710325175375777317735110110001107317351677771611013400100000000000000000703121313003012100010017777777777773433173131710735333113710305303431073737770777406456065570014777753103535113137773711771101110010100171771737777171607000010001000000000000000130110300611101010011077737777773717717353731730537516371737125313173171777575646747676566756704773757110717757777773773130000111001110771377777516101105010000000000000000000000532131101721000012000777777773777717317353431343133317171717035307173773777775747400456556756701773737711010343571513571110010010001001777777777357343034341010000050000000000000150121001121100010107777777777771635371353735343535353371335431713535377777770006047606677674073777777771711113173753000001000010100177777777535305141000000401070000000000000000313500310100100010077777777777373537037331530173537317137523173773737777777747650460447465677777777777777777777777300001100000010110777777573530530374175353107057310000000000001710071030010101010777777777377753713713573716137131733533507171353777777777774640540761465477777777777777777777575000000000000000007777777753577575031035257053007700000000000003100121121000300007777375777737343711713131716137171753533437173773777777576700766704465625777777777777777777777737000000000000000077777777773537077577561763571001000000000000053010121001000110077777737777773531733536173253717373373711717353777777777755046564476767477777777777777777777777111300000110101000777777753577753712707100142070070000000010010300713110100010000375771777373534371353317315171731717171707353777377777774766474677644747777757777777777777777717370100013000000007777777777777350574100005251007100000000000000611210030001000017737371777773716135271711732533537373737307377177777777777752424464765677777777777777777777777777141010011100000077777777777770712170710301701617000000000130010531031010101001071771735717777136131173731716173531717135353737377777777747654476744644777777777777777777777777713001011010100100777777777777570570110414161600071000000004170003071011000100000371737137773733417373171371217137373737737771777777777777777467444604677777777777777777777777771700000001010010017777777577757353052431201001015340000000012100171121200100101011735717773375353735317137131613717171731717377377777777777600742076565677777777777777777777777737100010110100000077777777777305705251525034000702100000000005037103101100010000037133710775737352135237317350713737371773777377777777777765046546046467777777675777777777777773710113110110110001777777777775730701006125010100050000000010030013103000010001010717717373737735357135117717334353537373773577377777777777567746644650477577775777777777777777750131100000100100077777777771775711753010530400001010000000010010703113100010000003710735357353737037333713317137373757377173737377777773776564745204646757774777777777777777773313010010101110007777777777577771650341252051012104200000000007001352100101000101017373171737373531617171371713435317337537357777777377377756470064404657777777776567777777777751111101000110011377777777777577161035214105200040101000000000101031013010001010000735377335773773535373173173353737737737737373737377375377777447476704677777775777777777777771371301001001011017777777777537577134104034001001000000000000000700033101000100001003171357331771737160152173171351733717373717177775371737776767460446044777777467774777777777771311111001101017777777777777577756134311012161401000100000000010101012121010000000077337335737377373137335353737363573735373737735337173177756104700046567777777757477777777777117131000100113777777777777776717351410401450101000000000000000016017110100010000010357371773177735371613533371353537373737717353173713717775654065400004677777774707757777777735311010112113777777777777777535757161252161210000010001000000010010310210001001010007335377377353773530713535337337173537373735377311713737765670000004004777765656577777777777531311010111777777777777777777536352141010014340100000000000000010000311010100000001071775335377373737170733735717537373717373735317373717775725650000474046777577777677777777773711311313777777777777777777777575757161050000100000000000000000001001210000100000000377335737737777737313571733733717373735353737731353733770567000007400077777677777577777777735311177777777777777777777777573537010116310100000000000000000000000131010010000010105335173353777371353053331353171735373737373713177737777770016140740004777757777747777777777531377777777777777777777777757347753777717400000000000000000000000010103001000001000035736317357357377317271737373735337171737353777333737377716140141003473777776757465777777773537777777777777777777777777775771757761601000000000000000000000000003100000001001010173317717377377373711373535353734737371717371377777777777502112007047377777756777777777777777777777777777777777777777777771777771501000100000000000000000000000003103010001021016317431635377377173727173373371313531373737377737737777733714005001737377777777777777777777777777777777777777777777777775777776142140100000000000000000000000000110001000101012017713173537377737353117317137137343777373737737373773737373737137773777777777777777777777777777777777777777777777777777777775011210010000000000000000000000000001200100010301211431617353717737353353613733537335373337777733737373373737373737737373777777474240567777777777777737173302137777777777777753435341410001010000000000000000000000010010003010101003173617313737573753353435373135337773333777733737373373373373733737773777756101000507777777777777776140500001377777777777753525210250000000000000000000000000000210010010210303117351314771737373371321733173737733337377333373333373373373733773773777714000404070747777777777400000000000400257777777757170714141001000000000000000000000000001003001011010100617335733135377717137152357333773337333333373337373737377373777377377777435777707477175777777700000000000000005377577777716171430300100100000000000000000000000000010021201210311314121353737173737313253333733337333373373337333373377337373337737777710777775077574707777700000000000000011007377757753717071050140000000000000000000000000000001000101310310035737171253537177317353057733737333333333333337373373337373777737777777775475725777770477770000000000000000003005777677757717070102101000000000000000000000000000121010100310311121312135353343737733337373373333373333333737333337373737373373777777777773470052574177777700000000000000000010077575777771751016010000100000000000000000000000000100030310130307171353433035353773731717373333333337337333333373733373737377777777777777747125352757657770000000000000000001250577777753571252501410100000000000000000000000000110001011013010112130313117312777733323323332337333333333737373333737373737737777777777777140016050257407700050000000000000041003777777777357103000000000000000000000000000000002030003071301213353413437017717737373333333333333333333333333373733737373737737777777777777375017257400747100000000000000001000075777577575307505101010000000000000000000000000010101211035351010313703113733337337333333323333333733733733373333373737373737777777777777777477405670067777000000000000000000007743477777737530302500000100000000000000000000000130300313121213013431353673377373323333333333323333333333373333737373737377777777777777776747640424000474775200000000000000007575707705753553141410010100000000000010000000000013011035217131301703137331373333233333323333333333333333373373733737373737737777777777777756777004774770576705700000000000002177677057777777347130012000000000000003500000000000013125035217050131353137337333313333323333233333332337333333333733737373737737777777777464644640004047406700677505000107161756505777000575357316153050101000000000017100000000000707125131213130137333273313332333233333333332333733333373737373733733737737777777777656740000074067640000575767700416500416777777775777777717535214010000001000005370000000000424133530351302130137333323233333333333333333333233333333333333333773377373737777777757474000004656504704756524057470770071257777777777777571771341431001010000010117430000000007406753071034111013273331333323332333333233233333333733733733737373377337377777777774246740047000064704706760077077574774774577777777777777775347131020500010000035210100000000675740243103130303033323233331333333333233333333333333333333733337373337373737377776564404004064000474404004104747724740776776777777777777774735317435102100010015035700000000004642440043101010101331333323333323333233333333233332333333733373733373737373777777706400000670400000000070470477777577074757757777777777775675775701520510521001431500010000000700040056103121312103233333332333333213333323333323333333733337333737373737373777744470000004041640560046747477757556777417677707777777776567467171353413001006143043401000000074000004640210101001033323033333231333333233333333333333733373737373737337373773774676740460000640646406756777477776775774675447407776774052467747257253143525012107100000000000464270047040121303121333333323333323333333333332333333333373333333333337337373377640444004000004004000046777770707756767775677777657574256477567057357057177171410507110100000000054640676740101001003033333132333332332333033333323337333337373737373737373777774040000600004640000470047677434475034774434774750676705657740400645717377753430001214730000000000600004404042101301333323233333333333333333312333333333733733333733373373737376420000004006040420006406767767477042457707407047765774067764740064163717575251010000573500014425604450000046500210130333131323313233333333333333333373333733373733737337373777745400044004040000405447747747577774050604077447747465765044747604776445777775200010101350102467406470640000046041030113233733312333323323323323333233337333373333733737373373774664400000004000000460467767676776770675424770747725046565677654004476064065351777777777770005470474004000600470001012031323333333333333333333333333333333737337373373337377777000400000000000040000006767477676777765702576004765406770464004604700440000577777777777777750076000000000007407646001211733330332332313333333333323333333733333733377373737373744040000000000040647400477676765657656564047645076567656440756425674004704047777777777777777710400007647600540044650030123333333331333233233231233333337333373733737333373737777000040000000000040004000445740400676472470041674004740400042447560470424747677777777777777777760004047044064600000640171130337303233333333333333333333333733333733737373737737765400400000000400046004600064000400400540470047040076000470047646404004740004377777777777777777040077047707400740000740121333331373323333333333333233333333373733373337373733777046400040000000640040074006004367400407601647400764045607404650470576474040654777777777777777770404400746440044674046002117303137133133233231233233337373337333337337737373777704640000040004040000004400440674400046764064740040410065247000006746645647704427777777777777777700600047004704670400674013031377032323333333333333333233337333373733733373773737400000000000004040074567202400460000007400564706776656065646406004007247044046577777777777777777040460057706000400005674001137117313330333333333333333333333373333733737373377740400004400004000004464044047004747440046564006004454045640474654004744064760006777777777777777777400400674147700707604060307032313733333233233213233333373373337373373737377737740004000040000040400070004406640460707656475004006020064047441600474007476500077777777777777777740000000047464064074004400117313073333333333333333333373333337333337333737373770600460004604000007006464640045061046404650640560056440540064674070465647400406477777777777777774000420000760000434007060003313753533723331333333333323333337337373737373737777704700040640004000044050065000460460074004604006544640046700470640470744006647040047777777777777000400404007704000467444044013073312713330323323323323333373333333333337373737377466400404400654060006460460447474050060000460046064740004474400564464024045240640004777777656744000640074047777047446056700053531713733333333333333333333337373737373733737377774400000000047676404746540000746447465440047406704504004467404046746540470564004740046567765656424064040060777744040610674003312731353333333333333333333333333333333333737377777400004440000464640004044604464647676766746560404046000476776767677776004646400404656676646464644400400640404777600004400460011713000000000000000000000105000000000000E0AD05FE")}, + {catKey(3), ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E5069637475726500010500000200000007000000504272757368000000000000000000A0290000424D98290000000000005600000028000000AC00000078000000010004000000000000000000880B0000880B00000800000008000000FFFFFF0000FFFF00FF00FF000000FF00FFFF000000FF0000FF00000000000000113330735737777704000000000000006060000252000014131315311716037070021240161075371617637506357172512171357170173537160000025214002070000012436167777777173333737171773737377700001111131330131357737700000000000010000050040006331131313310705073430040000003070761617433514356537571773171771716167170604076776775677064253437177777737357531737373753537337113313111111113131235777000043712006767773677777711131531311777377077043125361707171177773563737373563777177371374735716771707717735637700016777476777737531333733537371373717531111313130131316131112163035371350007753477177311137133171331777777770734772516177777717777352575777357377717537533173777160277767777777043635673717737533337171353735737353773713111271131130317013111111131131670073677776771733113113135137777777771470777616777777777175377375377357177773573757073577775677777677770365635777677533753537337371737353377377313111123130131313103131313131711770477670777353533133531331177777777763777177717777777777735357377757777737777353353553737737777777777761771637535733533333135317373753371317371271131111313521313310112131171337003717377771313117113171131777777777177716777777777777777777735353773777777777775773365777777777777767160677747673173717117337335373777177775311123103121113130371131317113313143047765767171715331313133137777777777677777734377777777777437577777357777777777737357530735777777777776071777737317371733733531737177313733737131113313113303511130153311311317343077777773131331171353153137777777735377777777777777777777537737177777777777777777737777767777777777717061617717331337313173371737737373573773103111313103132130133071311311313000617277173171731331313111137777777777757175377777777777736527577173757775777775257537171777735361777253525616717373535333171371737717173753713313121011313113113153131131371310000604167113131171131713131177777775353737736535777173773753752767757377737737777736757777177777776165206353735331317337133335373753737373773111311131311301312130331303113131701070127013353133131713113113171773737773571753773527657765274371717737737777773773753637177777707177716535273533173731713717133717377317177373121131111121130313131031313313531600076507711317153131313113117777777717163763777767753717373136161634777577177757757777567075717776353613437473353131737313313353373731373735311131103111131111301731115313313131120012507317133317131713113137773577737753753435373777675756537535736173777737773773577377736777777677617437173337331737313353353753771335337131111110131303121731073131071313134107612771313535313531311313573757373535375377777567743527253473434357677537577375777735717771717171771707167317131733537317311331373337137131103331313113111113113312133131313503071650131313131531311313173757377757777737437773717377775367343717773537777375773737777777777777777167736535333373173731313133537311313313311111121353011303310335313111331312161677373135373133131135311777377777373717537717777777777725357343634777777777773757757777777777777777716573733171317171733313713317331331313331331130313311113311133173311531371753777717131171117173131311777777777757737717777777777777776347757737777777777777377377377777777777777773777173133333373531313313313331331311121773313311031341373053105313171773777771313537133713131311137777777777377777777777777777775357777775777777777777777777777777777777777777777773313713535373313313131331110103131137701710113113131317331333131337777177731731311311317171131177777777777777773717777777777377737373737777737777775777777777777777777777357777131331313331373131313113111010111773371600771213011305313171143171137777777131713131731313171131777777777777777777777777773577737777774770737777173737375777777777777777777775737331313313137353113103113331111117701677000071513131331353133131331177737753131313531371313131117777777773777777777353535777377771773773775737177777775377777777777777777777377735313313131313373113110111131113171607777000072111111134337113131133771777735317113371310117113177777777777777775377777773777535377357177377777777537777735777377777777777777777133313333131313131101011131113111677077700000000003373131013353533713777373131313135135313631317777777777777371737573773777373737717337317737537373773777773777753777777777777737331311113133133313111131353353137777343400000000000113113111312111717357713171313133131717531177777777777375377773771771717171713707531753573735371777373777173777773777777777713173133313113013313131113377377177701353700000000003113713121313131317371371311317173171317131377777737357377735371771373737371771333533723171735373777577777777773777777777777733133113111113101313131757173173343410111313410000041371353135313537375335131731713117135377177777377757377577173773777773535333171713531117373537173537373777737171737777777777313313113131111111311173337357131353131301111343035363131317133113317137133313113313313531631177377753777773313717353121013131717171312113331313312113777753737777777573777777771331333111000101111130311771733110111111110311317525753173113153035337713535317317315313171533157773773735375353717353113313131331310110110131311313101313773573737773771773777771133131311111131331131371373713131313013111311313737371173713131317137713131315317313531373513353577757771337317313133313121111313110110131111111111313033173753773777773577377733133133331331331331011171717311111111303111131353537737113312171313713713135333713713171173711313737331371731313313111111113030101210111110010101011111110313371353737753077777313113131333133131131313373775313011031113013131317777713371713131131311353733171371313107171131131135777137113111101011011011111311113100011110101000010111121131377777377307377131311331013113133713131117131111313503113317035303177353131713171131731311171137131353131213171153131137313313010101001011010101110101111000111010101010101101111317377357757077111131101013313713331133110171031131352115213131313131317131353031311353373371137171313131713133131131331310113111131130121111110313101211310011010101010101100011337177737737173131111101011173735333533331313501013111631131735353173533533313135313135317137313313171713111111111111101011010121012111110101011011131101011001101103710112110101117373773777653113101103137173133533353133131313171313113073130331213353135353131303531733711535353131311313131313170101101111113111101010101101101010311010110110351310153501011237175377771377111130111313313353335373311013113101213713103131131317335131352133533171711733313313131311111111211331130110101010110110101000101101101013030010131031131313531011137337135370527131111333331353335333171313177307335112153171725353711131713313513530337331717317171131313113311017521012110101110010101001101000101111211111101013170170103031210135337377737147131331311133317335373731113111711533113313331131310317131311713313171537171371713131311331311011313113111011101011110101100100110101011131301317010113131311111113331713317777323131131003131331333131337307131130310311213533173131131317173171253133353137131313131131110101353531010101110101101010100111010010101112101101313131310101010312111113351731777507111110313133133113373111711103131135371713413107112111313131311353571373537131311103101013531131211110101010110101010110001011010110111101310101130113111311113010311331733537707331111310113113313171131173171533131313133173133713121713535312133171173533531313111111031121111301011101101010101110111010011010131031310111110112101010121013110311331737737707713111113313013013131103113131013131313111353171353171353131113517137317173173773301013113130311111010101301030112110101011010110103101010101010111101211111312111031173131717127731303311210101313173171311131701616017337335331331313317130353373531733317717311113113011131030301010110131111011010111300101011311131110100110100101101010113101133317173777077775775311111130117113170706070700005200101731731535351731171353173171757733717310313011011011111311110111101031013110101300001011010121010110010110110101100101311111313353177577737737713131111371311777777777716705347253531731313313731135353171373317753311351011101011101010010110101311013101011011101011013131110100010100110101001110100230011311377137037777777777777377770707725777777610634305277353535353731737137137137177731310131331310111010111111310113111011101131011700101101010110110101001010010101000011031371011373131717777077737575775777777777773677761071777520735373713171773535353717313713131013011111011001010101101031101011010110101073113110111011010010100101010113010311301107333013313773770777747773773437577725777777777707767036571737753735371353137353731713731101311303013775311011101111101111011010111111710100010101101111010101001011011131121100331311011311171771777375777777537777776776776777707716537371713717135377377171713533111331113011111313377751001101011100101011010101013311111010101101010011001010101010121101100110101011033735377777777777577777777777777777777070773535373773737735317177373733113131101711300101101113733100101101110101001010111011010101010110101011001110311010111101100110012110103117377527777377777357776776777777777770525313371717353713171737335371713311133133110111110131310357710010101010101100110101701110101101001010101110001101010010100111011111010010335317777574777777775377771725707777772531715373737373717377535771737351311111011310100111010310313371011010100100110010103101010100100101010101011131111011010111211001011311111131237777375777757777777704725707436531713737177173753535377637371713331313111101111101301111131011133010101100100110110111010101101110101111101013013011011010011011111011101313011753777737777777771725073520717753431353537317351373777071757373311111111301110101110110310113101013110100110110011011031001010100110110301111301101101101011001010101101110101071371777777777771704720742577634367125313531717377575707737375311131313131101011101011011013010111010111011011011301101101101010010111011110310110110110110101131310100101011113117177717577753577073512753471777707531753773717737737735357777731113113133101010101011011011110011010101001010101110101100010101010101010110111010110011010101113130110101101013031777773437777770742616343061617707672717177777777777767377371713113111113110101101001011101111011010131011010101010101110110111010111010110101010111011011010111110103101101211771777777577777712755257777777770735357677777777777771735735737313111012111010101101111100110101001010111001011101101010010010101111010110110101010011011001031011010100112111177377777777777757616327777777777777567773577777777777777537773535313100111101101100110010111010111100111010111100110010110101010110101111011011103111101010371713101113111011107313537771717577775257507777777777737377777377777777777773777377331310113121130100110011010001010010110101011010111011010110010111010101013013121101610101217130110101010101103131717771777777171727777727777777777477777777777777777777771735375331010101121110110111010111010111010101010101010001010100101010011101101011011101311371311525110110101013101111310317775257377777507776577777777777777777777777777777777737737137131310171110010011011110101010001010101110101011301010110101011101101011011101131311111703130310110101010110101031753777775777777727353777777777773777777777777777777777353753713571013121011101101101010101001110110110001011110111010000101110110110101010313010112121353411011011011111211113153775375377535776574347777777777757777777777777777777773713331313353535131000110101313110101100010101011101100110101003110110110110110101111011352111531113701101101001011130113317775777777777353774377777777777737737377777777777777757717131777317331031110101101101110101111010111010101110113111577777370110110101100101013131613536111305301011101011010317017737777777774347034167361757772757777777777777777777737313133111371531113111101110110110101011310101011101010301077377377710110110101110113111531353113635130171010173101131713777577777777774373436756572773757737777777777777777773737131011311713101035010101011011013107101011101100101311117737775773130001011101013101213317134311113717217073110113131317777717777777034141617373777677737777777777777777777777777131310303103131131773730101011101313111010110110101010137737727170131101000101101311311037135031701315313152111013112757777775777773436216167567535777777777777777777777777717373531311153110110737777537131031311777713110110110111110737521717310101371310101110311017101131431315311713313011013113737777777777741615250716352773777777777777777777777777777313130107301031013513353173511111035331717017010101001011713537371011135377510101011101713110353171301301110111011101357775777777777216102527777777777777777777777777777777773735773513111111101101315335317301035130101311311110101117727773777710112112537371301010330110317131135311113071121013137775777777777775250753477777777777777777777777777777777775733133717730101121101031535121331134111301711212511121713513773531312113111305377771735110101213170101303011121171101013777735777777702527673477777777777777777377777777777777737717113317531121101311131121711521131215131251113031112513411777701111010311310135371121031117152131310111011103171331357735777777777753473743777773777737777777777777777777777371737353533101110101012107111031110111133111312111103013103121331310101011010533531121113101213111110110101101353131111377777773535777673757777777777777777777777777777777777777773131313305311011101311311213101211031251031011101351710101111010011011101311351317111301531113012111011010101311301257777775777773773747763477777777777777377777777777777777737777131315301011301311131211101311131111131011101370130351310101111071103110130310313161713121013110101011010131301111377777777577777757737573777737777777377777777777777777777177353101311313010110121051303101121012130111010777173111210731101011031101130111310113131211113101112101011031101121071777777777717577763572757777777737777777737777777777777777777777131317101111031111315310101111111011011377353117121177101100101170101111030131211111121301101011117017521113113137777777777777737577257277777777777777777777777777777777773773731121735310101013030130531130301211301777717171303110310110111131371100101111101113030110110301010731713112111735777777777777777572775357777377377777737377377777777777777737753531171717010111011110113130111311101137777131171110357713010101011130111101010111011110113011111310173717110131737777777777777777257276353777777737737777777777777777377377177373713537313510011010131010131010301101777371731121110373053510111010113121101110101010110111012101311173137313031777777777777757775355717777777377777777737777777777777777773777713137717131311011011003111011311110177735331071152117771313210101011010110101010101010310101011101437113717353137777777777777777276373777377377737377737777377777377777777735777313171312535301101101111301301101037771731153121211735311435112111301013131101101011111011131121313310301371311777777777777777775717777777777737777377777777777777737373737737777130317113031710110110101101100101771731521311111317731213112112101111101030110111010101010101105015315301377137777777777777777773777777777737737377377773777777777777777777337777531313111131312110010101101110117771731131303110173111010717117112101101111030103110111011101131211313171313777777777777777777777777777737773777377773777377373777373737371777773131350307105351011101101101010773121121010110017211011313130311251310101001111312113013101301011121371313777777777777777777777777777777777373735737777377777777377777777737777777112131113130107110110110101137112110111011013771101014352535211130713111131271011101110110121130113137357777777777737777777777777777773737771737737777717373777717373737717777773113573773531310351011011101713111011001101073570131313113131213113152121051111101111010111110135711153777777777777777737777777777777777737373437737737777777377637773737377777777313173573535310311211010173010111011100111773171035253143151707352111113121031101211130101735737373377777777773777777777777777777777737717353577777777735737737737377737177777311177777317353535311121110111010011001131077735303513101313131311113030101113103135121413131733757357777777777777737777777777777777773573737377777777777777777771737373773177777173733531735371307030111031001011101110503171521711213531703703130311111313105314103531316113715331737777777737777777777777777777777773772737370777777777717777733717377777177777311773771531735317110101101101010103013113773111211713121311110110121210101121131713535317313331737777737737777377777377377777777777770753573534357777377771717752737377777777777731531731735735317312101101101031111013410717031701107111303110111111311121125371773535371711777777777777735377773777777777777777777377373727373734377577777771737377777777777777773173531737173535351330100111103013101311211101130313030110110103010101113537137177377171377777773777377777777777777777777777777777707773534365737716375357163717377777777777777777313771737753535373513171010101110131071112113011101111011010111113103071717753717717335377777777777737373777777773777777777777773707167335327777714177371727377777777777777777777711335353737137153717177711110311011130113011303101011073711012103113131313353773771737777377777377777773777737777777777773777773737376335353517730115073537377777777777777777777731537353537537371737137370710131030130113101101101033717735317177571615353353573777173777737377737377777377777737777777777737773435317237277735341617317377777777777777777777777773535373537177171717537731313017113513050310110117575773535353713731331357373371317777777777777777737777777737777773777777777773363735353773777535371637777777777777777777777777713137717177713735373573534307121703035371031037773737173735373777171171331317527777773777773737737777777377777777777777377777777170736357353032525637377777777777777777777777777777513737737771773533573531717171753537173537537173537753577717137173131171637177737777373777777777773777777777777777377777377777372537236353533531377777777777777777777777777777737353537531771737537773571777731373537173537537153717373335377173537170675242477773777777377377377777777377377777777777377777773712717737252163777777777777777777777777777777777577737737773173513717353777371435753717173713173735375357533133317373001024107343777373777777777737773777777777377777377777777777371210101217377777777777777777777777777777777773677753537317131371735371777713773335373571717717171737733113100137100000100074347735777377377377773777777377777777777777773777777773737377777777777777777777777777777777777773471757373717707175371735373711757357537173737713737335353531216174213000000000160137737177377777377777777377777777377777777777777777777777737777777777777777777777777777777775773472777573717707025371735353733717337173537531717175737373171706353471000042107162473637376373777373737377773777777737777377777737773777777077777777777777777777777777777777737753577777777770735317171737371753735717353713773737333531370070714253600000010616150377177177377377777777737377377777773777737737777777777307777777777777777777777777777777777777767177777777775040603121317177377173311317777131311010343107347435341041061061777276536363717737173737371677377737777777777777777737773777017377777777777777377777777777777777777167777777777727371707576713177377311060131313371763434340347737767761207161007077573713173771776375675271735377777377777377377737773777707677737777777777777777777777777777777777177777777777525677020753673135311677166072147777140774340377677777741676167077773767776172723377373737377377777777737777777737573777370761737777777777777777777777777777777777777777777777777773577527777172531770167107757375676372537734777777777275347712777677777037017757137373735235237377537773777372777353537777173577737773777737777777777777777377377777777777777771776172577777777607077070777677777314775610477777777720536710657777777770503303323743707073773577733637777377773436777670007677377777777777777777777777777777177777777737777777774176142770776777307077070773776777430637270777777777512416370376775777727765301703353737371212537771703774371773535213170001077377737777777777777737777777777777777777577777777736012147777753574007052525777717763410505003437707176012140505717727035001000600343303030035353637163775377763477637767007000375273773773777377377777737377777777777777777777777610400030707276301000000072504761700020000077410776701600210063600534720000201006304343037020071707707127525353703170716100007737777777777777777777777777777777777777777777777101073001675250534060030104352430170507016125703676010000050002714177043401401400751437070500143163740707703727343767070700001437735373777777777777377377377171777777777777777777777777705207772521014060707617767070305204036571717771610012415636707371423000210727052572002007707307007707163701010070760002053677777737773737777777717777777077777777777777777777777775307077560210110717617167761615314172776707761670717271610707671507014252572572570500740700434300707161677677770107001671737373773777773773771677777717171771777777757775357777777525252105060601671616170105204216357071770101034725777777771060701610777777777252070307077700071616177777777770707707373777777777777777777373777777777777177777777777777070777777777770707171777777777777773573577777777777777773577777777777717770777777777775257770434000000000000000000000105000000000000E1AD05FE")}, + {catKey(4), ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E5069637475726500010500000200000007000000504272757368000000000000000000A0290000424D98290000000000005600000028000000AC00000078000000010004000000000000000000880B0000880B00000800000008000000FFFFFF0000FFFF00FF00FF000000FF00FFFF000000FF0000FF00000000000000777777777777775773775737773773777777577777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777173434716174361735707353436571717377235700007777777737777737777737653777757377177737537537777777777777777777777777777777777777777777747657252103060206042434777777777777777777777777735777375374361705253432163617075727777777737777777737777777777777757775777773573777777777777777777777777777777777777777772524141210000040604004000000000004061677777777777777177763534736175370773527577757777737577777777773777773757717717717717373737771777777777777777777777777777777777777777777127052430200400604200000000000000000000000000077777777777771776773717237077052707271735735677377737357775773577737777777777777757753777777777777777777777777777777777777775251076502410040600600000000000000000000000000000000000007777777777171756757577307371717076734737177777777777773577777777377777777777777777777777777777777777777777777777777777777676107412042000000000004000000000000000000000000000000000077777777773773637075352525617357737576177357777357777737577777775371735737777777777777777777777777777777777777561600000016700604004004004000000000000000000000000000000000000000000777777777177777377677773765725772737777777777737777777357357377777777777777777777777777777777777777777776536177777777650060000000000000404000000000000000000000000000000000000000077777777757377671717075377777577573777377777777777777777777777777777777777777777777777777777777777777172577777777717777040040000200000202004004000000000000000000000000000000000000777777375775777777727171777373577777777777777777777777777777777777777777777777777777777777777777567537775767775600247142006040404040404000000000000000000000000000000000000000000077777777737370707567677774772777777777777777777777777777777777777777777777777777777777777757572536577727757700000164250400000000000000000000000000000000000000000000000000000000007777373775673773717353773577777777777777777777777777777777777777777777777777777777777756376357616767577777700000025020000000000000000000000000000000000000000000000000000000000007777777777174347777775352717777777777777777777777777777777777777777777777777777777777374357635737576167061652007560400000000000000000004020000000000000000000000000000000000000000777757377737716177767757777777777777777777777777777777777777777777777777777777777765374357434777077752161257003434246040000400400000000404004000400000000000000000000000000000000477377347563777071737377777777777777777777777777777777777777777777777777777777775161434243652527777756140007403400004204000000000000000000200200024040000000000000000000000000000377757737356177777756777777777777777777777777777777777777777777777777777777761636342707165256775777777777777000000000425200000040000000040040400402004000000000000000000000000000077373525617271617735377777777777777777777777777777777777777777777777777771775414340564167014707777777343576100000000004752440004000400000000000004204240000000000000000000000000077567773615777725777777777777777777777777777777777777777777777777777777770736340703167047025200777757202172507060000000652060004000400404004004000000040004000000000000000000000077352353634371737177777777777777777777777777777777777777777777777777777670504250746014304004043434275710050725100000000047004000000000000000000404000200000000000000000000000000077775743537477476777777777777777777777777777777777777777777777777777071072435274212420424200240041427060030052473400000000420000000000000000000000404040000400000000000000000000077172353653717353777777777777777777777777777777777777777777777777177477043425010410400004040043607404140061253043612000000040640000000000000000000000040400240400000000000000000176775253777777777777777777777777777777777777777777777777777777776537043471724202420424003043405607024240040243043416100000000060000000000000000000000000240000000000000000000000735077253434353577777777777777777777777777777777777777777777777773467743424014040040000604306521604000000000000100216034000000042500000000000000000000040004000000000000000000000527014343537072777777777777777777777777777777777777777777777770743535360500606034034070434702040000000040040042410501434020000000656504000004000000000004020400000000000000000003714363527707357777777777777777777777777777777777777777777777775347765160610014024072452400504042000400000000000200203030506000000000616160000000000000000400000000000000000000001634107107717777777777377737777777777777777777777777777777577767347724100424204070452521602002004000000000000040004000030314700000000042470040000000000000604000000000000000000061527077316703777777777777777777777777777777777777777777773752577716524612500563472524040040404000000000000000004000404004212520000000000047002400000000000421400004000000000000172016125613757777777777777777777777777777777777777777777765677741607521040256056152434306120102040000000000000000000000000040357340000000000650040000000000060000000000000000000705253573757277777777777777777777777777777777777777777775371777760524747025617256250004404464040000000000000000000000000000000000716707000000065242040000004074000040000000000000725363757257777777777777777777777777777777777777777777765671671702503244707657050656125212120350000000000000000000000000000000000001616520001060546006000002070400000000000000070707577277777777777777777777777777777777777777777777777371677564652645217771616070216525674774301000000000000000000000000000000000700712107700112034610000040470000000000000000170707257753777777777777777777777777777777777777777777776565352535214120747774343417470753537531000010000000000000000000000000000000070041650030677400046000253640000000000000007070737773777777777777777777777777777777777777777777777717374240607420547356534343743773676573000000000100000000000000000000000000000057252121013577777000040643740000000000000007071757777777777777777777777777777777777777777777777777656534343416520347736747343743571777741010000000001000000000000000000000000000217010000203177777742041677740000000000000725242737537777777777777777777777777777777777777777777753737470042430476770571734775376777177300000100000000000000000000000000000000034161001210102777777750000567704000000000003525375776777777777777777777777777777777777777777777777765743004341043177177365777167571677770000000010010000100100000000000000000000003002100010617777777600600000425600000000056102527377177777777777777777777777777777777777777777775363742524242147576525365777772773576710100100000001000000010000000000000000000000100210211037777777007000000000424000001200614357176177777777777777777777777777777777777777777775752542505252167537565372573577577357300000000000000000000000000000000000000000000010010201077777770077000000000000000025300020216177777777777777777777777777777777777777777777727772142525042536743534757777772777774100000001000001010000000100000000000000000000001210121777777700000000000700000000704034175777737777777777777777777777773777777777777777777577054252420356771776777273477777477710010010000000000001000000001000000000000000000000012107777777400000000000000000434303403434341577777777777777777777777777753777777777777777077025241504252563575257577775777177300000000000010000000010000000001000000000000000000001217777770000000000000000000000701612537363777777737773777777777777777777777777777777777167061626143473576377727573777777747100000010010000000000000000000000100000000000000000000357777770000000000000000000000761612535777777777777777777377777777777777777777777777776714161416007076175673572747377777730001000000000000100000000100001000001010000000000000000277777000000000000000000000000170777763777737357353757357777777777777777777777777777753422507241707716437757757775757777500000100000000000010000000000000000000001012400000000000021776000000000000000000000006707343575777777777777377777777777777777777777777777777767450615242506717653672771777377737010000000000010000000100000000000001000000001000000000000404070000000024000004000000016107777377357777777777777777777777777777777777777777777173070625042516705657757767167575770000000100010000000000000000000000000010000001010000000000000040100000176000377000770352525347777777777777777777777777777777777777777777777777656071425252435635270777777772777710010000000000000100000000011000000000000100000000000000000040252400007610004740007077602537737777777777777777777777777777777777777777307757775307406160043463527577757753577707000000000000000000000000000010000000000000010000001000000000025240000007000037000007761757777757777777777777777777777777777777777777774716773776502534165241756752707677767757770000000000000010000000000000010000000000000000010001001200061420000000000000000000003161207052777777777777777777777777777777777777377717617747777702436125260743657753777777257730102100001000000000000000000000000000000000000000001001000061400000000000000002506061657127052777777777777777777777777777777777775770777065707776561405601416165252765777577777100010000000000000000000000000000000000000000000010000102567060000000001773774352100001206107357777777777777777777777777777777177777073701752756177347360560605257653563477777070000100000000000000000000000001100000000000000001001010216100000000000606043437777777777535771677777777777777777777777777777777777775777720253617056704076161425241652577736577710100010000000000100000000000000000000000000000000000001636160000000000000000000424343437763071777777777777777777777777777177777777721747570257077717725036163425243652525777777700010001001000100000000000000000000000000000000000000101404000000000000000000000000000001757277777777577577577777777777777777735773577737051207430653524507041425241616525074743101000000000000000000000000000000000000000000000000010202020600000000000000000000000007477375777777773773737377777777777777775773576777752025070161347770240724340160652567371773300101001000000000000000000000000000000000000000010001040040000700000000000000000000077767777777577777777777377777777777737777777717161743507076146161657070524176050065256563673730000000010000100000000000000000000000000000000001002040075017700003740000020000077772507777773777773573757777777777735777737777007007342100170352573657070524016070024343571733733210100001000000000000000000000000000000000000001000077760077200007600000750000743756173777777773777357373777777735777777765307701635250610616070052725242525607043410706074773773610010000000000000000000000000000000000000100000777777700774000177000017600077774216775777777777737735357777777773777707534160060070521061001725250577752520140707060407434373333321000001001000000000000000000000000000000010137777770007700006770000777005777425621777773737777717373737777777743777777034177171030060125614165252552752576034043470702434277773732301000000000001000000000000000000000000000377777770017200017700003770027777001567377775773537351717537777777705053770436143434070104030612101612416070757434343434707056173773737321210000000000000000000000000000000101007777777430000000035700007761657777023057737736153434370703777777753027777072534341603436737410707061613611616252524340610707256173773737361000100100000000000000000001000000000177777777000000000000000075425367700456375773717273537073171777777361417777050616030141410041271527170040065255352534161460525024377377373337312000000000100000000000000010000103777776740000000002020340702576770612734736157777577577174341777770521257772171616577273430034020142534352101207614216167376167534217337377737631210001000000000000000000000100377777777700000000000140000252753470434717717235377363777373771777770525277576507177775001412535160342100052405205214704175070177777777773337333733733000001000000010010000100007777777576700720000002102141652752430525637777777535757375775277770702525777773777717120302050767050104777253721610610212527416777777773777737773772733312100100010000001000003777777777770057400000056000200256341402527535377353773735777377774030050177677177777772514101200103777777775705050161241470412707777573777373773337337373733250010000010000010177777777767700272000000374000175770002016752777557671775777375775377470276717177777773712037400142057740217737727060041020003040775773777357373377737737372373333431010001010037777777765776105750000007770006027740012452777353353771737177777377737775357777777777657571763002100212710612410535161061434343000737777577777777337333737377373733332303003037777777777777576002700000007740305057730003052557677771777477717377775777777777777777757373070104104000417651251243420107072534000437775737717377377737773737333373736373373377777777775777727770371000000777000026777400006357353777176717353775777777537777771737777377740170000630000377025724103416000057052573775737777777535733773337373776373733337377777777777777477577000000000003770002517772000534727771777717777777777773777777777775717777571270030010750304161407100617070012006100777737753535737773777377737373337333773737777777777777777777777000000000000300142437740002437577177777717357353717777777777777777777773751734000007614300037707010074010401000703717773777737435353717373737377337733777777777777777777777767765200000000300030303474340107437777777777775735777777777717777777777753567721737000000037430070707352037421000125075761777177717737271737373737337731437777577777777777777777757700000000000000404043076120001747777777777777777777777777777777777777777730507430001000753401000006004143100340003773777537712771717535277373717531343777777777777777777777777774100000000100003034307410002567377777777777777777777777777777777777777771752013410025037700000100104002016070000001771737753757172713617116352733077317777777777777777577774777772016000006000007000743600012074777777777777777777777777777777777777771612052412410735700772000000030100010170100070775717371736173753737353711653107057777777777777777775777777750275000016000007000347000074377777777777777777777777777777777777375377775210241277727777050000000000000000700000375363743563537152317071253731357317377777777777777777777777777061760000770000770024370000035677777777777777777777777777777777177777771421434120500143417007006005000000001600017527357377353716375613535352534331707777777777777737777777777776174300007700034740535770003473777777777777777777777777777777777773534163503430752142100025001010206000000000003073717717535341735133573617353531743177777577777776577777775777700374000077700077702476710043657777777777777777777777777777777777577672507701617742104371020060000100100000000000707352712737371736532535343172521343777777777777777777777777777740770000777000777012577600143777777777777777777777777777777771737761743700161614100630407050107050610020102500017716353753525361713533533172531717117777377777777777577777777774300000000700007770607775004256777777777777777777777777777737777770017171700000030601750300216100210061412517211473471735377173534352570347153170707677775767777777777777777657730000000000000017270537760021617777777777777777777777777777777771710024361614000001700250757576014070121612745763771372573433053713753177132352171311774377777577777777777577777430000000000000005027060000525677777377777777777777777777775771671600015300031200000010003002016030052410417320177774173437577070712352117153070352707777777177777577777777777777420000000000000205007030000435777777777777777777777777777737777161001020502404116100700700351701403001243524177777537073713137171751357216357171351377777757767577775277777777770105000000000000125614000161637377777777777777777777777775777712716000012100300600610601617206060340704100617777777253535256517343136131735121214325777357677776776777757757757616720000000500002070200000027477777777777777777777777777377717050705000400100101010071610404101100120120701777773771617037313725307717251123717335167777777775737771777737677777007700000027000014070000025017777777777777777777777777777737773010300001242520002000000030300607740165100777777777771617147707135301717367510714325377777737737757777677777777777475600000077000030770000024247777777777777777777777777757777340200030301010143414003416500010161034020773777777777771707331352717375215313671635101777717577777775777577577777703677000001770000047740000107377777777777777777777777777737534100100000020030201021343212177060002503177775777777535770731475251617031736161101016367777777657777277777777771777077340000077700000375200016074777777777777777777777777777777730000010001010001021000153534317173412147717777777777737771073031631617070517036373614177777777774775761777776775770054300000377600007777000250077777777777777777777777777775735701000001000001001003012153535637173577357777373777377777777071611613435272035014010337777717717777777777734777777770200000000777001007770001607077777777777777777777777777377773000100000010000000100017013531353572717737177775777771777717161631611201017037737775777777677777777537775773577777061000000000000020077700007007777777777777777777777777775773500000010010000010010001213343175727353773577757777377177777776173052163577777777777777777775347753777657777777776776100000000001070000024000700777777777777777777777777777773777000100000000010010001001715317031717757177173737375777777777712141253577373775737777277777777737675777776375777717716000000010020000030521430601477777777777777777777777777775373100000000010000000000121303535371713737177357575773735777777752173773777777737777775777777777577777577177776717777616000000200001210002402417423777777777777777777777777753777700000000100000000010000116152135073752577356737373775737777777777353777357377777573777777777776357276377777777777777017000010000024000052503402547777777777777777777777777777770100100100000000010001010311313437305317125373567175733477777777777771717735777777757777777777357777775777707757677706774000670001434005200342704377777577777777777777777777771734000000000000100000000035230533111735737737173537737577777777777777777737777777777777777777757707757777657777737577707770001775020777205274305216777737735377777777777777735777130000000000100000010000111531417251235017153537525737137777777777777777717777777777717777777777777771617777777577777052770027767403775600774704257577777777777777777777777773777000100001000000000000103030121301375137352370713737537577777777777777777777777777777777777777765727777777775367777770775216177703477761657770705237737777777777777777777777775301000010000000000000000013517171717013615357173757717343777777777777777777777777777777777773777537577777771777777777616563407777442577524377707027777771777777777777777777717373500000000000000100001001312303112317351361307153313717177777777777777777777777777777777777757777777777775777777717777012142147772106776106776524147777777777777777777777777774352101200000000000000000000111116116112351353533747717335377777777777777777777757777777717777777777777757777767777777756042142052142507706107770707071777777777777777777777777317016161100100000000000000016125213513515361353453313635707777777777777777777777777777777777777777777777777777777577777734304343252052052050617070607777777777777777777777777101521613016134301000000000001713531701212121116171335757171357777777777777777777777777777177777773577777777777775777777777400434344047025205261602434167777717637717777777777777702503507107010521210300000035371161735753534312134530317353777777777777777777777777777777675777777777777777777777777777770703434034307504361420410706177777777577771637777777777753503016107030505250103000071673535331361735717133517107103777777777777777777777777777777373757777777777777777777777777770600616034202704161430612506717677773777777577777777777777775210703503031216101717171163525677171723527507343712577777777777777777777777777773577777777757777777777777777777777401771616005614306025070416017777777777777777777777777777777777771410307041610777777777717171103525357353735371717777777777777777777737777753777757777777777777774777777777777776167760414777070615706003601677777777777777377777717777777777777777774101301777777777777777777771717015253437161777777777777777777775777777777577375773773777777777777777774777702570772430776061427741605261777735371777757777777777777777777777777777761777777777777777777777777777771717107127777777777777777771777777777777377737777777777777777777777777777742400412477775243477341615067777777537173777777777777777777777777777777777777777777777777777777777777777777717577777777777777777777717577777735677777577777777777777777777777777053761610077705243777600260135377703677777737777777777777777777777777777777777777777777777777777777777777777777753757777777777777777773777777773537777752577777777777777777777740276160607770425257740165016777577753177777577777777777777777777777777777777777777777777777777777777777777777777777777777777777777676777777777777757772777777777777777777777777705070501607721605277342032407703777777377773537777777777777777777777777777777777777777777777777777777777777777777777777777777777753535677277775773771753617777777777777777777777200020601400401240160104052777757777771437777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777773535757377777777075777777777777777777777774343430703430705216070612410777777777773777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777377777777707377777777777777777777777700040040240060420400000007067777000000000000000000000105000000000000D6AD05FE")}, + {catKey(5), ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E5069637475726500010500000200000007000000504272757368000000000000000000A0290000424D98290000000000005600000028000000AC00000078000000010004000000000000000000880B0000880B00000800000008000000FFFFFF0000FFFF00FF00FF000000FF00FFFF000000FF0000FF00000000000000777777740043734074373737370737777043707777777777777743777777777000534040673577777500740000400050040042500000777770004217073737373773777730040407073377307373725000043374053000003737373777377604074377777347737377047637777777777777547777777777400750250573177777505350700050000000040004000577770404033773737373773370400040407071214377373736100372000073777777777777377704404427437307377777734341777757777757477077577777771040075000777577777040614340000000040000000003777700007773737737737377040040000007061637373773536173040004003737737373737520404040453774777737373777777767777777777705777777777770004770477737777770051004100000000000000000047777700737373773773737200004004040407173737377373737340004000777777777777736440406404247073737777777737777577777777745761777777777750017770777537777750065000000014000000001040005777733737373773373771604004004000063773773737373730000004040737373737737700406040604007777773737737777777757777777770742777757757777777757777577777601734000000213000000040004167777763737373773773021400004004005373737373737373400404000007777777773753652444040404737373777777777767567777777753475757777377774077777077713777771561410504351750000000000000017771000437373772007003040000400737373737373377240040000040473737373776345340042440573777777377377777577777777777767527077357777717737775775777777770140040037077100000000000040477770703373772253733704304000071637737373737603104000404000777777773617370744044043777373777777777777775777777777577757477737777717777777737177777740000005753176001000000000000077710007373351273773334304037361603737773773506000400000407373373653773773734040772365777373777777777777777757777756770577577777705776177777577777005000127357710040000000000400077705011340361433071214015730001061733770003717000040000377777765343763770704077344032737777777777777777777777777775752777377771377704777737777773001057517375000000000000000007777001340004373737370033723710360273773173733733434004037373640436777377070773734537477777777777777777777777765777525257777777175754007775717777740070030777700000000000000050477775377310000073773207373376004005336036007256352000033507765060470737737707377772407073777777777777777777775775767775417577777737600017737117777101400573777000000005000000000007734170771371052371337373337010033404017373337253434372040430440475617707777737352507775277777777777777757777775775637777777777771404077777357777003537157700000000000000004340007734351005021050077373777373773700004007177373003737370060440440427603737373777777773465657777777777777777777777565547777777757770000077717777775007167375000000000000000000100577515335701507130036373333373340040400436334307773735300440040060471777777777377373746175767777777777777777777777716377357774277040417773777777710731717700000000000500000040000750072571173053711001067777324000000400435430773373723440444044040463737377377377743475677777777777777777777775375657757777714074000617753537777715731775000000000000000000140006734150163413041405031100337004040400400423077377737373040040044041777777377777737443475657757777777775777777567477777777777737434005747777777777777777701000000000000000000000017737350141741030017521110100000000400400437337373737374044444040773737377737377761674367767777777777777775656757777777777777400405521077371777777777771000000000500400000041043775775214170171413710052177111040400440053737737373737340040004376377777737777752574577575757777777777777777771777777777777753540436040777577777777777100502500000000000000104167527101507006121477105010713040000040007377373737373716444074407354241737737372777772567767777777777775777757565775777777777770714054050777357177777777010050050000001000004707573517040350514140717010711771310040040737337373737376300004016777365347737777573737777565777777777777777777767777777777777777777707016177377373535777770500160004014040000015707777071357300717152503537700103610000337024253737373014074407737374074327771636777777777777777777777777775777577777777777777775353757757657777753777777710143505000000000000420077505357314141361050341510153533000707700500273737043233016773777734004776167717777777777777777777777777777577757777777777777777777777735704777735777777052050000056100000000504573417215614170516135170077352700373373042125377352373577737773737737773534407777777777777777777777777777777777757777777777777777717777407707777737777701050000400014000000050030753751701016153050434037135310137377352104037304250732373777777737737772404406572777777777777777777777775777777777777577777777777777777045177777537777701000500050400073040070577053653507317053171714177534316373373734317304000303737773773737773772444061652775777777777777777777777777777777777777677777777777777775067577377777771070050014000077141017500773141250714705257371304712411017377733637724000404343717777777777377040407440657777777777777777777777777777757777777775777777777771777770572777777777771052500400007104040470077714105073531505346525035353104313523773370004040000340273733737377174042443470747777777777777777777777777777777757777777777777777777777770571771777777770014300007314000010140534107014143560734110505107173117703161137004000004040301777777777776377440641474377777777777777777777777777777777777775707777777777777777777765777777777710014005054000000404007717053430501141416350167125776143535377100000404000040773737737371616525341464077777777777777777777777777777777777777776577773777777777777704077735777777771401420000000000000771752140410716100715371711507010000705734340040000400033377777777563773736442537777777777777777777777777777777777775757717477777577777777777507105777777777700040500004004005271456105313410415351040507170100571050143410024000400017377737371736173777753544777577777777777777777777777777777577677657743477773777777777777057604353553777040170500000000005473116535441710430701313507173100071016110037100400403736337777760416773737763777777257777777777777777777777777777775777756543577777777777777777075070402741470004070400007504071457717731061770415061701161775014014017340336030000370034271730406635377761777377347777777777777777777777777777777777757777756777777777777777777743040057121741405000005500007700370571414141051734140534135301731421410037713005377003001760440440576527377777777777777777777777777777777777777777757777561635777775777777777704354100177717353400050520070577144007061735305301531073417770141040143000000347733330040020406044042437757777777777777777777777777777777777777777777777777775777777737777777777507377770377757771004377750525305335711507535377042561717101710350140053053100003777610040340444004040407377777777777777777777777777777777777777775777777575775657777777777777771777777777577737774005770061775254177721703405171010141712500404250143750414000100037361353044044424404777737777777777757777777777777777777777777777777777777777377777777777777777757171737353577700376107577771034351570514176053416077141301001104141000001004000737373204004400440563777773777777777777777777777777777777777777777777775657757777777177777777740257777577677377357414773777777514043052412511241710535035101070004100711250031003737204044420444243777377777777777777777777777777777777777777777777757777767757777777577777777055614016141014014707617571777777717107113414341530417101430417171300001071351410173714000400472405773767777777777777777777777777777777777777777777777777757577777777773577777770061616140164400071417773531743777777577414341041410716101410000471711400050031007373210400444041773743527377777777777777777777777777777777777777777777777777747577777777777777147141450706100165065675353435357077777701617134301570510700005710040043115004140002405200072407773774256577777777777777777777577777777777777777777777777577775777677777777777770434343070414147707173537753777717177777775353410467313050100053050010000401000015013303504041773777375616177777777777777777777777777777777777777777777777775777757777777777777750541404050404165047171653717777777753537777771711107507170521414070140100007100030077372007773777377737777774257777777777777777775737777777777777777777777777757757777777777777070043434252177165377531353717777777777571777777777173516530570101050101710417104103307353073777377737777776174767777777777577775777577777777777777777777777777777775753771771777057041405057416717171775353357777777777777174765777170253501071404340561013710000073733200777377737777372416477777777777773771777777777777777777777777777777777757777777775067774707143077257771777077137714777777777777777735173477535140407400101000104050000005343250177377737777377454657575777777777577773777177577777777777777777777777777775777475707575735707354145735371717357717371777777771734777777753507777311403140540505010000010002143027377737773777773652477676577777737777575777777777577777777777777777777777777577725707725675747777377775377717353715377737775775735775777777717174001571003100005610007710402007737737773777356165257747577777777757357773535777777777777777777777777777777777757577757575377571311177777761757357731177577173777771777777777777717161035040530001710510000053733737773777376737777756777777777757777777357777777177777777777777777777777777777777777777737753361037717171177335371107777737775357777777173777777717537571001041140040004000377373773777377717773777775777777777777577375371777777777773777777777777777777777777777777775771375113117717177175735737717777577777717777777571756571607573431405040000110000172137373777377436727777777777777777777737357775357357771777757777777777777777777777777777573737771133513137777177375734117371737717371717777777777717505170541401210171043500036373637377377700453577777777777777777777757777334357777777571771777757377775377777577377777357535177152103577777717537537701777571775777735777777777707352052070535050404173037100003737377430464367737777777777777777775737715153717717773777777777775777777577377777573571713117310311313735371773753771177777377537353077777777777775757715000143100130103724000073737700440404407777777777777777777737717734357777753571771753535371717777777537753777370171301711311017771717171717537357177537757775352577777771776717771571077300510733100700303720464040604745777777777777777777757771735353573753171773777735377777537177753751775373015311210313117777377777713517777177537371735352567777777717750773700571412513772300007340040404404524277777777777777777775377573537777357375357177771717535773757777353777531117131731533010717775717117752713777777757577137153177077777777104144353000751273373533700000040640460475777777777777777777777537357717535771521737573535353773775353577771753371713073503151137771731753771371477735375373717712714717577577777310000411410303737373360000004404140474377777777777777777777177757717537773173171717171716171757731073775377357130371753710370177775777375377173175777371757713717717052537177777771110061041737373360404000040466340477767777777777777777777717777737153577177175777713531071717771171775317357171171717711310777737175375317171371757573717717537777050547535777777710171103737375300004000140475253777577777777777777777771777753537777357717735371713171173753537771335757317173303711310117777777377175737177777737757770531773777770107677777525752570373737020000000406340527777777777777777777777777175777371753535371353537573017017315373753177573731713511535370311377777535717733531257353577773137373047377377771505043504005017737200105004000075257777777777777777777777777777173575716357771775353753153717171731353737173757171713033531110301777777777735753571377777753757535350004377377777777140105300373610373320000400527737737777777777477777777777757177773515353571371713773717113035775357173577353537171531437111177777777177737713175353537757337535314000377341273777361005343243043343734000037777777777777777577765777777777735717773617777375373757753535301537135317537317131717131353173031777777777717753716377777753735753530704044204377777377777735004004337333000373737737777777777476757577677777771773757771573717317571737373130121717531713753531053530107317513107777717771775353535377757373571373531000003537373737737773400000437432700137343777777777777777756767775777777775757377573577757137377575173511535313531253713413103113115313711377777771717737717375777375753775173530404043737373773773640040400003352177373377777737777777777757576777777777777357773537777357175173735013301317130313535713177117103710353217777777777717535717377777773775377171700017373737373373730040000404040012337373377777777777777777767757777777777775735353507517317777171373515311717115153530353533503111035211177777777773777731717777577777537534371007327373737377342504004040000037373736373737777777777777777757777777777775773577371731371775317177111321703130312135351313571313503531317777777777757377177777777371713717371521720500737373730301600400004003737373737377777777277777777777777777777777773577357535171701735777177771511353510116171310112131103111353135777777777377177717777777777757735173173700300373732161733400004007773737373373777773747477777777777777777777777773717353735301717317777177373771313135317171731717171313071301735377777777577377777777757777371735341373004003725240372733704002120063737373730777244345657777777777777777777777575775757134353715777777777177171617101137371121130131015353137777757777777775777777777777177535121737214001730000127337340003737003003737730003747434727777777777777777777777777737537371711135773777777717537171711312577133513713171331330757357377777777777777777777777753103571733733732040040613730034373500404373732013344346454750747777777777777777777777537575353716135356177753773535313035111352513711711350171117377377377377777777777777717153737171333773737040000000343430733736300003372000272434707256776777777777777777775777777537377717317125310177771535371715137305313153713712135330707537175775737373777777777737357577357773373700000000040000737773737030370040000336454745614757777777777777777767777777757535735711535317717073537011313411137171211351353531175353757737377757777777777777775737317333377370000404000000733733373733770004004037307257165677777777777777777777574777775373537571373130171711353751371371373571315353125313713537777373757737775753753777777773775773777307000400000400537377377377370004000000000745677725777775777777777777777777777777573537377177517170535353131071511053534331353535710172571357753717577373773777577577675033373337030710000000021237337337337000000404004007165747777777777777777777777777757777777777535777173135133531353571123713353135530353137313573767353777773777753577537437737070073777003733250040021373737737377316000000004000377727377777777777777777777777777777777775353537757171733513175271317151351357133535317111777171717775353777577777737773535753007340030733437300003724000323737300401600000000037747777777777777777757577777777777777777177777717737175351343531152531337135373171313535373707771771737777773717371717177737377733000030373730073771000007373702533121000040613737377777777777777777676777777777777777777737717517757125371353171353515113435353535353313571771770775653537577775777377171657173573400437233070373270000037342503363707000031343077777777777777776757575777777777777777775757737717777135357353171312312513133537173171703471771771637777777753737175377173377173773500005030373373303407340002527373000121720000777777777777777675676767577777777777777777777571717717107135352171715353710513113177161771347167171753537537375757377137757176170773700030737373737373300000043713250727372004007777777777777777567757576777777777777777771753773771731713535317073537131131251347537177175737717777377757777572735717771737717735357340373737373737304000040000605233737215000277777777777777777756776577777777777777777777777771717471753537531153513516113161337567177637571707175717736173757563707173535217537717170737373737376000400004000303773737321211777707777777777777756577777777777777777777537175775313531317777773533752137153175773574175737371717377777577577353575352527535737537763737373737373300000004000007733773737373607777777767777777777777777777777777777777777777777777531775777535313571317113317177577377537575670716177777352537777165753517172516153535717337377034014000400005733773373737300007777734347777777777777777777777777777777777577171753175713773771757173531735777777375734347371171617617575777757016133134361615217277173752737303703000400002733377337733734000777775674756777777777777777777777777777777777777773775303753757171377177171777717175775775174777071717763777534357715756717535125017137343737021633737300404310242337733470000007734241434757777777777777777777775777777777777777753777153757317775353353777777777777735734371771617717577773577357020115213434171257417353535001733730700037700004337733032420075676564743647777777777777777777477777777777777757775717717125777735353777757775752535767577576171771773717777147707152527571007055213735373730061673700373730000033610403713100241450470745357777777777777565747777777777777777775373771753537177173777753777773777776117717717707077175775707370535211357000505321756171617050033030377373600005340002372372006564773464767777777777777777767775675777777777777777753573717537177777575775777777534157434717757717167737737757173531525353410125570716135317304043437737373735320000405373310150473775341777777777777777775747675477777777777777777773573737577777577025677777577777353534357375777175775771616516503134353434121534357434315300073733737333734000400020342063773477434777777677777777777777757477777777777777777567777757777777777707571775777757525257357075776177737177141753713040535251000572515235315235173737773737770000000040004017333775773777777477777777777777770743777777777777777777756577775777577777753675777577737753525707777717753757734004005340017253505035251637512521163033733373733120004000004033337777437477773773717775777777777777777775777777777777777777657777577777757775777777357753747734100775701657370400005341153415343005071071410710507115063777373560000000040000377733773737777777777777670747777777777777727777777777777777757777777777777775771435777757775357534175377751774340000007342161617141000570161710712527031003373712134000000001373323737477773777777777741674777777777777777757777747777777777777777777577777777777777577377177735716107577377775100000014105141707107000000101071351117140377250073733000000360307373737777777777777761765453437577777777777777765777777777777777777777757777775777777775756717563475777657717534000004770506170716500100014303125306736037000007372730000173000003373773777777377777560563647467777377777777777536577777777777777777777777757777575777777735763753535717347777400100001536514175010140505211141507510517340040437373730033270000037207777737777773636561441641773777777777736464756577777777777777777777777777777777777775771757777770755353537500404167536170177710100005250003103712500040000043700037731300003000077777773777747573464344377777777777736753574356747777777777777777575677777777777775377077777577775277747743452525353516156150741410001000304341250040004040000737732736331200000737743777737737377504437563477777774757746065670777777777777775657677757775254777777717717357757535705371757341757765251617275100040000100517165000004000004373773373733120000007773743737707777703737776173437777777737357561477777777777777777775757675777734757777737757776376777775777357350717175071757524050100100052050100404000004006377377373372000000043770377604737377747737374467777370737777725477777777777777777775677675777757770757174757753575357717725257657756756177161613531214015000050040003304004003717337373377000000000743777704061677343737777737173706577777774773773775377777777777777565777777775777277731343777774735771757705770735717056140561405001700041000005373030403720727737377300200000000377700404043714377777373777744656277373737777774767777777777777777777777777777575757574315777173577775075773577571653535257170500534001100400337340003171000013773720031000000077707440442404277737377777340614045077747737777773577775777777777777777777777777777777535777777577765777373477537775256525507414341710406500016737370737234040273370177273040000700406160040405377777377377704607064343777777773777774725677777777777777777777777577536525735771077561775753567525775375707143705376501010400030400373737300007340060335370014137440604454040737737377737434740544144777377773777736161475757777777777777777773777777753416567167707176576757357577777525716141735710040400040403777373737737704004075737203633606160100600437673777773563434042424637377737777777456576706777777777777777777475777777777771714141414753571775737252777571657177525705040004000737337373773340000000020201737373604437340443700047373772537737404417477777773773617252414750777777777777767477767677777777777777777770777774165475757773477377470521400004000433737737737374004040404070737373730100737003737070027742563737717707737070737777477777756740777777777777777757475757077777777777777777574757777737773774161037373073773160000537527737373770034000040000073737373737347700737734043770406177734707373742563773743737371614377072777777777375253434257777777777777770707070737737773777352407740407073237000343600000377361073434004040717373737373737077737373737370000404361773777777356177044377777777777777757777777777777747475777777777777777474565654657777777737373700000007373703173373000037302527373300400030233737373737700000000000000000000000105000000000000E5AD05FE")}, + {catKey(6), ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E5069637475726500010500000200000007000000504272757368000000000000000000A0290000424D98290000000000005600000028000000AC00000078000000010004000000000000000000880B0000880B00000800000008000000FFFFFF0000FFFF00FF00FF000000FF00FFFF000000FF0000FF00000000000000733431247777777777777777777777777777777777777777777777777777777777777777777777772577777777777777777777777775677750043507777777717677777776343737737172736373635337373373727300002407477737777377377777777777777777777777777777777777777777777777777777777777777777777777777777777776767777677777775247757777776775761707373737237372737173717377373777363737733477777777777777777777777773777777777777777777777777777777777777777777777777377777777774777777377777777776777767677760104357777775673372737373737737373737373737337373337173732407777377777777777777777777777777777777777777777777777777777777777777777773777524000000000000004165777777777777777527750435677777773734737371737373173737373727372736334737337377777777777777737777773777777777777777777777777777777777777777777777777736140000000000000000000000000040507777777777777770041757777777733737273637376373736373737373713733737637777377777777777777777777777777777777777777777777777777777777777777777734000000000001404160746740040000000040657777777777775241677777377737373737373373727377373737353773737237377777777777777777777777777777777777777777777777777777777777777777735210000000044767773777577353777777652040000043777777777774161577777737725373735367373737317371737233373737737377777777777773777777777777377777377777777777777777777777777774371600000006177737534247043414747416171777737000004077777777770043777777773737373737337371737736372737776373733737777777777777777777377773777777777777777777777777777777777756370700000004577616506461407404740043406060407437760000005777777775241777777373737363737737373633737373733335273573737777777777777777777777777777737777777777777777777777777312777700000407773052507005040400000040040414052525041775340000537777775340777777773727373731737373773737373757337373637777777777777777737777777777777777777777777777777777753434777000000077757047040404004000400040004000404040406524177704000477777774177077777377353772773637373373737373237737373733777777777777777777757777777777777777777777777777437243772500000477725242404000400000000040000000000000400050412407772000057777777407477777737373337337363736537343737773373737377777737773777777777377777777777777777777777775373607346750000047750404140004000004000400000400004000000004040443400577700007777777001477777737277737737353733637373733377363734377777777777777777777777777777777777777777176372577747770000077725257040404004004000600606070745424040040000000400561617700004777777403777737737337337173737737373727771325373737737777777777377777777777777777777777763617253476347370000067745040400000000406574757577577577777577777704000000040040407770001777777754777777737537727372733737373733367373657477777777777777757777777777777777777535375361767356776000047710343400040042456535377777773752713637777757774000000004043430770004777777037777773773633737373773737337177735657727377777777777777777577777777777773537270365763565252710040775644040040042457373765725730577757757577577777777640004000040450776000777777414777777373773737373172527536725727373577777777777777773777777777777373436071675364743363770000077205340040006577707161775735476177343777373776773777740000004000434170400777777434777777373373737363733736717737776563477777777777777777777777770325253437767164733256577400047705404000004753520712577073527717707775252572537575775774000000040042573001777777057777777777373525737747773770771737373777777777777777777777352137563767743706733565777700007714742000407573672171657617161753770773537357357757777777776000004004050774000777777077777373716373737743737167077273434777777777777777777737716376562570752743712567776776000477600144000256171353435753707167370753073435257743727737777577400000004250376007777777007777773737477773776577737717773737777777777777777750730707077576772703725777747776700007705640000057177071253437617343572534771717077717353531653737777700000040040577005777777757777777773716577373727537772377733777777777377301273472777616312171774777567677770000770561040004347712165617561340177353473563435752717073431307171777400000004340370007777777077777777777673635365373673757773777777737163434767747374161631676765677667747776000077524004000534712161731347317037725347172153772710725343574716167177500000404004770007777777517777777773777777737737377337377377777727073777706347436373477777767675767776570007700534000004716774352164357075053534363717257713770125352131616107037600000000700770057777777657777777777577737377737377737375337164177474617716303434767675675675767577437000077564040400034353316171356371237773057170717717703073527074777777777757700000050601740077777777727775757477777737377377737737377727077607273630716777777577567767676747237760007702014000004770774317070757071407307277353653631717143577777777577777774000004041403700577777737575777777737577777377163763576374177670734307076657677676777676775370737461000570655600000061177130707165217121770775317075377143434377757317177775777770000000040477100777777777777777773753777737737771777735777601271616777777767477476767757363437073770007341600400004576174353160177707525371737613777316317177777357736134361677770000004161077007777777777777737170300777735673773737777670761677776776567777677776567347163477477000477041400000037716334343170537103525356535616357316167770537305070537171777770000000040077007777777777773507140500777773777777472731271777567475656776767727037352736752707630043707560400004547357435252077416343036373721775325217577072534373172534361757740000004340770047777777773052500100005777775617617357761677676777767777656350343761674775274731400074040000004056371753121735701735171715717177733535773713535215257053437173773700000404107700777777775705210434770007777773773777777777577476767777257316377341765770725321633041734304000000775772765343521763532070737677375343436570761253725217343107252577400000042407100777775210705070057750007777777777777777676767777652707306716407665361635325333700077054704000007525351734303525172553035253534737353577731743071717053167153173777500000141077005777775615250575257770007777777777777777477773435270724735673725326121633736173000734214000004756572737571613472573257125777273171257713430353521613707316361616176000040060530027777771614377725437741017777777777777767774347361437573467342530713373361337300067425400000401653715257303425357705302537717577617776161735252535241736534171737750000004106700577777777435477752577704777777777777777773337325277462567134312733727353373733400171443404000741745635217753524735737171637773713777717161435353431360512712073577700000004217300777777771603577743077700777777777777774346164775637572126127373633733273370733006721040000000747375735250717134731707031527565703757725363434361655372510717073077400004054075007777777775743477747077750777777777777337357736473402163713731733573537316333700017547040000051617436525377616571743717563577377775737170153537171325052070707147375000000001720077777777777051777705777005777777777776164625653343337373373273273233633737373004770605000004060471753752171770367125752317777537167771617637053436503735371737335700004007427100777777777777476577500777007777777777757735733252373731327337335337373523731734007701424000007147430743717071755707737357707777777771721613537073513752525252525777000000401437007777777777777053577614774047777777777625620707373361727336137337335333731723300037525004000007057717147677072705353434375735775775375717617073527703717173533521774000000400730077777777777777456777403770077777777775733737337353733731737327336336373723737300772434000000447675637717177153652757737073577777777737731743577170750707052503577000000402407500777777777777777251777147770177777777720703333613273363363733732533735233371730005705405000005357177170743416365351271577356377777771653565317217371273735271777704000000050532007777777777777775467776017740777777777737336173377317317331271373373333537323700067007244000004776574173577355734777172357375357775773763737635634375353435370777000000404002710477777777777777777147775077700777777773333337373337237337373363373072732717733700177141000004034717372532516375734352541257377773777777177535735735271617070777740000000025057000777777777777777776147774077704777777736177336374337336327337373273373373323173000716064040000473617577053617527757773375375307177777177737773734377173753777175000000000404330077777777777777777777077770577437777777337333713333631731733633137337317373737270043705500000004775776175301712557765357432525777777777737657257537707161677577770000004040033400777777777777777777774177770777077777777336363377373723733371372735732733631733320047406040000005723573077770353253177273573737177717777753735363753777771777303400000000050471007777777777777777777774077770777577777733713372336173732730373373323733613732737300017014204000025707170014774353043417753575777776777777777777577777777777774353000000040403700477777777777777777777775077743777777777363373173733371373737327373731733733733517000770605400000477716527021775707353771763736573535357777777777777757317777530740000040020070007777777777777777777777770577747777777373372336333536336132733533163363373363172270004350500004000571205710170120775735077170717253777771775777773707374775743070000000004507100477777777777777777777777770777773752536773177337363733737371633727337337343136175370005704244000000775207770052501030437177777777777567777777777757756177777352500000040400071007777777777777777777777777777777763777757336337137333713337333733737137123347436327570007705340040004375301772102161430525071717775363717777777777777777777352017400000002401700077777777777777777777777777734373577777777337723635363727127373731323612547323575716300053404044000004770707170014121043035377773537577777777577777777777775251700000000450070007777777777777777777777777370777577767727337133373337333733737300272561252361756363637400077007000000005771616570612525352527753777777777777177777777777771725364000004040007100677777777777777777777777534777776777353756723673353633773733030735073076777776777735773700437405604000004771610771701020353717677577777777577775777777777773537100004000404370007777777777777777777753736777777737257777773373372333730312163434261677353535737775777773400437005040040000771630777775753757617537475777757145735777777777777774000000400017000777777777777777777773775777777161757777767773353373733437256143737163534372737573777777777000570524140000045775771614361674217343563777777534735777777777777714000004040407710077777777777777716353653677753437776777767777372334330343704307234256352737353773757377377777000770416004000007777577777577535747773757377516043577757777577753400004000005370007777777777777777777777777772777777777777777773373336161720736714737357737537373777277777777777000572407404040007777775777777773731777374777753557577777777757741040000650427100077777777777777777777777473757777767776777767734334072525734716300077371737737353773735777777777400353416050000407777777775777777473475377753502537577777577777361040400407710047777777771657777777777777777777777777777775337336137252721630616737717673633437361773773777777777040770407060400057777777777757777753777575752757757777537717775004161400771004777735256161352535777777777767776777777353637474072725256163073733752773317173737737177377777777777000777054142400077777777777775717777777777175535777536575757534704142573100776150505001050041430375777777777777777257677777737253525236177173477771353737317131737377777777777777400177206414240047777777777777777757405147537777716553577777405256053700007150000000040041001414125377777777737177777777777725207337177372373773777353014307471657075373777777777424057516414340000577777777777777000065217575777616757177707074017770006500000000000010000100210525275777777777777572534333525737373337337377371301201637737373737725477577777777500077701654045042525743452540500400534577777775751777750474057770005010000000000100001050215050105177777777777763773737707333707377273773737525034373731737777771513707757777777740007774006524004000000000000000007507357577771777754707017370000100000000010000010100001000030712107577777777537377373737373736335370343533125373501434143113170755757657777777774004377500416504040434000000000043565737777742570525405775000040000000000000100000010140121411053503177777373773237373707373737336172777043731003471077756740010217717777777777777004137770416165240404040400400043575757775756164007773000141000000000000100010110010100501421052507177777773775737373737371725616252127530047741647077657777435005775757777777777400407773404054165210207040404247177777534004537771000460000000000000000000000001000010121143017107137737777373727373730636163735717753057777064704775357477706135777777777777777740000577771600404444540525041404757752407573752000447140000000010000000000101010011410416105710713577773737373737371671437170763653007004374014707764652574404431577777777777777777400001777773500000004040600000001757777740000477740000000100010012011000001001000121103530171053117777773737330631637436370343743704074774060565735656571606470757777777777777777774000041777777776716350747777777725000004477777400000000101010510010350101041034143501537171070737737773737671477073616177770340416007706560077440617764040435377777777777777777777440000004161753757775352507000000407777777700000010110121013013525201705103501210712507071171773773772731637212525777777075000610477040056775775776504746475777777777777777777777777404000000000000000000000404777777777770400101001201010701210111171013041035070351717127147777377173777077777777777770524041470775076005761474756765777677777777777777777777777777777765616442406146567477777777777777410010101010101010105112530172053171417155071717503177377737373212577777777777735004200420764054065767470757776577771777777777777777777777777777777777777577773577777777777777734010000101010000000000010035153101731713071352513543777737737377777777777777774360404041457704204074543474765677656577777777777777777777777777777777777473043547777777777777777410001010000000000000000001403016171071653161717752177737773637377777777777777735040004160077004702524246056565765656777777777777777777777777777777565173516170034173777777777771000100000000000000000000000105010001711317170735017737773737373777777777777777470040400070774000404004147677775777775777777777777777777777777777777777052710734537575777777777771014100000000000010001010000001061520343435353537017773777373777777777777777777354002404040770400474256157577777757347577777777777777777777777777777777150705010527771775777777775210100000010100001000001010001000140151435357153073773773723777777777777777777004040016016040040005057677757675775734717777777777777777777777775374347070034273417347777777777777530000000000010100010100201601014134303534307705374373173577777777777777777752400040640404004140524775775775777535553575777777777777777777777707535712016531750774737377577777777740001000101020014010011101125030411753411711077737567777777777777777777777712404040160404424240534175376175357563743525777777777757777777777000473417013400301717574577777777777775000300001010010014004004101413061071701077377777777777777777777777777777752400040525024141700434165757075347577577577777777777774777777770071403016701250077073737707575777777777700170500410010010101030521615171307777177757737773737777777777777777777300040000406502524141435716175717537573743757777777777777577777025420350011077006516174771777777777777777750010011041043016161052141034377777777767727777377777777777777777777777174246740440750050343570717521617757575757777775757577777777777503505000000734717217717577717777777777777777161000100105010105214377777577773777773777777563777777777777777777777014777777700050304101034161755705277777757777777777716377777777740300000401413714770737057735777777777777777777770716125252525777777777775777773777717373777777777777777777777777125777777570340102407537161617775417577777777777777757077777777104034000720407217714717365771757777777777777777777777737777777777717637737737757357777777777777777777777777763536535074752050014251716757173565777743757577777777777777535777756010010037053505771771657717677377777777777777737377377777777772536777777777757377777770777777777777777673717176777034347205007061125071307545773777775737777777777777777773777703400007041272125367165341773575777777777777737777777777773773777777377777377377777370777777777777775253747776776777735075703414104025347753731757357776577777775777777777757777410000171005714734177177743577767737777777777777777777777777777777377537367777677347777777777777673677657776773717074777714710034375373312301610307707577575777377777777777773773474040061730037716170773776177177577777777777777777777777737773775737777777673577777777777777777577577777253475677777767771067377337107351171310110713577777775777777777777757752100000173407750777077357717777737757777777777777777777777773777377777771735777777777777353673676772763617767767776776573425736530712730303601013031165375777777777777777777777605200716152570177716534777775177577777777777777777777377377577177777773777777777777777737777577777773757657777777776777571773533377731713101173011103125375777777777735777777777005721712073706136173525347767707737777777777777777777777773777777772577777777777375737777776777437477777677767677777776077373563167723010767753031101134377777777777777777777777701703417161177417761777177177077577577777777777737773773777777253777777777777775377775677777777777777677767777767717617725363353713111771110101410310135357777777757777777775770340147350167701673577707737777757737757777777737777775777677377777777777777277377777477177437776777767777777371716761771737135361774765213031713371431016777777757777777777777774034217236530777143761772577177377577777777777777777777735377777777777772737167767167337677777777767777767757677777343737037635330135771011101215073537353777777777777777777377377041605753053770757177752577757707777777777777777777737777777777777377375777777577737477776777777777707777677767675775277717325136127773012115301102114353777777777777777777777777353731257770177307771777073777777777777777777777377777777777777376377777677673725777777777777437777777777753717327537177735373517157741311210125311613077771777377777777537777735671477730077716571277717757777537777777777777777777777777773717357747777171765777677777777777776777770737363737577373637103016330371770121313530567101777567575777777773777377777167301677716377347571773437777757777777777777777777777377257677777773737777777777767777477777777773373437372733735253127353710534371353101017713533125777777677777777777777777777716771771617771737077577707777777777777777777777777774377777777677365777677777657377777777777353743733737173727773353530343773135161253531277030103527777777777777771777777777777717670777741677525723757771777777777777777777757353777777777717347776777777437371777777737257273737773737373577716303771717170121311727031731135312577777777777771777777777777777771771717377177773577377577777777777777777773727777775763527777777777777373707677777773477737373737336373737377331734770307353177163513177503031253777777777777177777777777777777777072777716177177057777777777777777777757377777777277177777777777732537347777727737777373737737637773727373343523131371307773477353703437313431477777777777125777777777777777777777570707777167777376177777777777777737377577777477377777777677637773657777735773477377777737373737373737367373561343071703477375301713101713167777777770125777777777777777777777777773752537171777177777777777775737777576777773757776777777735737577777347637777377737373567353737371737337373316131371173773537730353330350717776101577777777777777777777777773737775377776772577777777777753777747776777352777677777773437737677737776371777377773777773737373537373537735335731352536313572163531234117077771717777777777677777777777777777777777777716173757777777777773777777777737737777777767777377752777377761735777777733753737373736372736372733363730525373535273173173435137253677777777777753777777777777777767357777777767777577777777777753777767777767735677777777735377172757757725377777777773773773737373737373737373763731731125737331343152173727101057357777777753777737777777774377377737777737177777777777775637777777777737177777777777737736167777777361777777777773753773773727373737373737373373727352112521637336377353116167777777773637777777777377773777757777767527777777777777773737777777775370777767777671771707777777572343777777777775373777373253735373537353713735373736377250575350531710705617377377376177577777377776573577773777767737777777777777772777777776777377777777777777376377777773772357777777777777737773777377373727363727327363736373737337333016177161071737777776356177773737377775373776777776777717777777777777373771777777775377767776777775277717677773752177777777777777253637373377377373737373737737373737373737317373737074377777737252177377737777756717377777777774734377777777777777275271777747743767777777777776375347773770743777777777777716357375777377737377373737373733737373737073034725616527377434361617377733737773737373777777777373737777777777777773707377767767373777777777777712717277736577373777777777777713737737737377373777373737373737373737373737377773773773737773737373737773777737377777777777777777777777777777777773777777777777737777777777777373777777777773737777777777777373777777777777737000000000000000000000105000000000000A2AD05FE")}, + {catKey(7), ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E5069637475726500010500000200000007000000504272757368000000000000000000A0290000424D98290000000000005600000028000000AC00000078000000010004000000000000000000880B0000880B00000800000008000000FFFFFF0000FFFF00FF00FF000000FF00FFFF000000FF0000FF0000000000000033273373337373373373172177765677143477212657777776567776777777747077777777777777777777757777754757777777777777777777377777777777777777777777377777777777777777102136770007770000373373373233373373372173613777767773617717725777776777777677777777771777777777777757777777707746757777777777777777777777777777777777777777777777737777777777777752412576706033277337317373737237337373303763470777675672730737677777677767777777777777777777777777775777740447575677777777777777777777777377777777777777777777777777777777777777771252637616373333733733373333733737337373137377565777574777271756777767776777777777777777777777757777706070047565477777777777777777777777777777777777777777777777777777777777777777701043777337733732737317373373233737337375236377276777617677374347777777676777777777777777577777777405771065772577777777777777777777777737777777777777777777777777777777777777777777300333733732733737237373173733737363373535275752777775367377774777777777777777777777577777777404177774065746577777777777777777777777777777737777737777777777777777777777377777777770733773773373373733273373773733373373733123375656767574737377767777777777777777777775777406546746750074756577777777777777777777777777777777777777777777777777777777777777777777773732333337337333737337333233377377373737734373777776777477737357777777777777577577777560654257757750475656577777777777777777777777777777777777777777777777777377777777777777777773777373732733773337333773737337333372733737343707777777616577737777777777777777777760475604047770640465756567777777777777777777777777777777377777777777777777777737777777777777333337333733733373737633373737337376373773737373770776777777777777777777777777777770045253404047475240434756152767777777777777777777777777777777377777777777777777777777737777777373337273373372373333737337237327337373373737377377717777777777777777577757757774040404645256561764571656747671374767777777777777777777777777777777777777777777377777777777777737333737337137337237373733733737373733737373737737737777777777777777777757777771404250041435614044406774475475277635177777777777777777777777777777777777777777777777737777777377337273337337237337337337273773737337373737373733737737737777777777777777777777642404040040404044000447770477465635267252477777777777777777777777777777777777737777777777777777773737373737337337317337337333733737373736373737773737737777777777777777577777741450404241000000007440777770475747475257353701777777777777777777777777777777777777777777777777777773333373337337337336337337733773373373737373733377373773777777777777577777704067000014104000000740067775740474747765624721676577777777777777777777777777777777777737777777377777373737363733733633737373733373337367373737377377737777377737777775777777770474757645650000000444005776563704657452777752567312047777777777777777777777777777777777773777777777773373337373273733373337333727372737337373737737737737377777777777777777774052474775347050100557000477757447750076544347772524677300677777777777777777777777777777377777777777777737367737373333737377337373737373737373737733737737777377377777777777777006440424675747446577604077774607446774476564342477717077731257777777777777777777777777777777777777777777337333337373733733337237337373373737373733773773737377777777577577777404704250450476400247400407777405440045704056547707047677404673025677777777777777777777777777777777777377777737377737373273363737373733737373737337377377377777737377777777777400474040405246750045404040757040640045426504674763736342567773447312107677777777777777777777777777777777777733333333737373373373337337373373373736737737377373737777777777777740045404040464144244343440577644044040060456714074454775256347477256256313077777777777777777777777777777777777377725213737377377373737273727372737337733737377377773777575777770004061604040564044047440777740040000444040444654565275677365267347356346243066377777777777777777777777777777773333737721235337333373733333737373737733773737737737377377777774004240474104340404004750775674074004140000040407706565477767537124765674716527135677777777777777777777777777777725217470773733733777333777773737373733377377373773777777577774004414043414004770040047777465047504070400004074757744656565777652532126167653652463150767777777777777777777777777737773773470723730337773333373737373777737377773777377777773400004647751000004740000467774164074000500000007404777714742567677777656116347747677167273705777777777777777777777777470163573777561735323577773737373773373777373773777737777740434004041601000016400005777704157700414000000040477777604756565677777776617307247167734203630357777777777777777777773777743675273776727173333737373773777373737777377377777740000470004351140004440007760746576770400000000004077777775504654747676767477765325727756565743070727777777777777777777163572777437577717777637753637737373737777777377777777770004047416405040000400005774054056507440040000044424777657652447565257674767646777430347656376347632531677777777777777777743353437777252773537656371737777777773737377773777777000000507471401100000400776140424075600770040004000577575750657006565647765777776777777737274074771643673134777777777777772777737125277777476777377777737377373777777773777777777000404404743400004000477547465140005405640044000477764774250756506565677767776747677777701277160677743466330147777777777353473777737132537777177777777777737777377773777777777777000000040040004400057706704100000561404040000457760536047740640456474767764765677676767761216770043777114636334176777777737777373777777771777777777777777777777773777777777777771000040004050400047777444410400004044000000057671457654565340477047474356777767647677474777610036764647673652437213677773773337773777773777777777777777777752544644647777777777771000044004770004774340007410000400404250747775440675000044000477447437271667777765777676777773531212716471673707243177777377777773777777377777777776756040400000000000657677777776000004000400177454740001000001400404777777737400770000040404770567473475375677767777656777767470712616674747657343063337733773737377777737777775250000000000100500400040147777771000040000577407604044140000070000077777414440475010000000077774047747276167347767675675646767677251213434717274767377777777377777737777777765000040500725656572571414004004537770000044716565604500401000000404005777750640057105000000447775704776775616735636567767676777747677767343732667477356733776177777777777777775000400070775777777577677777525000405770000004774041406470700000070004064777740040247105000040777770400775677671663570747777676777747477774761617136346657777377373777777777775000002577775774757776575656757777741600570000004704040414040500000440005074477140044041100000077774400043737765675743632342771656766767665676767616137353676177777777777777771400007777656565777773577765475647577774140470000004042407404400000040004774405676564004100000007754000007377771777767765656353077674777767567777677776530343377377777777777777040007774747775777757056175357640757675777701040000004040447340050004004474040065750400471000004750400007777777777070756565361607603436747776677574767777677337777777777777777740000747774145770775311110535375744654567775765000000005040047740400404034074161440675040044040757440000577777777777776372765677761743613716777676776576767464777777777777777770000777744776452577525343010105071741650575777777450000040400440404000004465406560045640040000777400000067665677677777756753774360767253652716056776776756777776777777777777777000177543474453457757115115014100165665676565657777000000040040040000474047500040045000474004757400000054745064040604656777674377574707675247273734677777677777677777777777777100457756744045657775315773701211400157575757575777777500000044000007577404710400005240043777574340000747664646476474464042407777406373465436734342437147677656665677777777777774003776054656161657775771775351521014377777777777565777740000004405047473404400000044160447616504000044747475675656476566546404467770437327657474773432737167657767777777777777000577056404047577757735775571353510015777777777777775777710000000777407446100000000074400775404000056767656674664676474566474424064776035307276676567476163437277777777777777740017747475675774757777773430014116171077777777777777776757740000044750605014100000007040475040400047747474656477475656666747467464046475727307135256353617253617477777777777771004770747477577177777775351117113105105777775357757377575777700000004740560400000004754707504000005674767677777767767677574746746546400066435332523725665765670723030777777777700077775777177177177777775071010141107127777777717777777777757740000007704071000000414604740400004677777777776767777777767676774746656464047663733703071725661671747677777777774007774775777176175357777711075531305105577757175717577777777777700000045704461405040405714400000777776776776777776776777777776767656656474040561733373030721765467253677777777700177577777177535377777771035312150110127775737370753517775657577740000074400040060407740400000777777777777777777777777677676777777765665646044642737373372163127345765777777770047757771717717777577777701515715310300577777754153534775777777777000000470004004047754040000777777767777777777757777777777777767676776566564600470733337333312525272567777777500077675777735717573577771103031710514100775771731356571717775775777000000040040405777400000077777777777767676466656747477677777777777676566564640463737337173733321252777777777007775777535707773577177761511430713012102777775534317775635734775775400000470400777540000077777777777656404405416414707407460656777677776566565604460737033363737373303777577700077577753771717577356177530161151715351057535773515777711577475777770150000405777400000077777777776400441753777111373511701454206467776776506746400452337373333233337337777775004777777775075777375711573117111214311110777777150377717077775777577704770000044540000077777777764004167377777710535751305157335501044677776640656540663733337335373533777707770077757571735717575777771056111715315707017717157357753471757477167777700770000000000007777777764000535357737773131173315121717533757100406777746066400473372536333233633775077500777777775701772535777777157141215217500007777357777413575357477577577505770000000007777777774014171717375777771121753121525733513317711404776756164640407333337337333737770770007775777531771517573577777731315131530011075777777773777307777757757776007770000005777777760407773131617737737131113351151173153051713771004277674674404633737333733733377507700577777717057053756357777777141717077501000177577771715715757575677777750577770000777777764053577115171353777753010377101217351313131353177300467764067404373733163352337770077000775775771317161715777757757170717537011100177777775735777777777575777700777773477777777412537177312135357377331111311315353311511050117357710007777446044333277373337337700770047775777575017571777715353757175753501070100577775775725771777577777577705777747777777440513537371117131335377112117311130357131311317153737737004767065044373333373337377007700077477757353573577775121514377173750351010017777777777577475777777757775077777777777740713712575730311435137313111035301115331011011503717113535006776442407373737273733770077004777777775357757777531510135757775357507103777777777777353527577577777704777767777764171371535337111131303531713013131134337113135303171701711777000767640433372333332377700770007717575777777777773010034131777777773101157777777777757504153777775777705777577777517371113537737312531713433111015131111517111111153531131537377500774640673371737173377007710077777777777777777751711010415377757575177765743577777773534747777777775167777777744357371703111171711311111053131037121303131313012173111103577373700677440173373333373770077400777577775737777777131001011205777777777770171715347777741417175777757760577777774173735121153737331310121101316113753111151111011717310301773317577104767046373373637337710771004777777177575377750100100501577777777777417501534157775350747777777777157777777413535737113131101171113112111111353735103134353131311111173115353777106764413336333732777407770007757575377777777171010013101057777777701777701010015735250153757757774077777734171733713141053171035317313134135371773110113713535312103171301353777107762437337173733771077740017777775075357777171001075317137777577577775340311025775340757777777754777775431311753711313111131131357717131031737777131713773771111173111301353777006740437333333737770777300077577177177777711101005315014577777753777530111400537534110777775777417777743535371371731111210113101373311111173573731713743157131107111311535253777007643337373633377747775000777775715257777170101173503173777777377710153701300753515257775777711777770311317137373110301131315377315313035317353121071317337101311301113131757775076073732373373777377771000771537771177777170053510150157777775747105341141101773705357765777647777541735213530353311110110131117331111013737313513135311577717311312134173777770065233737373377775777770000777575756177777710153100050357357761710717101370065757165777777775177776171313533171353313131311073717121313103533512153535313777771211115135777777775265233333373377777777700004756377375757771753010010315375775110531717161517737357737775777507777705353531353131311317711171113731311111353713111213130777777771130537733535373700703507173373777777777700005304735171777147710105001435047137351741410117777575737575177752577770537313131211312135771103105371153112131331301137173535313131131013531151317577146742333216337777777777700004004577017743105310111011161077505301101014357777777577767777741777543717135171131311037771111301173313111101711112113173313110131117773171314317177106374247313277777777777500004000537417101005710001071540473525357100017761775777577577775177777013737133133111311717373011131153113131317313117373111111311010177113110715377537014377342061777777777777000004000753504110771011353561317751050012177757175777577657777507777701713533113110311173737111131013110771101777101735313130110131137713510351351737770673434356167777777777777000040004775311657170777410110477777535757775777107777755777764177777071713435311131013353537301011311053713377353537531110113013113571713135135277577700343473616377777777577777000005000617571777577751757751177777777777777057753576777775517777741313531316133171353133531311010101373735317337773111313153117077731105137135173777506363061617777777737277777400061000425655377777701777765777777777777757053577557775700777777525713153131531131352513531311010153735737317135371301113353317777735317175735777777417163563607777777777536773000057000441657577771777777104777777775774777770747777560577777770533171317133113113113170171031311253533135313135771135271315351753531737331737777770277352341677737757777771775000007500044753771417775750407777575777577553575777716153777777437117125313110312101353113017125135373717131351177313131101121131377373511153575777717737735373777733777777777737000000771004465753507770142407777371753573747177777450777777777477313111313531111171303111311130117371335311301257011131131111101577711312110313777003733333377757773437377777777140000577300042571153750054465775771770541177777040177777777750771713131713131301131153011311110335371313711311317131131011213533771301111311141777433777727333773373716777767777300000057535014304351005243543477474071777777404177777777777707113153101313101133413111301031017173134311311131313512131371353571311110130170113501373733737773477373737177775777740000007777535011000464444644450517577775040435777777777770771353317130113135311311301131101737317331130103535353335317135373331112533511113577127327373333373337273737253736777735000000477777735315141615353777777770404001777737777777770171315213135311135311211310101317317311113131317737335317135131775112113513737713174007134377377377737373737370735735773700000004057777737777777777777744040005777777777777777561335313517131313131311311131101775717371311111777717135313131577737113533511535777771216337257337233737373737333723527377770000000004056575757474440400000017377777777777777777057113153335353501773531031113177573313131310177731131131716173777713513101313073777007634605327737377373737373773773372537177700000000000000000000000000567777777777777777777770731705315113131357773113105311577371311210137773113103111177777531311311131115357770773561362533737337373737373373377373736371773521000000000000041074777777777737537777777777704711131336317111373535351331130375775311131737353713113121157777131131131112101777506167374356377337373733737373373373737373727777777770707134373777777777777777777773772577777707731141111113017777331011170117131177375777531313713113113017737171161135111101776072525276377337737373773732737373373737373733077777777737671777767777777777777777777777737775077713135313111357735171371131433531357777713105111135311301057713131113533530177717352527525237373373737337373637377737373737377307777777777776537373777777777777777777777773770477713111310131373713011013113353101771337711131213733131111377353537377751111771477777352161737337372733737333736333373373737337737077756777777777775375777777776777777777777730777353301531171171311317311317313111035110353111110177531301577737353531377777770777777777373373737373773737733737772773737377733737343773477377675777737777767777777777777777714757353131171121171711111301735353313131311353131131037371107777373313110177777477777777777737373737373372733733733373373737333773737370347374777776777773477777777677777777777433735317133131113131031311173573311111131111317131101353131377735311300115777750777777777777737373733737737373773377377337373773337737377333433073777777777737377777777776777677057131713511101017131110173373331731307113010731711311173537777131130111217775277777777777773737337373733733732337337337737373377733737337373737352352777777777777777767777777777037531310313110217131313177171731111137311111531311121177757733131537311777765777777777777773737337333773773773737377337337373337737377337373737377373437777777737777777777777775013535311110111371711177373371373107173531037171311110171317531031777711777077777777777777337336337773333373373737337736337376333737337373373373337373734356777777753777777677760713131131010351131373717171331113533713101177317131131311113113177531777707777777777777777337313723337777337373637733737737333773737727337337377737373773735775777777343777767710771717113135303513535313173533313177131137577717173531521301315737777771777777777777777736336363163712337737373733773733737373273733733637736333737373373737273677777773737777740773717353731713103131353113535371317713512577717173531111101337777777747777777777777777313777176163737337337373773373733737373737733773337337737373773737737353736777777771767760753507353531113107171117103531371317773713577313537135311105777777770777777777777777776363737777752527316737373373373763337373733773377337733737373373733373737171777767767173100753535373531711131313311113171131135353151253111107131131137777776577777777777777777777177476377377756733173737377373336737337377337333733377373727737277737373736161757777777400711535313121107135351130107313121777315305351030117353113577777717777777777777777777737737134347777373567133737337373733733737337737273377337373733737333737273737373637777677700773535353131317131253131117371111375311311371110017317101777774777777777777777777777476777773725277777376753437137373733772737733737373372737373737377737273737377373752735637740771311353111317131311101017331307737715341351310017131137777077777777777777777777737137373737737167737735273327237373733373733773737363373737373735333737337373337373373433477300777131313053535351353111735310117771701315331101617135777547777777777777777777777777770737373777373477777756537430373737373773377373737373737373733773373737377737377337373307500477711111301313131311333531311777173171537110101777777540357367777777777777777777373737677577777377347727737473774343537373377337373737373637373373357373737333737337733373733730077773535353711171310573711113777353112157110077777734376347377777777777777777770733437033237377373737527773767377727032353337737373737373737367373373737337733737333777373737370006773131111353011131713713011777353513773110177770457436343436177777777777777737677777747537377777773716161777773777757273733733737337373737337363373637323733737733317373737371214375313531311317565351311177177311357775377700063767757777716252773777777773437716577372761716373777737373436747737725252173373737737373737737373733373737363732373637327332737000435713111131537773121013777753535777777550475353617676777271634343434777777777737074771772737373737377773713707477777777072373533737373733737373737337733733377373337337373733730000757777577577575113577777777777776740247677761707375675674777616732617716577777370777777747743477373777773737343773777756172733737373773737373737333733773337373737337333737373700050577777777777577777777777777010747777677767765271671676347774770727737777777773434743737373725361737337773777074736377252521637373373737373737373733373733733733737373373373371240441675777777777777735614045676777676767776777763434354776076576577777777777777737377777777777373777773777737737777777777773733737373737373737373737373373373373337337337337337333161434565747475656561616377777677777767777677777677637257777677777700000000000000000000010500000000000092AD05FE")}, + {catKey(8), ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E5069637475726500010500000200000007000000504272757368000000000000000000A0290000424D98290000000000005600000028000000AC00000078000000010004000000000000000000880B0000880B00000800000008000000FFFFFF0000FFFF00FF00FF000000FF00FFFF000000FF0000FF0000000000000021312121001212103012101120511213001003031000000000001001201000031434130127125003036131617112161707121633525212434101612140103000000001000010000001000000000000010000000000000000100102120005212143012525002070011030101770000000000001001200070431030521125306171134303436170643125351431717043523421052136001002010020100200001020100010000000000000100000021310316114104031050307010303401035203003073000000000001002101000031071430161771407072535353717341752534363761634352153525252411700000002100000100000100000000000000010010003000100114211021201343030103430100312121010301710000000001020010001007007021316161302717353434371734367377371717173525257616137171360000100104002100007000000000000000010000200000100316211203104250103016101125061614121201437600000000000103003004101313534313177500717373535773534717535377777174071301216516165170070600030100000000100000000000000000001000000014211251007100212107012306121001303410301371000000000000001001006124252134343137437775373737777707737775375377370477777532735327124010100000001000002001001000000000000000001000211230303112041352101214130106121071211037500000000100010020102043513353437177703753737753537773477777377737537563010103471634716412520210121020001001000000000000100000000000001251014304204120016125013016001413001243772000000000012001012054303614353717371747377777377777347777777777777756357777777177171610252141060000000000000000000000000000000010210130303030311006171212130305210121341301317710000000000000120010025351734353777770777777777777777577777777750602004000000016124371675252161010410002010000000000000000001000000000014316100253000125050030301204120300341377000000000300100121034070361373753777377777777777777767672525004241447420704046004100400435353070212000400000300100000100000000002100003030310351204352121217050341420150350337770000000100010210103007171375357377777477777777777752705464464604766640446464644606460704024347125001000010030000000000000001000010003016100021021001214130103030120013021037577000000012010010121610657375737377777777777777774040644466567600406400040006000000476746424400212534120430000000000000000000000000003000310313521705241212016310161003503143533770000014010030216101341735737777777777777774740404647460406040042040004620040000000440240467504040025000000000001000000000000000030000000021001001200214316110701030402143031777700000212010010107161347737737777777777727000606676764000000047047640000444644640002040000466464040020041200012000000000000000000001010013523121311041310112530303042112035373770000001010201034312170773777777777777600444664404040070416000000750000000000000000046400560000046744004200001001000000000000100000002000001000525060120430612011610015217135377700000520001121011071737777777777774040476767400000206400640425675200000000000000064000046440000046767400101000000000000000000001201010043121312121124131031215201216025125371770000024012120161637353477777777770464676440000700004740400777777700000000000000000007747600674640000446740002000000000000000000001002000000520012121006030011211201007021317377700003070010121311717353777777524476777600067046404006730331777716101031000000000000047777704040000000044664014121000000001001000000100421312113041706011413705205312101350717373000104030120534371717347777444676460420000464006421171531537133131313133130100000000077700000006740000067400002000000000000000000030041000012241130001321300121130404034333717100000603016113137173775775647767400047400470000703417133113313713131135313133100000000160000000000000650467460010000400000000000010000020013041302112430520712161033007135353637000131430314343537371737674766765000420476000065713337113301311317337333737313110010000100000000000000640046540000002000100000020010001010241101306110431110353125013002537351710000161410331353535377747664004067607400404777521111113101353313337737373337333301011300110100000000000000006620100000000001000100200020001302305112003161212102533404353353737000137061371535373777764677000702444660000077760103131212133337311773737377717310313130130213011000000000000476400060100000000010010040010001300121216142125350713001025335371700003703173173737777767777640046740000470527777012112101317352710033773331773001331371011011101301113000000006774000000010000000201200121020305116125000351210330107124137173527000713547357377777777746464000006460000677777730113111203131311300013777373172101073131030121301313011200000004674004000000000001000000000100121301031250215214136103106117173531001733717377777777676400000075000000057777776101313311133737337310003777377317271373130113101310312111100000004460000002001000003004210000016121031304071211213012507017373617000371753777777777767670006746460000773737377300131373733773001717000003377733721037310131213131213013031300000006740000010000001200010003030301007061002121025341703300737171700017537277777777777767400074006777377170737735013137773373100000330000017177773103373013101303031311301110000060474000300000000301000200100001031131121417017110303114343537373016333771777777777664000066470171331333331737720013177773770000000730000002003773737330003121131313331303310000006764000000001000000041002010007060021520013012730707210073777771017533777777777675000000002533131737173737337100113377773773310100110000010001043735300101133133735371101210040047764000100000030001200100210131135132106107251013101777777777073713777177777747760066000173307371313331337373000311377773777770017200000001000331031000031313737731000131012774066400012000001000020120030000021012010612310123716173716577717171777717777777776006500077317133333373733737710013133777777777310031000000000131303130001313373750000000101304600474000000000000007010101001035131215300114313501210370712530777373737777777666440764617337333017371313730333700013113370737777301170100000013131373010001337377300000000310000000460100001001200000020021020012021210706030612353017710712531377777777777777400064001737131373213337331371733000013013510437377773332101007770131343000001777371710170000030040476520012000200041210010000101215141210013011215213710131253075777373717777760000000073733737371217133521333770000100313000347777307310207777100600010000131777373737310013104607764000000001000100061201200021213030300705321521435207701347777777777777766704760007333533733335313313337173300010010312000103773731031177310211111700000127773777777001210070067600010010000420210101000100141203150430120512121370070077131677377777774776007461033733733717333335317133337000000013113000053717731003375311303373700003113777377737101004670447400200000000101020000300303030142130433130717037001701700771177737777766000006043173733733737371303033525373000000013113131313331031177731311153777710003031000077733714000400642000000000012041012100000003152112103050031211770070070077007377777776400474404333733731733331331313132133331000000013131130311213037773131333377777771311021001373773000000076410100000005001020000210100142111250241307125377103107701700377737777740000060031753353373735337130303013353530000000011313311131131377313537377777101107003112121777300600027640000010000020303012100020002112341304170314313770077073037007737777766700067427533373377333337137131313352333531000000131311333123537373313137377777373713103011100070067600474600200000010100400000010100011250301202170307317300701700700137777777777400040442153773337371333131303052317121212100000001311131133137773313777373737737717133313100040047000674101000000202121030120020000341330701407035317770073070077007737377776400200000053333373737337173130313313312131030231201301213523173377111217377773737737733111210000645644046600200001004110102040010100400301010300313521737205701701701773777777460047400601317177373313713313371361371213002113173130331303353337733333137373337373723734331001000066427641001001200210602010121000020330703431140703171771031063077003757777767400246407643737337353733371711131331335213112030217313071301337777310103123777737337370330101001000400465603000000001201050020000200410010303072121717373702560500700173737777767004006046013537333313531333330731731213030317131212353303737337717313131113230717735350733100101000000764000300000001021210101210401213431103100431217177053017037007173777774676470000700533737373733373171731333131303530303031313210313333713731210130311010313133333130100310000064003010000000702500002000000034003070710613353737301300700700173777777767406464046653353735331317137333132712521313211313035311337373731217371000000000000000000103010005600054404300205210210010012000100243413103111210417353535007012071037373737777640006700004217313333737333537317313131303011730301231230133133131213773000100000000000000000420064046676030535301001021212010142041012200710217302412173730300710300435377777765600056400744737371713137313317335313031713231313331331337137131311337777003500000000000000007406400076434777777777773500016030100006015011211210143371217007013007053173737777767000600064072531333373713737337333733121311030310331217133313131337710001003000000000000560040670004677777737337373777353001002000413421217307034215071710700700700317353777774764000052006653535353373373333733531353312133135337131333313131353770000130101000000000466460000600247773733373737337336165102500601200121210117104333173001030042143717377777767000006650040033333371337373533733733313713103033112173131313121333000177773301100000016400077404444777373373333333333337326100010301430007032121214127171070030140313035377377764000000600005735373371737373733731717331373131123312133131313133771137777777721300024600004660067673737373373737373373333107202410421070117110710433512103035000007161737377777400000046460463533135333733733533733331731333733131313073121131373377777777733131110400004640004767777373373333333337337312301401203007012121071243341253070100702317173537777747600250000000771353733373373373373737173121731353533131310076037177777773421713131201240007000067777773737337373737333312330020160500707010713031041371343134310505303173537737766704640400066121273735337373317137333313131373333353031771771002777773770001313101000000046146777777373337333333333327206676160120121021071217070250361343130700305352173717777576000006700041111353337333735337317373735303353131331377777770017777070110313131311101000476777777737377337373733367764747476764106161403031013004335170307071021734317353773776640047606000131271237337373131733737313131313337121035777737701677700016131313131000300006777777773737333733333376767656476747416010030317073701631433031713125052131635373777774000064000003011313533737333731373137337333537313131777733777740100000013131313000310000677777777773737373737276767767767676765601243400013010301433143431617006316171735377357776604000067053073133353337353737373737313533313313137777771777700000000001313111310160647777777777777737333337770746776777777760341212103701711600523131613037105317034377177377756700000006000161053371733333337331337313133353130013733137770600000000000012301316777777777777777777777777575652105274777677740250400010301271073516161343500612527173537357477764600076400007117373333717373537377337335353130017373707310040000000000000311130676777777777777777777775252000040705010677764705203000711635004301235317171253171717353717737373560000640404771331371713335333335333533533313121231131313000470000000060004003447777777777777777777772524040406170707061057776124100041271037125371521612534247373717377737477777744000007600135371333737331737333733313113131001137171057200674056000400067746777777777777777777777440442400004070707165207474030000035005030425231713713716171753777777777777777747640040007131311713537333317331717333717121021121200064004604600000005666777777777777777777775652560404004124143507175707074000430037133516135353071253714373777777777777777777746700000717135331313137171735373331513331101000040006500670067404204467777777777777777777777765646046527424470743707070706021402005030150243534373535734637777777777777777777777746000024373313513173537333333135133371012121000675004646560042777466777777777777777777777764564654746440407065254525257750000010133512335061350353731735777777777777777777777777774404400071313313113313171717133171707000464064460067006000047667777777777777777777777774776565646142400041525273535253774020000150215307073371717777777777777777777777777777777776767600000113537353353313131171060000007604700000040047444677777777777777777777777777777777777746440604061615067617057734161202335721607171737373777777777767677776767676677767774465600007117353371313313132006504640000006000000007766767777777777777777777777777777777777777774740402525637141747257700014115303171617177177777777777647777767677774747766776777464047776025335313171317750076467000000000666040666477777777777777777777777777777777777777777777404041653450772535777701202721617125373737377777777677777777777767676767777677677777464674001731313137004600000046004000007777647777777777773777777777777777777777766676567777777774065257274357707770205013171735205371777777777777777767465445465446506447677676777774646476775210476400004762000676744664677777777777737373733737377777777777720456446474676777777075257174717157750020017121735373737377777477777746476766767466746652704667677677777747647465467674206476740447464767777777777777777737373773737777777777720456444747665474777746165705253676777701012735273707777777777777777476777775777676774765644470047767767777774776766746465447464764765777717777777777777373773737337377777777774046646676765646464777707560725771517770207001735353567373777777777476777777664765656465644767474000767767777774777777777767672574567773777777777777777777373373737737377777777004654656767665646444677563570573472777750106173703733537777777777767777747654765647656065644646476444247677777777777777777177576727137777737777777777777773773773730707777777740076647676767664746404477562537473557777030300353571743773777777767777774764264424244406424740656446765647767777777777777167707717536537773777777777777737377274340577777777774006656767676764746442460656156743143777752505213733373353777777777777746466404402465427650424074240474765007767777777777016750770773753567777777777777777777414121437777777777000765676776767674644644040771635356777772010321471747374777777777777764670404720744067406470004656560464767407767777777756777360777753763577777777777777777616034103077777777740056767776676746465464464640775774357777417070521373317377377777777767464400004446700467400656706606474240767647777777777007757177777761742577777777777777765010121410777777777024667766777676776666464440460734357777770203030527374737177777777774760000464420044474646744642474577065064777427677777770757767774752577173737777777777776121216030301777777747567776776766766674400000000047577777774010141430117373777777777777464404600002466067067476464747466646566540677647777777652775775737377617756777377777773010104101050167777771676776767676677675000000000000077777772000252020343737173717777777746400474606444044040046440046046147416740670477527777770077737737057017727737777777777750306030252120177777647656777777777767664000000000000777777410010000140143777173737777776564020640407600000000006056424066706465724470677477777752477747573077077177017773777776100101011010520777771657677777742460000000000000000007775610000010100212073717735777777746406440060440474241200046460404744470724477465676477777007077773777107716776777777777710342125203021013777567767775402050004014000000000000061430020100202001000173733777777776400044246404604665424000004656476467656452742564777777774747777771710637777517737377773020105010141101257760776776020605224707421604200000000042014100214010000057735753777777747400240040604707466456000004656647647677656740770774777737777777737731777136743777737341052120302124210777756777040414524570617461670700040000010020310010010000233773771777776046074740044424464467601000004656465640477600700646777777477777777777703776537771717776121010150510110703770777740016724724256425641474700007001250100612030030041753773777777754604464600606406470440464000000647467042763476525756777770777777773373377733743173733753403430212030201057547760017605434165070520742434760400000030701014007000303771373777377660472000614040646464706072000000046746174147616476677777777777777777777777775737353175200300101001410102736370056760562506506076474257425700470010000125210300070473775375777775474444044644004044046440640000000037777776747477077477777777777777777773777372517377737500010217030216105756561614163416470616052435607424760670030107100210061031373737377777764600064000607400060076745676100000337777777777477477777777777737777337777777773617137737214210001010012121770047607407430434075250461643560704740042007000430061605375371777377740046706064464006746406046004200004777777765406747767777777777777777773721377353737753744001070120010000167043650743470463434424427074346165647301010301012040301337377177777777460044446446060474404006774761000710677777767677767757777777777777777775014777737527377700000012503021014352525270343434341643430745616434706574000250002010071434371737357377774470604650654042464240046464047100671077777747567476777777777777777777733133573573577773700000000104102034607470564742434060743465261653461656771210303010061021037177173657377376465624644644644004600424065246301277077777676565777777777777777777777737773173773771777560000002112150135616526160743434343461605652470743477701000000010071034037353717377777746564406760674240647407000464777040577777777476167777777777777777777777377777657375377737140000000000212461616507074340656146165770470475256775242101012061025031173673477737377706400676404604404060244052677765330777777776774776777777777777777777737777771335737736770606100000301005356461647006165216705256043063424077721014200004016112143717173437777777446004440604006000444424645667777731777777774076577777777777777777777733777776521617716165752561614161764253461605745275641674252565147077777521210121007212052373477357735377737164002404656444646460464644567777731377777774676777777777777777777777777777737537703353063773777777777756470343420204025250614052066340477743050240103001413050173435253537737777470404740640065646444040606476777713737777677477777777777777777777777377773712501705252573771617617435607444652547525000605256056140077777161211303040703043037357525277773777777646466440006646000600464650477777347776774747777777777777777777777777777773070372525247075271771342570442700056200605670524203616047777706134124050030301303435253347137171737375744000024454040040400044666077777777746776777777777777777777777777717777773070503121437735370377070374240642005470725052535654007777770135216113200414160521525257077173777757367764560406600740766400004506460677777676747777777777777777777777777377777771032506161435370357071470314040404200404024242400025747776120417136141212030130523347307077353737375737576600040464644442464076406574477777654777737777777777777777777777777777325052317063536177217302171634000404002104000500776567774116153212512300417012012157071707134735735727573744406400042000047402440656647777777727777771734777777777777777777777737012341420707253521771657061010400004040612570674657656161601205071614043121705214307030613531731733563773777747604740000046440604644746777777457371777735777777777777777777777015250123505371361171210303112521000000040442467477525252121125303003300352501211211707616563434735734143353775347564600240400604004246746777776377771735737777777777777777777777210270503607071725275346160703071300100000640507472420105025001617141000212161205230611003113531612527375371737736165644740664074644756357737616717373530777777777777777777771705251030340713521735324215130141024070210420170703010052021103161212300615214305301616534707343071371617136173617056577734765416567173635371771653734717371747777777777777771021030343435025252170530501632070212140305021017021050041201104216113434071120121030431003001310317070140617535353737037353435373777073717135275347377137343527077777777777777761525350343025435316172573061053071250210121003021103030210142131030603100016170143413034703424250313136341230370317124437253734353771473716735337047357135307134217537777774777302120352171302525312533070163010010304250121001421701000710300070113143043012170303430001300711214242510061535035617102517352535271243170711635720735234363537004730737737137750714170353070434316173525306107071241000121412121121030400030171034207104216121034130143424070125031311206112303703124253734353073525216171277170142537537153616173071777343046321033034307170713735252534010303000134252102001016107002103143003031530201012507103052000714036130070525053614341716105252531617343704352125217356072530703617100717173773352010507143530703024343431712125630503030000212110307212100012502125341403014303611212143043070124112532132120003310321213407171613071717061353535343314173531713037060703273070316030302100713571435353431617001430341071050500610010510300410341301231343000501201252103004036434350152516142505251525070243061707034370070343070347025252160707502431705347171404341716170343242703421743524343070303024212121210712030012030305251425316070341705214340724110121030301210003121212103005317130713537007135307135310437135313531241352130312120030121213071735071353170312100301010101001210101030301000001010121303130000121030121030030434300000000000000000000010500000000000094AD05FE")}, + + {empKey(1),ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E506963747572650001050000020000000700000050427275736800000000000000000020540000424D20540000000000007600000028000000C0000000DF0000000100040000000000A0530000CE0E0000D80E0000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00FF0CB0C9000B090900000A009009000000000909A09A900B09000A90A00000000FFFEFFFFFFFFFFFFFFFFFCB9CFCFEFAFFFFFFFFEDFFFEDEFFDEFEFCFFFFDADA00D900009009009000000000090A00090BC0000900900000000A00ACA0E0E0E0F0E9CA9000A9CB0C00009090E0000009090B0000D009009000000900009A000FFFFFFFFFFFFEFFFFFFFFFCADEBDBDFDFDFFFFFFFFFEFEDFFFEFFFFFFFEFCAF0C9A0A0D00009A0000000000000009090A000B009A9000090000900C0900900900FA90ADA00090B00B000000009000090000009009A9B009009A00000000C000BFFFFFFFFEFFFFFFFFFFFFCF9FBCBEFBFFEBFFFFFFFEFDFFFEFDFFCFFEFFF9BC0B00909000900009000000000000900000909009A0C0B00000B00009A00E0E0E0EFCADA0C90000CB009000009A9A09A000B0090090900D00A9090009090A0000FFFFFFFEFFFEFFFFFFFFFFF0F0FFDFEDADFDFFFFFFFFFEFEFDEFEFFCFDEFEC0F0C9AC00A00A09000000090000000090A900A00009090000A00000CA00C09009090F900DA009A9000DA00D0000009C090000090B000A9A9B090000000AC00000BFFEFFFFFFFFFFFFFFFFFEDA9DF0FF9FFFFFFF0FFFFFFFFFDFEFFDEFFEFDFF9A9A0090909009000900000000000090A9000909A9000A90D090000A9000B0E0CACA0FAE9A9CA000A99A90B00000090B000909000090090D00009A900009000009FFFFFFFFFFFFFEFFFFFFEDADEBCFDEFFDADADFFFFFFFFFFEFEFCBEFDEDBEFCAD0D0E00A000000900000000000009009009000000009000A000090000000009A900DF090CA9009090E90900B00090A9009000090B009A09A90909000090B0009FFFFFFFFFFFEFFFFFFFFFD09E9CFBFBF0FFFFFEFFDEFFFFFFFFDFFDFEBFEFDA90B0A90900A9009000000000000000000000090900090A909000A0000ACB00B0C0E0AF0F0BC09000009ADA9090000090F0000900909009909A9A0900000000000FFFEFFFFEFFFEFFEFFFFEA9E9FBFDEDFFFDFFFDFFFFFCFFFFFEFEFEDFCFDEFC000D0000900000000000000000000009009000A0090090000B00900009000C00A09C0FF0F0BCA0B0A00090F0A9A900090B0900900090000B0D090A900009A000BFFFFFFFFFFFFFFFFFFFFD0F9E9F0FBEF0FAFCBFEFFDFFFFFFFFFDFFAFFAF9EFBC0A9C0A00909000900000000009000000000900000000B0000000A000000B0C90A00F0B0F009C0009000B009C000B00AD000B0099A909A90B00990000009C00FFFFFFFEFFFEFFEFFFFFCA90F0FCFFDFDFFDFFFDFDBEFFFDFFFFFEFCFCFDFEFC00900A9000000A090000000000000000B000900900000900909AC90000A9000A0C09CF00F00DA0090000909F0B909C0099A09009A00000090909A0090009A09FFFFFFFFFFFFFFFDFFFEE99CF0FDBE9EFAFCBE9EFBEFFFDEBFDBFFFFFFF0FEDF0A90AD0009009090000000000000000090090000000000009A00900090000DA0900A0AFADADB009AC900000009000A9009AD00090090909BCB09C9900090009AFFFFFFFFFEFFFEFEFEFFDACB0FDADFFBDF9FFDFDFDFDFFFFFFEFFFFFEFCF0FBED0E9009C0000000000000000000000000000000900000000009A00000090B0C00E9C00FDA90E9AC9A0A90A90A9A909AD009B900AD0A90009090B0A90000090BDFFFFFFFFFFFFFFFFFFFC09AD0B0F0BEDAFE9EFAFBEBFDAFDFFFDBFFFFFFFFCFFE9009A0B00000090A90000000000000090090000000000090009B000000000B0000AD0FADEB9E9000D00090090D0B099A9A0F09009009090B09099000000009EFFFFFFFFFFEFFFFCFFC0F0DAFCF9FD9FDF9FF9FDFDFEFFFFEFDFEDFFFFEFCFFEDE9E09000090000090000000000000000000090090000000099C00900000F000090000FDBADCADA900A9000000B009CA9C099A00B0090A9E900B00A0009009FBFFFFFFFEFFFFEFEFFC0B09AD09ADEBEFE9EF0FCBEF9FBDFFFFFFFFFFFFFFFFEFF000900090000900000000000000000900900000009009009A0A9000000900000ACA0AFAEDABF9ADA900DA9C909E9A99A9C9A9990900900909909900000000BCFFFFFFFFFEFFFDFFCBC0F0DA9EDBFDF9FF9DFBFF9FEDFFEBDFFEDBFFFFFEFFFFEC09ACB00000000909000000000000000000900900000090009000009000A0000000D0FDBEDACFBCBCBA09A0A00909AC9BA99E0A9A90090B00BCA9000000009BFFFFFFFFFFFFFEFF00909E9CB9EDAFCF0FEBCF0FE9FBCFDFFBFFFFCFFFFFFFFFFFE09009000000A0000000000000000000000000000000009A0909000090900000000BFFE9FDBC9E9BC900909900B09B0D9A0999C00090909A0909000090090FFFFFFEFFFFEFFFFC90A900B0DADBDFBFFDBDB9FDBFCFFBFFEDFDBFFBFFFFFFFFFE900B0C009090900900000000000000009000000900000AD090A0009A000000909A09F9FFEBEBEBDADBAD0BCA09C0BCB0BC9B00A9090B0F0990B000000000B9FFFFFFFFFFFDEFCA0C9CAD0DADBEBEDADBEDAFCBFCBDBCFDFFEFFCFDFFFFFFFFED00009A9000000090000000000000000000000000000090000090900000F0900000DAFEADBFDBDF0FAD0A9090B0B0090B0BD0B990000090000900000090090FFFFFFFFEFFEFFFD09A0090B0F0FDF9FFEDBFDFBCBFFFFFAFFBFFFBFBFFFFFFFFFFE09000000000900000000000000000000000000000000900900000909000000A9A9AF9DADAFCBFBCBF9F0B0F090B9A9C90B90E909090B909B0900000000090BFFEFFFFFFFFEFE00909AD0BCBDAFE9FDBCBE9FFDADAFDF9FDFDFFCF9FFFFFFFFFC000090909000B009000000000000009000000900009A009A90000000B09E0900C90FEADADFBF0DBDAF0BC090F90000B0B0DB90B00090000090B0000000009FFFFFFFFFFEFFF090C0AC9ADBDAFDBFEBEFFDFF0FFFFDBFFEFFBFFFFEFFFFFFFFEB000000A0000000000000000000000000000000000B090000009009A9000000090A9F0DFAF0FE9EBEBDBE9B0B0BC090BD09A9A9009A90090B000000009009BFFFFFFEFFFFDFC000A9090F9EFBDBEDBFDF9FE9FFBDBEFFFDBFCFF9F9FFFFFFFFD000000090009090000000000000000000000000009C0000A909A0000009009A00A9CFEA0D0FDBFBC9CADBF090D0B00990B09B9E90090B009090900000000000FEFFFFFFFEFEF00090DAF0F9EDEDBFCFBFE9FFADFEFDFCBFFFFFFFEF9FFFFFFFE90000900000000090000000000000000000000090B000009000000B0F0A0000F0D0BFDFFAFAFEDBFBF9E9EF0BA9ADA0A9C9AD09A0900090000000000000009BDFFFFFFEFFFFE0900A909F0FBFBFDBFDEFFFFDFFBDFBFFFE9FDBDF9FEFFFFFFFFC090000090000000000000000000000000000000F00000909A9090090909AD000000FBEFDFFDBFE9E9A9B9BD09DB90D09B090B909009000B090900000090900FFFEFFFFFFFED000D0CBCBDADEDEFFFFF9FDAFF9FFEDF9FDFEFEFFFF9F9FFFFF9B00000000090090000000000000000000000000B090009000900009F0AC00000A0BCFCF0BEDAFF9F9ADF0FFA9AB00B09E9A9A9E90090900900000000000000FBEFFFFFFFEDF009009B0F0FDFBFBFCBDFFEFFDEFEDFBFFEFBF9FBDF0FFEFFFFFE00000000000000000000000000000000000009F000000009BCA900B00909A0AD0D09FA9EC9AF9EF0ADBAD0BDF9C9F0B09090999A900A09000009000000000909FFFFFFEFFEFC00B0E0F9FFAFDFDFFFFAFDBFFBDFBDFCF9FCFFDFBFFBDBFFFFFD9A0000B0000000900000000000000000000BCB0B000009A0090009E9E0000900A0A0FFCBFEDAF9BFDADBBF0B0BB099DA9F0BCB0D0009009090000000000090B0DFFFEFFFFFF000099F0F0FDFAFEBDFFDFFFCFFFFFEBFFFFBDEBCF9FCBDADFFFAD0000000000000000000000000000000000090D000090090B009F09090B00C090D09FEBFCBADBEF0BDADA9FDBC9FA0B90090099A909000A00000090000000009AFEDFFFFEFC09090E9F9FBEBDFFFFBFFFFFFBDE9FFDF0F0FFBDFBEFBFFDBFFFF0900000000000000000000000000000000B9A9A00090090900FA0BCA0000B0B0E0A0F0F0BEDFEF9BCADADFA9A9A9099E99B0B9A900009009000000000000009ADFFFFDEFFDA000F0BDAFCFDFFF9FFCFFE9FFFFFFF9EBFDFFADEBFDBCBCBE9EF09A00009C000000000000000000000000090C0900900A900B0F909000900D000C090DFACFEDAF9F9EBDB9A9BDBDBDB0B9A90990D0B09009000900000000000909ADEFEFFFEF009009F0F9FBFCBFEFFFF9FFF9FCF9EFFDAF0FDBFD0FDBDBD9F9FF009000A00000000000000000000000000B090E9009090B9C9A00E090C00A000B0CB0FDB0F0F0FBE9CBCF9EDA9A9ADBCB9ADA0B0909A9000900000900000000009BFDFFFFFFC009ACBDAFDEBFFDFBDBFFFDEFFBFF9FAFDFF0F9EBFBEFEFADADBE9000090000000000000000000000000009A090000090B0ABD090900B0A900F0CB0E9F0EDAF9E9E9BBCB9EBBDBDBDB09AD0999090090009000000000000000009A0FFFEFEF00000DBCBDFBFDF0FBDEFFFFBFFDEDFEFDFADADFE9FCBDBD9FBD0BD0B0000CB0000000000000000000000009C90A900A90BC9D00A000B0009C0B09AC090F0B9ADADBF0BCBBCB9DADA9A90BDA9B0A9A09A00900090000090000000909DBEFFFFDE090DB0FBFBEDEBFFFFFFDADFDFBFBFBDFADBFFADBCBDADBEF90BD009000900000000000000000000000009A000900999A90B0A090B9C90F00BC0AC0B0EFB0C09ADA9FDBFCB9EBB9BDBDB90B9099C900990090000000009000000000ADFEDFEF0000A9F0FCFDBDFDFDFBFFFFFBEFDFCFADFBC9ADADBCBF0F90FBCBE9AC000AC0000000000000000000000009A090E90A09E900900BCA9A00B0090090009FDBB0F09F0B0B9BCB9C9E9B0BCB9CBDA9B0900B00090000000000000000090A9FADFEF0090E9F9FAFFBEBEBFDFFFFFFDFEBFDFBCFFEDBDADBCBDAFD0F90909000090000000000000000000000009009090099E900F00909909C900000A9A9E90FBCDF0BE9BDBDADB9E9BBDADB909BB090900090909009000000090000009090FEDFED009ADBCBE9FDADFDFFFEFDBDEDBFDFCBFCBCBDADADB0F9E99AF0BDA00B009000000000000000000000000900900A9B00909B09ADA0E00A00D0B0D0C0000F09BA9F9BCBB0F0BE9BBCBF9B0F9AD90B009090000000000090000000000009E9EF9E000D9ADBDFDAFFFBF9FFBFFFFFFFAFBFCBDBDADADACBDADBCF99E0D090000A0000000000000000000000000B0009000B09A000000900000000C000B00A9FFAD9CB0F9ADBBDB9F0DBF9ADB0B90B99A9A00B09090090000009000000090B9E9EF009A9ADBCBEBFDBDEFFFFDFFFBFFDFDE9FCBE0F9E9DBCB9F0B0DA99A000000D00000000000000000000900900009A90900000090900909090B09A0000900F9FABBDB0F9AD0BCB9B09AF9A9BCBBCBC90900900000000000900000000000009E9CF009E9ADBD9E9FEBFDFFFFFFCFDAFBFFFAFDBDADBE9CBCB0F0BE9E9DA09000000000000000000000000000A9090000009000000A000A000A000009000090FF9FDFBE9BF9BF9B9E9FBD9BD9CB9090B090900090009000000000900000090B09EB009C90F0FEBDFBDFDBFFBFF9FFFFDFCBCF0F0F0F0DA9E9CB0D0909A09000009A000000000000000009000900000909000000909000909009000000090DADFAFFFADBDADBF0F9E9BADBBE9AB09F9B90B0009B009000000009000000000000DAD0D0A09ADBCBDFADFBEFFCFDEFFFF9FEBDF9F0F0F0DA9E90BC9ADA9E0900000000000000000000000000009A009A9000000000000000000000000900900B0BF9EBFFF0BDA9FBDB9AD9BCF9B99B0B09E9009000000009000000000000000909ADA00090F9ADBDADFAFDFFFFFFBFF9FEBFDBEFCBDAD0F0F09E90F090990F0900009000000000000000009009090000000000000900009000000009A090A9BDBFFBDF9FFF9FFBDBEBDBBCBB9F0F0DB9B090900909090000000000090000000000009E00090CBCBFFBDFBFBDBFFFFFFEFFDFADBCBCBCBA9E9E90F09E9E0B000000000000000000000000000000A0090090900000000090009000900009A90BCB9FFEBEFFB0FA9FAF9FBD0BF0FA9B9A9E99A9A90009A000900900000000000000090BC909AC9B0F9E9EBFDEFFFDFFDFFFFDEBDFEBDBCBCDCBC9EB0DA90900D0B009000000000000000000090090900009000000000000000000000A909090F0BFEFF9F9BF0FB9FFF9FBCBBF9F99F9E90B9E90900090009000000000000090000090AC000009A0F9E9FDFDBFDFFFFFFF9FDBFDFE9DACBCB0A90B0D0F009E09A90000000000000000000000900B0900900000000000000000909009090B0F0B09F9F9FBEFC9F90FB9BF0FB9F9E9BFA99F99090900900090000900000000000000000909000900D90F9FAFBEFFBFFFFFFFFFEFFAF9FEF9E90F9E9CA0B09E090000090000000000000000000000000A00090000900090090900000A90B009090DBFBEBFFDFBFEBFF9CFDBF9FADBFBCBDBA9ADB0B0B009000909000009000000000000000000009A0BDADBDFFDBFFFFFFFFFFFFFDFFE9F0F0FA0D0A9D0CB0900B00900000000000000000000000090909000009000000000000900900009ADA9A90F9F9EFA9EDBDB9EBBAF9ABDBE9DB9B0DB9A99C9090009000000090000000000000009090000A90D0BF0FBCBFDFFFFFFFDE9F9EF0FF0F0F0DE9AD0A9A0F9E90D000900000000000000000000900000000000000000000009000B00909E909C9AF9E9E9F9FFAFBEFDBDFDBFDBF9FBE9E9B0BDB09A900090A909090000000000000000000A00909C9AFC9FFDBFFFFFFFFFFFFFEFF9FDADAD0DA9AC9E9C9C90000A090000000000000000000000009A900009000009000909000090090B0090B0BF9E9ADAF00BDBF9BBEBFADBBDAB9F9B9E9DA90B090090009000000000000000000000090900000B0D9BE9AFFDFBFFFFDFFFF9F9EFAFDADA9A9C0B00B0A9A0BD090000900000000000000000009000009000900900900000009A090A9C0B0F0F90F0BDA0BF0FADFFDF9F9FBCBFDBE9E9B9A99F90B090A90009009000900000000000000000000090B0E9FFDBFFFFDFFFFFF9EFEF9CDADBCF0DA9C09E0D0C9C0A009A000000000000000000000000090000000000000000009009009CB090909AFB0F0F09F0B0FFBFBFEBFBDBF0BDBBDBC9F0B0B0D0B09009000900900000000090000009090009A9C9BDADBEDBFFFFFFFFFEF9F0FBBDAF09E0D0A9E09A0B0B09C90090000000000000000000000900000000009009000000A0900B90090B0BBD9CB0F09EF9ADB9FFCB9FF9EBDBDBEDB0BB099F0909090900009009000009000000000000000090009ADADBDFFFFFFFFFFFDF9F0FBCDAF09E0B0AD00900D0D0D00A0900000000000000000000090009000900000000000909090A900A9A9C9FDAFA9BCBE9F0F9FE9BFFF9EBDBBEBF9BF909F0B09A9A9000909000000000000000000000000900009F0F9FBEFBFFDFBFFF9FBEF0F9CBAD0DA0D0D00D0ADB0B0A0B0900009000000000000000000009000000009000909000000090E9090DA9A9AD09FCB9CBE9AF9FFF9AFF9FBDBDF0F0BEDA9BCB90990B90000909000900000009000000090000000090F0DBDF9FBFFDFFFED9FF0F0D0F0BCB0A0F0A900C0C90D0CA9C00000000000000000000000000000000000000000090B0090A9CB09ADBDAF00BCA9FB09FE9A9FFDBFE9E9FBB9F9B9BC9B9CB00900B090A0009000909000000000000090009A9AD0FBFAFFFFCFBFCBDBE90BCB9E9AC9C9C900D0E90B0A9A9090A90000000000000000000090090000000009000B009000900090B90BDA9A90BD0F9FFD0BE9ADEBDBEF9FBFBE9F0FCBC9A90B0A99C90900909000000000090000000009000900D0BFBCFDFE9FBFEDBFDAD9EDA9E09C90B0B0CB0B00B0C9C00A09000000000000000000000000000000000000090000000900B90B0CBC0BCBCBDAF9FFFB0BDBDB9EBFDBFADBDBC9B9B90B9CB99DA0B00009000000900000000000000000000009AD9ADBBF9FFFF9FBDEBDBE9A9C9DA9E0C0CB00C090C90A0F0D0E9009000000000000000000000000000000900009090900A90E9C9B0BDB9E9FEDFFFFFF00ADADBDBEBDF9FAFFBCBCBF90B90F09909090009000900009000900000000090009009AD9FCFEF9F9FFFFF9FF0DF9EBCBC90B09009A90E90AD0900A900B000000000000000000000900000000000090000000B000909A09F0ADF0F0BFDFFFFF0DBCBF0F99FBEF9F9F9BF9090F0B909A090B009A009000009000900000000000000009E9AF0F9F9FFFFFDFFFE9FB0FD0F09E9C9A0D000090E90A0909090C090000000000000000000000000000000000B000B0009DA9A99E9F9A0F9FDFFFFFFB0B0BF0FBEFADBDAFBCBE9A9EB909C9AD9A90090090000090000900000000000000909A90F9FBFBFFFFFFFFDF9FEDFF0BDBE09A0C9A0D0B009000DAC0CA0090000000000000000000900000000090900900900009A009C9E9A9EBDBAFFFFFFDFF00DADF0F9BDBFBF9FBD9BDB9DA90B0900909009009000000900000000000000900000D0F9E9FDFFFFFFFFFBFF9FBCBDEBC9F0D0BC09A00DA00F0009A90B0A0B0000000000000000000000000000000000000909090B09A9AD0BDADF9FFFFFFFF0A9FA9F0FCBDADBE9FBE9E9A99F090B9BC0B09A900909000009090000000000000090B0ADBFBFFFFFFFFFFDFFEDFFDBDEBCBDAD09E09C0090009A00000C90C000000000000000000000000000000009A090000A00909A9F0BBCBDAFFFDFFFFFF09F09E9E9BDAFBF9F0F9B9BDAF09A900099000900900000909000000000000000000009DBDFFFFFFFFFFFFFFF9F9FADBDDF0F9ADA90E9A90CB00C9ADA900090900000000000000009000000000090900000909090BC90F0F0D9FA9FBDBFFFFFB09E9E9A9BFAF9F9EDBFADADA999A9C90B9A09B009A0000900009000000000000009099FBFFFFFFFFFFFFFFFFDFFEFDFFEB0FDAD909E900CA90090000000A9A00000000000000000000000000090000009000A000B00ADA9E9ABE9FDFBEFDFFDF00BF0BDAC9F9FBFBBCBDB9B9CBC90B09000900909009000090900000000000000000EBFFFFFFFFFFFFFFFFFFFF9F9FADBDF9ADACF09ADA9009E0B090909C0090A00000000000000000000000000A90900909090D0909ADB0BDE9AFB9EDBFFFFF000BDA9FBCBEDBCFBF9AFCB0B9A909B0909090B009000090000900000000000000099F9FFFFFFFFFFFFFFFFFFFFFFDFFCBDE9F9B0F0909CBC090CBCA0E0900AD090000000000000900000000009000000000000B00BCB0F90BDBDADEB0E9FCBE0009ADADABDBBFB9F9FDB9F90BD0BC090F0009090A90000090B000000000000000BBFFFFFFFFFFFFFFFFDFFFDFCF9EBDBDADF0FCD9ADADA09BCA9009090A09000000000000000000000000000000009009090B0009090F00F9ADE9A90F9EBFEB009ADBDAD9FADF0F0FA9BCB0F90B909B09099A900900009000000000000000000BDFFFFFFFFFFFFFFFFFFFFFFBFBFDDADEDB0FDB0AD09AC9AC090CBC90009C00B000000000000000000000090900900000A00090900BCB0BB0F0B0F0F0FEFC0F0000A0BDBE9FFBF9F9FADBD90B090B0090B0090A9009000B09000000000000009BFFFFFFFFFFFFFFFFFFFFFFDFDFDAFF9FBCF0BCBD9AD0BC90BCA900A0F00A90000000000000000000000000000009009090B000A090B9C9CB0F0F09AB0FDBFE90BC900BDBE9FDAF0B9F0BBE99F0D090B0090909090000900090000000000009FFBFFFFFFFFFFFFFFFFFFFDFFFFCBD9FE9FBDF0FCBE9AD0BCB09D0F0D00900009000000000000009000009000000000009000909C0B0DAB0BCF0B0BC9CB0ADAB0C0B0A9E9E9FBFBDBDE9F9C9BE9B0B09090A90B09A900000900000000000009FBDFFFFFFFFFFFFFFFFFFFFFFFFDBFFEF9F0FA9F9BC90DA9CB0DA0A09A0BCA9CA00900000000000000000000900090009A0A900009009A9CBCB9CBC0B0B0FDADF00BC0DBCBFBDAF9EB0B9B0BF09900909009900909000090900000000000009BFFFFFFFFFFFFFFFFFFFFFFDFF9EBFC9F9EDF09FCBC9ADA9CB0DA9E99E0D009009CA0000000000000000009000000000900090000B00BCBCB0F9EA90B0E9E9A9AF0000B00B0DAFDFF9FDBE9F90BCA99A9C9A009009009000000900000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFDF9FF0F9A9DE09ADADBC9F0DAD09CA09ADACB00090000000000000000000000009000009000090009090F090E990F0F9B0F0F0F009C0BC9A9FBFBFFBE9F0BF9B99A0D0B0909A900B00900900000000000009BDBFFFFFFFFFFFFFFFFFFFFFFF0FF9E9F9EDDAB9E90B90E9A0B09ADA9C9E9090C9A00000000000000000000000000000B00A900009000B0B0E9BCACA9F0E0F0B0BB000A90A09E9CBDF0F9F9BD0F0DAD90B0900090900900000090000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9EBF0F9ABD0F0F0C0F9CBD0DADA9E9A9CBCB9AD0DA90000000000000009000000009009000900A09A0D0BD0A9DB9E0B9F0BC90F0A09CB0DA9EBFAFBDBFAF0B09A9B0B09A9090900900000090000000000BDBFFFFFFFFFFFFFFFFFFFFFFFFFDBDAD9C9090D0F009E9B90AD0FADA9C9A9CA9000C0B0000000000000000000000090000000000000090090BC0A9DA0E9F9E00B0BEFC900BC0BCB9F9F9FBCBDBDB9F9090909C90000B0090009000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D9A99A090909F090CAD9AF09E9E9ADA09E9E9A9009E900000000000000009000009009009000900090BCB0D0A9F9ADAD0F0F09B0AC90B0DA0DAF9F0FBCBCBCB0F9E9ADA909A9009009000090000000009DBDFFFFFFFFFFFFFFFDFFFFFFFFFDBE9C90D9D09A0009E9B0AD09E99E9E90DBC9A9C9E9E00A000000000000000000000000000000000090A009CB09DA0FBF00B0B0F0D00B00F0A9FA9FAFBF9FBF9F9F0B909090BC9090090000000000000009FBFFFFFFFFFFFFFFFDBFFFFFFFFD9FC9F9BDA90BC9D09000C9C0F09E9F09E9A0BCBCA9009090D00000000000900000009009000000900000909A009A0BDBCA9E09E900A09FCB0099E9CBF9FDADBDB0B0B9E90B0909000090000000000000000BBFFFFFFFFFFFFFF9FBFFFFFFFDBDFF9F90D090D9090000909A9B0BCB0F0F0F0D0909DACBCBCA0000000000000000000000000900900009A900E90DA0F9E9E9F09E90F0D000B0F00A9BF9FFAFFBDAF9F9CB9BD09A909A9009090000900000009DFDFFFFFFFFFFFFFFFFDFBFFFDBDB99C9E909090090909000090C9CBDF0F9E90BCADA009000909C00000000000000000000900000000900000900B0999E9E9E0DA9A000B0009E9E90E9FADBF9FABDBE9A9CB0B9090B090900000000000000000BFBFFFFFFFFFFFFFF9FAFDFFDBD0D009990F9CBBC9E0909090090A90ADBCBDBE90D0DAD0F0F0A0B00000000000000000090000000000009090A909A0E90F0B9A0F0C900DA0009E9AC9A9FBFDFBDFADBDF9B0F9CB090900009000000000000009BDFFFFFFFFFFFFFFFFFDBFF90009A9FBEFFFEF9CBE0900000000090F9ADBCBC9E9A9A90B0909D000000000000000000900009000009000000090BC909ADAD0E9C090000A9A00B0BDB0BCBDEBBFFBDBFA9B099AB090DA9090000000000000000FFFFFFFFFFFFFFFFFDADAD090099BF0FFFFFFF9AFF09E0F00000000090F9ADA9A9C9E9CB0CBC0A0F000000000000000000000009000009009090F0B0DAD0F0E90B0E0000F00009ADADE9BFBFFDF9EBDB9F0F0BD9DA9090009090000000000009BDBFFFFFFFFFFFFFFFFBDA900000FDFBFDBFEBC9E9E0900000900000ADAFDBDCD0B09A9CA90A9C90000000000000000000900000090000000DA090DA09A9009A0C9000009FA90C09A9BC0FF9FAFF9FADFB9BD0BA99A909900000000000000000BFFDFFFFFFFFFFFFFF9FF9CAD0090B0DAD9BD0B09090090090000090909ADAABCBCBC9E9DAD0A9E9A0000000000000000000900000090090B090A90909DA9E0D00000000E900A9AC9E0BF9FBFFDBFFDBADE90B9DBC90B0A9000000000000000BDBFFFFFFFFFFFFFFFDFF9E99909090999000090900090009000000000BC9ADD9AD009A90A9A9C900D000000000000000000000000000000A000F09ACB0A0C900A0009009B0A90009A0BF9EFFCFBFF0BD9FB9BD0B0B9090900900000000000009FFFBFFFFFFFFFFFFFBF0FFFFFF99090F09BD99C900900990009A90DAD00BDB0AD0BDBC9E9C9E0A0F0A00000000000000900000090000009090909ADB090DB0B0C9000C9EF90A9ADAD000FB9FBDF9FFFAF9BCB0BDBDA9E909000000000000009B9FDFFFFFFFFFFFFFFDFFBDBFFFFFDF99BC909A909D0BC0C90BC0DA090BD0BCBD0F000A909A099C909000000000000000000000000000B0000A0BCB0000B0C0090000B0F00FA009A9AC909EFFFBFE9FBDBCB9DB0B9A90900B000000000000000FFBFFFFFFFFFFFFFFFF9FFADFFFFFFFFFDBDBC9FDA9C9B9ADADB00D0E00AD0BCB09F9C9E9E9E0A9AC0F0000000000000000000900009009090909ADBC90C9A9CA0A90CBCBF00B0A00A9A00B9FADFBFBDBFBDA9AD9ADB9A9090900000000000009BFFBFFFFFFFFFFFFFFFF9FFBFFFFFFFFFFFDFF0BDFBDE0D0900DA0B09F9ADCB0F000B0909099C0D0B0000000000000000090000000000000009A9A090A9AD0B090CA90A090B000B0D000B0FFFFBDBCBF9CB9F99ADBD090D0000000000000009FFDFFFFFFFFFFFDBFDBF9FDBDFFFFFDFFDFFFB09FCBCB099E9E9A9AD0F00DBB0D09E90D0F0F0E0B0B0C000000000000000000000009009009A9E9C909A9000BC0CA0F0E90FA00A90B0BC00DBFF9EFFFF0FBDA9ADB9B0F909A909000000000009BFFFDFFFFFFFFFFFFFFDEBFFFFFFFFFFFFBFFC9E9BDBC9E0900D0D09A9DBAC0F9E99E9A090090900C90B00000000900000000000000000000909ABCB0C00F09A9ADFAF00B0DA90A00B0B0A00BFF9F9FBFDA9F99ADADB09A900000000000000009FBFBFFFFFFFFFFFFDBF9F0F9FDFFFFFFFDFFFF9E9F0B090A9B0B0F0DA0D9BD0A9E909CB0F0CB0F09AD0000000000000090000000000900900BC90909A99000D0AFFDFBC0FA90A90B0DAD09ADEBFFEBDFBFF0BC9B9BBDA909090000000000009FDFFFFFFFFFFFFFDFBFDF9DBFFFFFFFFFFFFDBD09F0D09E9C0C90D0B0DB0F0BFD090F090D09A9C09E00BC0000009A0000000000000000000090B0F09E90A09A0BDFFFECB0B00A00A00B0BCA09BDE9BDFBFF9BDBB0DBD0909A900000000000009BFBDFFFDFFFFFFFFFFDFADBFCBFFFFFFFFFFBFCBE909E0000B9AD0B0DB0F09C90B0F00BCB0A9C0B009F00090009000000000000900900090BCBDA90F09AC90CBCFFFEDBCACB00B09A9AF0F0F0FBFFFA9E9FFEB0DBA9BF9E9009090000000000BDFFFFFFFFFFFFFFFDBFBDBC9BDBFDFFFFDFFFDBD9E9009090000B0D0BCB9CBBCBC90BD090C90B00DA0009C0090A000000000000000009000090A9CB0BC0BCB00F0FDFACB0BCB00A00090B0F0F0F9FEDBFFBF9FB0DBCB09909A00000000000000BDBFFBFFFFFFFFFFFFDF9FBC9BDFFFFFFBFDFBFF99E9000090C90B0D09CB9C9BDBCBCADA9BC0D0B09E90A909E09C00009000000000000090B0990BC9CBC0BCB0BF0E9F9E0EB00A0A9A0A0F09AF0FBDBCB0F9FBDBB99FFB0B0990900000000009BFFFDFFFFFFFFFFFFFFFF9DB9CB9FBDFD09B9FDB0F9000000090009A0F90F9E9E99CB909C09B0090C90E90B00900B000000000000000000009E9E90B009ADA9E00BCA0A09900B0900009A9A009FADAFF9FFFFDAD0FFB90D0900000000000000BDBDBFFFFFFFFFFFFFFDBDBE9CBDFFFFF99F9FDB0FD0E99A909A90DBC990F9E9CB9EBD0CB0B0C0F0A9A09000CB09F000000000000009090909E9A90B0CBCBCBCB0B0B0F09AFB000A0A0A09AC9E09FFFDBEDAFBFFBF9A9FB9AD0B0909000000009BFFFFFFFFFFFFFFFFFFFFDF9B0909FFFFFFFFFFDBFBDAC90F0DA0009F0F0F9FBDE9D0B9C9CB090C9009CADA90E009000000000000000000B09A9EBCBE9ADACB0A0CA00A000FA9009090A09A09A09AFADBBFDFFBDBF9F9E909009000000000009DBDBDFFFFFFFFFFFFFFFFBDFDF9F9FFFFFFFFFFFDADB9AC90B0D09A90BDBCBC9ADA9FCB9A9CB0B009E090090909E0F000000000000000900BC909CBCBCF0B0E9CB09A90B0F00A0A0A0A90A0B0090F9FFFDFBFBDFE9FBE9F9A900090000000000BFFFFFFFFFFFFFFFFDFFFDFBF0F0F9FFFFFFFFFDFFFCD99E9CB009C9E9ADBDBFDBDBA9C0DB09C09E090A90CA09A09009000000009000900909ADA900FFACF0B0A00A00A0090B00900900A090ADAF0FBCBFAFFFFBDBDBDB90909A900000000099F9FBFFFFFFFFFFFFFFFDFFBFDF9F9FFFFFFFFFFFFDB9B0E9CB00B09E9FDADAD0BCBC9DBDB0CB0B090B0D0A909E9D0F9E000000000090009A9ADA9ACBDADF0B0F0B0BCBCB0FA0A0A0A0A090A90B09ADBFFDFFF9FFBFAFBF0B0900090900000009BFDFFFFFFFFFFFFFFFFFBFDFBFFDBFFFDFFFFFFFFFEDE99AD0C90DA9DA9FBDBFDF9FEBCBC9BC9CA9C000D009090A90000000000000000B0D09A9C0BDADA0FCA00E9E9E9EA0F909009000A00A00A900CBFBF9FF0FFF9FDBFDBC9900000000000ADBFFFFFFFFFFFFFFFFFFDFFFDFBFDBDFFFFFFFFFDFDBDE09A9009ADA9F0DAD0B0F099DB0F0CB0900BCB0A9CA00B0F090F000000000090009AD9ABDAFCADA0BDBB0F0FAFDFF0A0A0A0A9A09A09A9E0B0BCFFFFBFBFDFBBF9B09A09090000000099FDBFFFFFFFFFFFFFFFFFFFDFFDFFFFFFFFFFFFFFFBDA9DAC0B0BC90F0FBDBFDF9FFE9E90F90DADA090900A9C90D0BCB000000000900090B0BED0BF00900F0A0CB0F0D0EAB00B09009000A09A000900FFFBFFFFDFBF9EDBCB909000900000009FBFFFFFFFFFFFFFFFFFFFFDBFFFFBFFFFFFFFFFFFFDE9DA099C9CBBFF9E9DADABCB090F9F9ADBC909CAC00900009AD00090000000009A0B09F9AFC0D0EBDA9ADBACBEAFBDCB00A0A0A0A090A0B0B0E900FFF9FFBFFBFBBF9AD09090000000009BFFFFFFFFFFFFFFFFFFDFFFFFBDFFFFFFFFFFFFFFFFBDAD9E00B09C90F9EBDBFDBDFFF9E9AD909ADA099A0090B0000BDA00000090000900FADAD0B0A90C0F09A0DB09000ABCB09009009A00900B0B0EB09AFFFFFBFDFDFBDB9A9A0909000000099FFFFFFFFFFFFFFFDFFFFFFDFFFDFFFFFFFFFFFFDFCBDA09B9CBCBF9E9DADF0FDA9DAF9EDAE9E90DA0C900000909009C900000000900909FADAC0090B0B0F09A00E0BC909A00A0A0A0A00A0A9A00B00CADFBFFDF9FBF9ADBC99090000000009BFFFFFFFFFFFFFFFFFFBFDBDFF9FFFFFFFFFFFFFFFFBDADBC0CB0B09E9EBDB0F9ADFAD9E9BD9E9ADAD0B00000000009A9A0000000000B0DB09A9A90CAC0F00BC9AD0B0A0AE90A0B090B09A909A09ADADA09BDFBFBFBF0BFFB90A900909000009CBFFFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFFFFFFFFDFCBC9009B0D0DE9F9DBCFDADFA9DAF9E9E9AD09A90C00000900909C0000000090909ABCBC000A090B00B00A90B009A99E09000A00A00A0A0B0B0A9000EBFFFFDA9F99B0F99090000000009BDBFFFFFFFFFFFFFFFFFFFDFBFDFFFFFFFFFFFFFFFFF90F0F0C9ADB9E0FEDB0BDA9DEBD0F9E9ADABC9E9A9000000000A9E00000000000F9CB0BC00D00009AC9ADAC0F0E00A9A0A900B09A9009A0A0B0E9090DBFDBFF9BF0DB9E9DA90900000009BDFFFFFFFFFFFFFFFFFFFFFDFFFDBDFFFFFFFFFFF9EFF090B0D0ACBDB9BEFDAFDA99EBDADBDE9DA9E9C000000009090909000000090B0ABC0009A000ACA0B09A9A9A90B0DA090A0A00A00EB0CB9CA90A000BFFBFF9E90BB9E99A9000090000909FBFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFF90F0D09A9DBC9EFC90BD0BDE99CADACB9E9CB09A900000090000BC000009090009D0B009A0090090900E9EDAC0AC0A90A00090A09A900B00A9CA0900BCBDBFFBDB90DB9A090090000900BDBFFFFFFFFFFFFFFFDFFFFDFFFDFFFFFFFFFFFFFDE9CF09A0A9DACBAD09BFECBDA9ACBF9BDBCBCBC9E9C000000000A9C9A0000000A9B0A00CB0C900C9A0A0A9A9A09A90BDA00A9A0A9A00A0B09A9A09A0909FFFFBF9AD0B90F99090090000900B9FFFFFFFFFFFFFFFFFF0FAFDBEBDFDFFFFFFFFFFFFB0D00D9CADBCDADAC099BC9EDB9CBCBCBCB09E9A000000000909A0C0000009000C9A900000A9A0009090DAC9E0A00AD0B0000000B09E0EADA9A09A0000BDADFF9B909B9ADB090009000990F9BFFFFFFFFFFFBDBE9FDBCBDDFAFBFDFFFFFFFDBCDB0B0A0B009A9DADBCAC0A900FA9CBCBDADFA9C9E9000009000C9B00000000090B0C0BC0B0C09A0A0A0B09A0090B0F0A00B0B0B00EA0B09A0E90ADADA9AFFBFBE9E9A9C9B0900900000009B9FFFFFFFFFFDFDFF9F0BC90A90D9EDBFFFFFFFFFFBC00D090C9E0CA900909090B90DA9DBC9F09CBA900000000090B0C900000009A900B000900B0E0900090A9CB0A0000B00A000000A909ACA9A90A9A900CBDBFDF9990909BC9A9000000099F0DBFDFFFFFFFFFBF0FCBD0BC90F9ADBFFFFFFFFFCBC9090AC0B09099C0BCBCAC0C0CB9EBCBAC9EBC9CA000090AD0A9CB00000090000CB09CB00F009A0A9A0AD0A0BC90AF0B090B0A0B0A0E0B0E9A99E9ACB00BDFAFFA9B0B09B909B0909000099A9BFFFFFFFF9FDFF9BDAD09A90090F9FFFFFFFFFDAC0A09090009E0AD00090B090B0E9CB0DBAD09E9C900000909CB00B900000909A90CA09E9ADA09000909A9AD00A90BC00A00A90090A90E90A0EB0F0BCBC9ADBDBDAD09CB0BC0900000909A9DBCBFDBFFFBFFBCBC009000909ADB0F9FFFFFFFDF9900D0BC9CB09000090000000090B09E0D0BCB0B00000000A0B0F0C000000000DA9BDA0000A0A0A0A00A900A900A0CB0A09A00A0A09A09AD0B00A0B0B0B0BFFBFF99B0909DB900900000909A9BDBFFDBFDF0F0909E0A9C0B0D09DBFBFFFFFFFACA090A00B00DAD0B00000000090009E9B0F0BCBCB0000009C90D00B000009A09A9CA09CBE909009090B00AD0E0BC9B0B00A09A9009A0BE0ABC9A9F0F0F0D0BDBDBCB09B09A9A090900909B99BF9FBDA9DB0909E0000D009C90ADB0FDFFFFFDAD909E090900F0000C00C0090000090F090E90D0F0C000009A00DA90900000009A9CB0F0B0000A0A0A0A00A90A9A00AF009A9A00A0A00000BC0AAD0A9AA0B0F0BFBFB9F0090909B000000009AD09EBDBDA0090FFF00C0000A009009FBFFFFFFFFA000900C0090BC000000000000000009E09EB0F09A00000009A90000000009009EBC90F0A9A00090000A90EB0009E90FA0009A0090A9A000B09A0BCBD9BCB0F9E9FF9B90B09F00F090900909B9999EBD90009BFFE0000AC9E009009F9FFFFFFDFDA009A90000EF0A00000000000009A09F09CB0F0D00000090C0F00F0000000BC90BF0AD0000B0A0B090A900E9A0A0F00B0A00B0A00009A00E0CB0B0AACB0BCBFBF9EBF9090099090000009090BCB9FBE900009C09E0000009009BE9FFFFFFFF09090C000B09000C0000000000009A9CA0BCB0CBCA000000009A0090000090909AFC0E9A0A0B000000A00A0B00090009A0A09A000B00A0090B0B0A0CB990DADBDBFFBD90F099A009A909090B0BDB09FF9D0B0000000000009000BC9FFBFFFFF0F0C00B0090000000000000009009C0CB09DA9CB09000000009A0900900000009AFCB090009000A9A0A09A0900B0A0BFA909A009A00A900A0A0CA00B00EABADBAFDBF9FA9090009A090000090D90BDB9FFA90FF0000000090009F9BF9FFFFFFFF0B0B009C009E900000000000000A9A9CBCACA9CAC0000000009C090A000090B0F9000A0A0A0A000909A000A0A000009E0A09A0A0B00A09009A90F00B090CB09DBBFFF9FB09B090900B090909A99090B9FDF909E9C00090090000FD0F9FFFFFDFE9C00909A0000F09000090900A90C9A90B99CB90B000000000B0000900000090AF00C90900909A0A0A0A9A0090A9A0F0B00A009000B00A0A000A00A00A9A9AFADFF9FBF9F09000090090000B90F9F09FFBEDA909A909000009BDBEBFFFFFDFEBD0B09E0A9C09000E09E000009C0E9AC0F0CABCAD00000000000DAD900000900BD000A0A0A9A0A0000090009A0A00000B00A09A0A0B00A00900B00B09A9A9AD09FFBFFFDB09090900090A9099090B0B9F9F9BFDAD00AC0909BADBCBDF9F9FFFBD0BC0090D090000909009A909E9A90D9B0CB9C09A000000000009A9E00009A0BC0000909A9000009A9A0A00A00090A0F0A09A09A9A0A909A0A00B00A0A00E90B0BFF9FBFFB90B000900990000B099D90BFFFDEBFBFF99BCB0D9E9F9BCBFFFBFCFAD0F0000A0F0BC00A900CADA90D0F0AC0BCA9F0C0000000000009F0000000D09A09A0A000A0A9A000000A900B0A000AD0A00A000F0CAA0009A00A0909A90BE9F9FFFFFBDFA909900000A99909B0B0B99B9FBDF9F9FEF0BDA9E90BC9BDADFF9F9DA90F009000009A9CADB9CBCAD0B0D9AD09E00B00000000000000000000900BC0009A9A0B09000A0A0A90A00000A009A90B09A9A0BA9CBA9A0A90BCA00A009AB0FFBF9FFBD9B009090090A090D099C0BDFBDBDE9E9909009009F09E9ADBFCFFBE9F090F0090000009000E9E9DAF0F0AD0BC9DAC000000000000000000000A90B00CA00CB00AE0B090D00A09A0B009AF0A000A00ADAD0A000009CA0A09A9A9EBC9FBDFFFBF0BC9B00009099909A9B09BDA99CB09A9000009009A09F9BDFBDBFBC9FCBEF090009000000909009A09C9AD0BCB0A0900000000000000000009090BC00B0B0B0CA90900A0A0A90A00000A0AD0A0A0A90A0AE9A9A9A0A909A0000A90BBEFFBFBDFF9B909000000BC90990B9099EB90B09CBC9CBC9E9DBDADF0BDFFFDFE9BD99CBCB00090090A000B0D0F0BCBC0BC0DA000000000000900000000B0F00CB0C0A00B0A0A0A0B0BC0AC9A0A0009AB09090A0909A0CA009A0A0A9E0B09AF0DBFFFFFBFFDAD0B90000909A9A99CB9AD9CBC9CB09A990BF9A9ADB0FFDAFDAF9FCBEFADADCB0F0DA09C900C9A90F0F0BD0B00D00000000000000090009009AD0A0B0B0F00E90DA90000B00A00C9ADABC0A0A0A9A0A00B09A0A0F0BC00B0AC00BA9FBFDBDFBF9B90D090000B909CB9099BFB9B090F9C9AFDAFDFF9FF9EBFDBFDFBBDBDFBDBBDF0FADBE0E9F0ADFE9F0F0AD0DA0000000000000000000900F0F0AD0A00A00B0A0A0ACB0E0A90B0B00A0F0A900B000B00B0AA0F09000A0B0C0B0F09FFDFBFFBFDF0A9A0000099C09B90B0FADBCF0BF9E9BFDBF9F9BCF9F9F9BDFF0FCFDE9DEFCF0FAD9E9F9BCBDF09F0F09DA0B00000000000000000000009090000A90B09A009A090B0A9A9CA0A00A900B00A00A9A0A00A0900A0A9A9A0A9A0A0BE9FAFFDBCBFBD909909A009B900BD9999BDB9D99F9F0DBFDFBCFF9EFFDEFF0FFDBFBFEBF9F9F9DEBDF0FCBE90FE0F0BCAD0C0000000000000000000090ADAD00B0CA0CA09A000A0000000A0900B0CAF0B00B00A090B09A0AB000A00900A900909A9FBFBFBF9EBF0BC909000099B0B0B0D9BDABCB0F9B0F9E9FBDBF9F0F9DBF9FBCFDBDF0FEDAF0DEBCF9FDFFF9F9E9E9B0B000000000000000000000A90B000A0B09A90A00A900A00B0A09ACB0CA90BE00A0A90B0A0A00B000B000A0A00A0A0A0DADFFFDF9FFDBD09A009A99AC0999DB0BCBD9B9F9C9F9FBDBDBCBF9FBFADFADEBDADADFDBFDBFB9DBFCBE9E9E9E9E9C0D000000000000000000009009AF09AD000A00A9A900A909A0090A0A00B00AD09A0900A0A9A90B009A000B0090B009A09A9BFBDBABDBBF9A9090099E99BDA9A9099B0BCBCB9BF9E9FAFDBFCBDAD9F9DFBDBFFFBFBEDBCFCFEFCBE9F9E9E9E9CA9A0000000000000000000000DAD00E00B0A90A90CA0A0A0A00A0A0090A0A90BA000A0B09A000A00A000A0000A000A090A0BC9FFFFDBFDEBDB0090009A9099F9B09E9F99BF9E90F9BDDB9E9BCBDAF9EBC9FCBDBDEDFADBDBF9DBFDADE9F0F9CB9C00000000000000000000009A90F009A000A00A0B0009000A9000B0A9090A0CBA90000A09A0B0B09A09000A00A090A00090BCBFF9E9BF9FBFF909A099F9A9A9D09090F090B9F9BCFB9FF9FDBDBDAF9FBEB9FEDBFBDFFEBCFEBCBDFBDADBCBACA90000000000000000000000A9E9E9AC9A900B0000A9A0A9000A90000A0A000BC9A0A9000A09A00A000A0A900900A000B00A09BDBFFFDBFBDBDAD09000B0DB9FB9A9099BDBD09ADB9EF0BDBADADF9DADF9FEF9FCFDFADBDBDBDFBE9EADBCBC99C00000000000000000000090D09A000B000A000B0A000090A9A00A9A00009A0F0A0000A0A90A00B00A00000A0A0B00B000B099EFFFBDABFDAFB99A90099FB0DB0090B0F09A9BBDB9F9BDBCBDF9F0BFFF9EDF9EFBFAFDFDEDAFE9EDBDDBCBC9E0A00000000000000000000000A0BC9CB00A0000A0090A9A0A0000A00009A0A009B00B00090A90B00A90B0A00000000000A000A09A9FFBFCBFF0FFADA99A09DBA9F900099F9DAD09E9E9CBDBDA9E9FF0F0FFBFF9FDFDBFAFBFDBDF9EDABCBC9A9C0000000000000000000000909F0BA00A09A9A09A0A000900B0A090A0A000000E9A000A0000A00A90A00090A090A0A0A09A09000DBFDFBFDBDF9FDBDAD9E0BDDB9F9900B0B99B9F99BF9BCB9F9BDADBDF9FCBFFBEBFCFDFDAFCBEF9FDE9E9EDA909000000000000000000000A09FC0B00A000000090A0A0A0009A00900A0B0A0BF0B0A00A0000B0A09A0A0000A0090090000A0B9A0BFFFFBFFBE9FFADAB990BA9EB0B0909DAF0B0FBC9DA9BCBCFBDBCBBCFBDE9FDFDBDBCBF9FDDBE9E9F9E9AC0A000000000000000000000909E90BCA900A0A0A0A0090000A0000A0A9000000E000090090A9A0009A0900A0000A00A0A0A00900C9FFBDBCBFFDBFBDBD09E9BDFB9D090B9A999CB909BA9FCBDBBCBCFBCFBDFBFCBFAFFEFFCFFEBE9F9F0E9AD0D000000000000000000909AC9EBCA00000A900900000A00B0000A090000B0A9AB9A0A0A0A00000B0A0A0A00B0A00000000900A0B000FFFFFFBFFF0FBCBDB9E9B9F9A909CBD9E9BDA9E9D0B9F9EDBDBFDFBDAFCFBDFF9F9F9F0F9FDEFADF9EDA0A90000000000000000000090A900C0B0B00000A000B0000000A09A0A9A000000CB09000000A0B00009000900009A0B00B00A09009A90BFBDBDBFFFDF9ABCB9E9E9F9E9A90BA909AD99A9F0F0F9BCBDAF9EFDBFDEBDCFEF0FFF9E9F9CFADA9ED90C00000000000000000000090BC0B0000A00A000A0000A00A900000000B00A00BCA0A090A00000A0B0A9A0A00000000000A00A09AC0BDFFFEBDADBFAFDF9F0F9FBCB9099BD9F0990BE9F0BD9BCBDBE9FEBDAFDBFDFBF9FFDADEFF0FF9FADE9ACB00900000000000000909A00BCA000A0A09A00A0900A0900000A0A9A0A00A900DA9000A09009A09000000000A0A00A0A0009000A0900BF9FBDBFBFFFDA99A9B0F9F9E90BCBE90F0BC990BD0BE9F0F9FDBDFF9FE9FAFDFCFADFBD0FF0F0DF0BC900000000000000000000009DA9009A9090A090000A0000A0A0A000009000000AB0A0A000A0A00A0A0A9A0A9009000900B0A0A9009AD00FFFFFCBDBDFBFEFDADB0FBF9AC9B99A99099A0F90BD9E9F9EF0FF0FE9FEDFDAFBDF0FCBFA9FCBF0BCB000000000000000009009C90AD0CA00A0A0000A9A0000A00000090A0A0A9A0A00CF090B0000000009000090A0A00A0A00000000A0000A90BDBFF9EBBCF9FBFDADB9DAF99ADAD9A09E9990BD0ADB9CF9BF0FF9FF9FBEFDFCBFF9FCFDE9FCBCDAC9E90000000000000000A00A90A909E9000B0A00000A090A9A0B0A090000009000B00A000A900A9A0A0B0A000000A9000B0A9A0009A9AD00BFF9FF9FDBFFF0FBDBCBBDBE909B9AD0B9A0C900B9ADAB9EDFF9CF0FEFDBDADBFCBCBF9EBEDBDBAD9A0000000000009000009009CBC0AA0A0B00090A0A090A000000000A0B0A0A0A0ACB0000A00A00000000000A90A000A9A000009A00000009E9FBEBFCBF0F9FDADAFBCBF990F9E90909C9B0E9D0DB9CFB0F9EFBFB9DBEFFBFCBDFF9EFD9F0E0D0AC90090000000000900900B0B09AC909A00A00A90D0A0009A0B0A9A0A009A00000BCA9A09000B000A90A0B000009A0000A0A9A00A0A9A9C09BDFDF9FA9FAFBFFBDBDBD0FA90B99F0B0000990A9B0FB0DF0FBDCFCFADF9EDBFFEBCFBDAF0F9DAC9000000000009000090A09C0DAC90A0A0F00B00A0A00A0A00000000909A000B0B0B00000A0A000A90A000000A00009A0909000A090000A900EBFBFFBDB9DBCBDFADAFB9FDAF9E900DB09000900C90DB0BF0FBF9BDFA9F9FCBDDF9EDBDADACB9B0F000000000000900090CA9A09A0D0BC00A00A90B0A9090A9A0B0A0A0A0A9A0000CB0A0000900A00000B0A0B00A0A000A0A0A900A0A0900A09CBFDADFCFBC9FAFDBF9FF0BC9A9A9F0000A90009B0DADBC9F9CBCFADFDEFAFDEBFEDBCF9E9F0C00009000000000000B00090009E0CA00A9A90A0A0A00E0A00000009000090000A0AB0000A000A9000A9A000000900000A0009000B0090A0A900BFFFBFBFA99EBDBFE9FE9BFDBDAD90009090009B00B0909EBCFADBDADAF9F9EBDADBEDBCBD0A9090000000000909A900A90A9DA90B00B0000A09009CB009A0A0B0A0A0B0A0A0B090CB00009A0000A900000B0A0A000B000B0A0A0000A00900F909FFFDFBDEF9CBCBDF9BFDB9EB900A9000090000D090CBC9CB9DBCBDBD0F0F9EFDEF9F0F0AC9CA09000000900A000009C09C0A9E00B00A9A090A0A0A09A0009000900000009000A0BC0A000000A00A0A0B0009000A000B00000900A000A09A00E0BDFBCBF9FAF9BFAFFDBFEF9CFBC9009000000B000B09A99CACADADAF9FDAD9A9F0F0F0D90A9000000000009009C90A90A909E9CACB00A0A00009A0A00A9A0A0A0A9A0A0A0A00009A900A000B000009000A00A0900A000A0B0A0A90A900A09A909AFFFDBFFDBE9FDBCBE9F9EB909A9C0009000090000000E9999ADAD0F0ADAFDE9F0F0F0AC9009000000000009A00B00090BCBCA900A00090A9A00900B000090000000900009A0AE000000A0000A9A0A0A90A00A00000A900000000000A09A0090FDFFBEBDAFDFCBFFF9FBFBD0F090A009000090090909090ACAC909A9ADBC90B9E9F00909000000009090909009B0D0F0F0F09000A909A0A00000A0A000A0A0A0B0A00A00A009090A0A09000A000000000009000B0A900A0A0B0A0A000000B0E00BBDFFDBDBFBFFDBFFFCBDAB0BC09000000000000000009090BCBC9C9009E9C090090E00000000000000ACA9E0C0A90B0F0BC0A900A0000000B00900A0900090009A00A9000A0E90000A00009A0A9A9A0A0A0A0000A000900000900B00A0090B0CBFBDFEBDEDBEBF0FBFFFDF90B00B009000900000000000090000A00F009A9E0F0E90900000000000B090909B090F0F90000000A00A9A09A00A0A090A00B0A0A00009000A000BCA90000A9A000000000900090A0000B0A0B0A0A0A00A90A000090FFFBDFBFBDFFDFFFF9FBE9E909000000000000000000000090909000900090009000000000000900C0BCB009E90900F0F000A9000000A0000900A000A000900A0A0A0A09A0CB00A0A000000B0A0A0A0A0A0A090A0000000090000000000A9A0F9FBFFBCBCF9FFF9F0FFF9F99E00909A0000900000000000000000090009000900000000000090A099B090C9E90BCAF0900A0000A00A0000A0A0A090B00B0A0A90000000000B0000090000A000090900009000A09A0A0B00A0A0B0A000A9009000FFDFFFFFBE9BFFFFBCBFBCB09000090000000000000000000000000000000000000000000009C9A0000A9A9AD0BD9AC00900A09000000090009A0A0A0000900A9A009A0A0CBA0A00A0A09A0B0A0A0A0A0A900A009000A900000090A900A0A0909AFBDFBDFFEDAFFFFFFDFBCBCB00000000000000000000000000000000000000000000000000009E9C9C9C09AD0AC0B000A0000A09A0B0A0A0A09009A0B0A000009A00090BC090000900000000009090000E900A0A0000A09A000A00A00009A0E9FFFBFFBDBDBDBFFDBEBDB909C90090000000000000000000000000000000000000000090A9A9090BA9AB9E9EBDA9C0A0090A00000000009000A0A0000000B0A0A00A0A0C90A00A00A0A00A0B0A0A0A90A9A0B0009A0A00A00A00000090A00090FBDFDBFFFFCBCBFFFDFFBCBCA000009009000000000000000000000000000000000009A09C90F0BC9DADCB0900C0B0000A09000A0000A00A0B00900B0A9A00000009000BA00A90A0000090009C0000A00000CA9A0000900090A0A9A0A000B0A90FAFFFCBFFBFFDBFFBF0F9B090900000000000000000000000000000000000000000000F0B0F0F9BEBDBB0F0F0B0000B0000A000A0A09000000A0A0000000B0A9A0A0A0CB0000090A09A0A00A0B0B00A9A0B0000A9A0A0A0A090000000B0009A09FBFBFDADFFBEFDFFFFBC9F000A900000000000000000000000000000000000000090090DA9BCF9F0FC9E9F0C09EB0000A00A900000A0A90A00000A9A0A00000000000BC0B0A0A00000009A0000009000000A0F0000009000A0A0A9A000A000BC0FDFFBFBFDFFBFFF9FFBF09F090090900000000000000000000000000000000009E90F0BDFEB9E9F0BE9BCAB9E9CA0A900000A09000000009A00B000900B0A0A9A090CB00000009A0A90AC9A0A0A0A00A9A9000A0B0A0A0B00900000A090A900B9AFFFDEDBFFFFFFFF0FCBF000000000000000000000000000000000000000000000F0F0FA99EDBE9F9FC0BDCF0A9000A9A0000A0A000A0A009A00A0A0A00090000A0B0A00A90A0000A09A09009000A9000A9A090090000000A0A0A900A00A0B0E9AFFFFBE9FFFBFFFFDBC9AD09000000000000000000000000000000000000900BDA9DFBDEFFADF9EF0BD0FA0F0A9A00000A000009A00000A0000090090A0A00A000E90B00A00A0900A00A0A0A00900A00000A0A0A0A9A0B0009000A000000090990BFFDFBC9EFDFFFBEBDB0F0A9000909000000000000000000000000000000000FABCFB9F9FAFF9EDAFF0F000000A00A009A000000A09000A0A00A0A0900A009A09E000000900A0090B009000A0A009A0A9009009000000A00A0A90A9A09A0A0E0DBFFFCBF9FBFFFFDFADB09C0090000000000000000000000000000000090DBC9DFBDEFEFFDF0FFBFDA00A9A00090090A000A0A00900A000090A0000A0090A000E90A0B0A0A000A0A000A0A00009A00900A0A0A0A0A0A090A090000000000090B0BDBFFFCBCFFFFFFBFDADE9A900000000000000000000000009009000900B0BFEBCBFF9F9EBFF9EDE0CB0009A00A0A000000000A00A00B00A0900A0000A090A09A000000000A90000B00000B0A00000A0090090000900A090A0A0A000A090A90F0FE9FBFDFADBFFEDEBF9BC9CA90900000000000000000000000009000BC0FCBDFFF0FFEFFDADEFAFB0000A00A000000A0A9A000000000A000A009A0B00A0000E9A9A0A00A00A0B0000A0900000A9009A0A0A0A0B0A000A00000900B000A00A09A9BFEDEF9FFCBDFBDFCF0B09000000000000000000000000009A00A000B0BDEBCBFF9FF9EFFF090C0F0B000000A00A9000000B0A09A0009A00B00000000A0A0BC00009000900000BCA90A000B000A00000900900000B000A9A0A00000A00000A09CA9FBFFE9FFFFFFBF9FCBCBC90000000000000900000900900909090DADABDBFDFEF9FFDA09E0BA0000A9A00090000A00000000000A0000000A000A090000CB00A0A0B0A00A0A0000000A000B000A09A0A0A0A09A000B000000A0A9009A0B09A09FFDFFFFE9EFFEDEFA9F009A000900000000000090000DA0D000000AD0BDEF9EBF9EF9A0DA09C90A0A0009A00A0A000A00A00A0A0900A0A0000B000A09A0B0A09000000A90909A0A0A09A0000A90A00009009A000A000A0A00900A0A0000000B000BFFFFFF9F9FF9FDF0DB0DA9000009090900000009A09A0A9E9E9D0FCBDAFDF0F9E0FA000A0A009000A00A00000A90A0000090A0A0090A9A000B000000F09A0A9A0000A0A0009000000B0A000000A9A0A0000A90B090090A000000A00A0A000B0009F9FFEFFFFFFBEFADE99CB0B09C0000A9090F009F090D0009AEB9F0BDAFBFCBF90F09E9A90A0A9000000A09000000A9A000000A000000A0000A0A009E0000000B000009A0A0B0A000009A0A9A000000A9A00A00A0A0A00A90090000000A90000BCFFFFFFFFFFDF9FB9ACB0D0DA0B09090E0909A90F0F0BDAC99CFADFEDBCBF0CAC00A0000A0000A0B00900A000A090000A000000A0A0090A0009000E90A90A0A00A0B0AC0000090A0A0A0000000A9A00009A09A009000000A0A09A09A0000A900FBFFDFFFBEFFFFEDEDFBDA9A090D0ADA99F0BDEF0F0BCABDBEFADFA9BCBCBCB090090B0A900A00000A0A000A000A00A000B00B000900A000A00A0A9A90A00900090000B0B0A0A0090900B0A0B00009A0A00CA0B0A009A0000000000090A90000009FBEFFDFFFFFFFFBDBEDBD9E9AF99CBE9FCBB9F9FDBDDAFDBCBCFDE9E9E9E000A0A0000B000B0A000000009A000009A00A000000A000A09A0000E0A00B0A0A0A0A9000009000A0A0A000900A0A00090A90000000A00A9A00A00A00A0000A90A000FBDFFFFAFFFFFFEDBEBFADF9FEB9DB0BDFCF0F0BFAFDAFDBDBE0BCBDAC9E00900B0A000A00009A00A0B000000A00000000A0A00A0090000A00DBC9A0009000900A9A0A0A9A000090A0A000900A0A090A9ADA9A0000000900000A00A0A900900009EBFFFFFFDBFFFFFDFCFBEFE9FEBCFFAFBFFFFFCF9AFDAFEDBFCBCA09A00B0A00090A900A000009000000A0A90A00A0A009000900A0A00900A00AC0B0A0A0A0ADA09000009A9A0A9000B0A0A90000A00000000B000A0A09A0000090000A00000009FFFFFFEFFFFFBFFFBFDBDBDBDBF9FFDFF9E9FBEFDAFF90FCB0F0DAC0000090A0A000B00A0A0A00A0A000000009009000A00A0A00000A0ADB00B000009009000CA0A9A0A0090009A0000900A0B00B0A0B0A000A09000000A90A000A0090A0000009FDFFFFFFCFFCBDFFFFFEFEFFFFFFBEFFFFEDBDADBCFF0BC0000000B0A0A00000A0009000000000009000A0A0A000A00B0000009A0000ACB00A0A0A0A0A0B0B090AC90A0A90A000A00A00900A0009000D0A000A00A00900000A9000A0000000000AF9FFFFFFFFFFFFFFF9F9FFFFFFFDBF9FFFEFFEFF0E0CBC0000B0009000A9009A00A0A90A0B009A0A00000000A000000A009A000A00F90A090900000900000A009A09000A00B009A09A0A009A00A0A0A90A000900A00A0000000A00B00A00000900F9FFFFFFFFE9F0FFFFFFFADFBFFEFFFFDFFF0BF9F000A0A900A0A0A900A0000A000000000A00000B0090A000B0A0009A000A0900A0E900A0A9A90A0A0B0090A000A0A90A000A00A0009A000A090900A900A0A000000A000A090000909090000F0BC9BFFFFFFFFFFFFFFFDFFFFFFFFFDEBFC09E0E0CB009000A9000000A00A09000A0A00A00000A0000A0090A00900A000A090A0AF90CA000000A000900A0A009A9090A900A0000090A000A0900A0A000A900090A00090A00A000A0A0A009A0000CBFCBCBF9FFFFFFFFFFFFFFCFFDFEA9F0F009FDB00F0A0A900A9A0A00090A0A0090000000A09000A0000A000A0A900A900A0000CAB00B09A0000B00A0900B000E0A00A009A9A0A0000A90A0A000CB0000A00A000B00009000A090000A0000B0090AD0FCFFFFFDEDFFFFFBDBFFFE9FDE0FCA00BEDF000000A00000900B0A0000A0A00B00A900A000000A000A9000A000A0000A90BD0A000A09A9A00B09A0A00B09ADA909A0000900A900000009A9A0A9A0000000A00A00A000000A09000A9000B0AD0B0B0BCA0FBFFFEF0FEFCBC9E00BCB09CBCBFADA9A9000B0A0A0A0000B0090000000000000A09A090A000A9A00B000B0A00AC0A090A00A0000B00A0C90B0FADA9E0A000A00A000A0A0B0A0000000009A00A000000000A90A000A0B000A0A0090AC900CBDFBCB00909E90BCBC00BC9E0EB0BDADA0000A0A009000000B000A0A0A00A00A0A000000000090AD009A000A0009A0BB00A09A900E9ACBE9A9AF0F9FFFF09C0B09A009A00900009A0A9ADA0A00009000B0A9A000000B00000A009000A090ACB0000000000000000000F000A9090ACBDA9A0A00900A0A9A9A00A9E90000000000000B00A000A00A9A0BE0BE9000A000CF090A00A090A9BDBCB0DBFFFFFFFFA9A0AC09A000A0A0A00090E00090A00A00A000000B0A0A000B00090A00B00A0A90000000000000000000A00BCB0A0A09A0AC0009A0A090000000B00A9EB0B0A90A9009000000A009A00F00BDCBE9E90A909A0A090E90A09ADBFF0FFFFFFFFFFFCF09CB0A00A9000900A0A009A0A090A00A00A0A000009000A00A0A000A0090090A9A9A0000000A0900B09CB00009009A090B0B000000A09A0A0000B0F9F00000A00A0A00A000000A0DA0BFCBFF0F0A000AE9E900A90A09ADFFFCBDBFFFFFFFFFFA9EBA0DA900A9A0A900090A09C00A0900900900A000A0A0000000000000A00A0000000A0B090000A0000A0A0B0A0A0000A0000A00A000A0090A9ADFBF0E0A0000000000000A90A09ABDFCBFFDFFADA9A0B000A90A09CA9ABDFBF0FBFFFFFFFFBDF9FDFA00A000000A0B0A000A0B000A0A0A00A900B0000B009A00A9A0A00A00A9A00A090000A0B009ADA909000009A0B00B0A000900B0000A0009A9FCB0900A00A0000A90000090AC9EBFFFFFFDF0E000DBA90A90A0B00DAFBCBBEDFBFBFFFDFBFFFFFDAD0A90A90000009A9000A9000000A000A00A00000A00090000900009000A90A0A0A0000A0A000A0A0A0A00000A00000B0A0000A9000A0A9E9BCA000000000A0000A00A009AB9E9FDFFFFFF9F00BC00A9E9CB00B09F0F0C9BFDE9FBEBFDFFFF9E9A000A00A9A0B0A00A0B0E0B0A9009A000000B000000A00000A009A0A09000000909A0F0909A900090090A00009A0B00000A0000A09000DADA090A09A00A90000A00000A000E9FFFFFFFF0E0A0CA9E90B0B00B0BF0F0DBBE9A9B0F9F9FBFFFEDA00B090B00000000B09000000000A0000A9A000A00A000A0A000A00000A0A0B00A0A0900A0A00A00A00A000A9A0000000B009A0A00A0A9A0B0F0A000090000A000000A0900F9FFFFFFFBCE9090B9A9AF0F09AC9E9FFBEF0DADADF9EB0BDFFDBADA00A0ACA0A0B0B000A0A0B0A0A0000B00000000900900009000000A900000000000A0A00000009000000B0000A0A00A000A00009000009FFF9F000A00A00000A09A000A0B0ABFDFFFEDB0A0A0CF090A90E09A9BFFFF9FFBDBFBFBDADA0BBEDB009A909090900C0BCBC0900009009A000A00A0A0A000A0B000A90A00A0A9000A0B09090B0B00A0A0A0B0000A00090A90A0000A00A0A9A0A9FFE9E9000000A0000000090000BC9BFFFFDAC90000BA9AF9EB90A0FFFFFFFF0FADFDFFCB0BDFDBF0E9A00A0A0A0ADABCB0A9A0B0A0A0A00A09009000000A00000A00A0900000A0B0000A0A00000A90000000A0A90A000000090A0090000009BFFFFF0A00A00000A90A00A0A0A90BEDFEFDA0B0A9A09DAF90B0CA9FBFFFFFDA9F9FBFFDBE9FBFFFFCB000A9E9C9ADABDFBE9E0000900000900A0A00A00A00000A0000000A00A000000A000000A000000A90A09000090A00A00A09000A00A00BEDF9F00000900A90000000000090EBDBF9FA0F000000EB09EBCB09A9FFFFFFFF0A9FFFFFF9FFFFFFFBC0B090BABE9BDFFFDF090B0A0A0B0A0A0000009009009A00B00A9A0000900B0A90A900B00B0A0A00A00A0A0A0A00A09A00A00A00B00B009BBE00A9A000000A00A09A009A0A90EBCF0DA0A0A0009EB90B9E0A9ADFFFFF0DBCA9FFFFFFFFFFFFDFBC00AC9DBDFFFFFFFEFA0009000000090A90A00A00A0000000000009A00A00000000A0000009009000900909000900009000900000000A0C09A900000A00000000000A0090A900B0A0909090A0F90E9E09A9CBBFFFFBFA09BFFFFFFFFFFFFFFF0B0B0BBFFFFFFFFFFF9CB0A0A9A0B0A0A00A00A00A00A00A00A00A0A00A00A00A00A00A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0B09A9A00A00A000A00A00A00A00A0A9A0B0A9A0A0A0A00B0A9A09A00B9FFFFFF000B0FFFFFFFFFFFFFFF9E00BDFFFFFFFFFFFF0A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0000000000000000000000000000000000000000000000000000000000000000105000000000000B4AD05FE")}, + {empKey(2),ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E506963747572650001050000020000000700000050427275736800000000000000000020540000424D20540000000000007600000028000000C0000000DF0000000100040000000000A0530000CE0E0000D80E0000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00F00000000000000000000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE000000C00BFE000000A9000FFFFFFFFFFCFFFFFFFC009FFC00000000000000000000000000000000000F0000000000000000000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB09F00000000FCBFFE9E0BFFFFFFFFFF0FFFFFFC09FFFE0000000000000000000900000000000000000F000000000000000000000000000000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE000009009F09FFC0BFFFFFFFFFF0FFFFFFFFEFFFC00000000000000000000000000000000000000F000000000000000000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0B00000FF0000000BFFFFFFFFFFFDFFFFFF0F9FE0000000000000000000000000000000900000000F00000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE09FFFC0A9F0000FC00BFFFFFFFFFFFEBFFFFCFFEFC00000000000000000000900000090000000000000F0000000000000000000000000000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF009CBE09C00000000BFFFFFFFFFFFFFDFFFCBFC90000000000000000000000000000000000000000000F0000000000000000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0009FF0BFA9F0A9BFFFFFFFFFFFFFEFFFFFFC000000000000000009000000000000000000000000000F000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF009FCBC0DFFFFDFFFFFFFFFFFFFFFF9F0FC000000000000000000C0000000000000000000000000000F000000000000000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00AF0000000000BFFFFFFFFFFFFFFFEFE0000000000000000000D000000000000000000000000000000F00000000000000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9FC00BF0090BFFFFFFFFFFFFFFFFDFC00000000000000000C00000000000000000000000000000000F0000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD09EBFC9ADAFFFFFFFFFFFFFFFFFFFE000000000000000000000000000000000000000000000000000F000000000000000000000000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000009FFFFFFFFFFFFFFFFFFFC00000000000000009C0000000000000000000000000000000000F000000000000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0BFDBFFFFFFFFFFFFFFFFFFFFFFF00000000000000090000000000000000000000000000000000000F00000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000000000000000000000000F0000000000000000000000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F9FDB9FFFFFFFFFFFFFFFFFFFFFFFE00000000000000000000000000000000000000000000000000000F000000000000000000000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFDBFFFBFFF99FFFFFFFFFFFFFFFFFFFFFC00000009000000000000000000000000000000000000000000000F000000000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBBFFF9FDFBDFFF9BFFFFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000F00000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FBFFDFBDFFBFFFBFFFF9FFFFFFFFFFFFFFFFFF0000000000000000000000000090A0009CB0DA9A9AF9E9BE900A00F0000000000000000000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FFFFFFBFFBFBFDFBFFFFF9BFFFFFFFFFFFFFFFFE00000000000000009CA90A90F00C90BDA9ADA9FF9F9E9BD9B0C9C0F0000000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FFFFBDFFF9FD0F9F9DFBFFFFF9FFFFFFFFFFFFFFFC0000000000000DADA99E09CB0F9BCBDAD9F9BDB0BCB9ADAFCB9A9FF000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFDFBFFBF9FFBDA9F0AD0B0A09DB9BFFFBFFFFFFFFFFFFFFE0900000009CB0B9BDA99F9BDB0F9B0B9AB0F9ADF9BDADB99BCBDA9F00000000000000000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFDBFBFDBFCF0F0FDFCBDAD0D9F0BEFDFBFFFFFFFFFFFFFFFFC000000000009CBCF0BCB0F0BDB9E9F9E9DB9E9B0F0BDB0FE9B9ADBF0000000000000000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFDBFFFF9FF0F9FFBCB0BD0F9A9E00D090BDFBFFFFFFFFFFFFFFC0000000C0B9FB9B9BDB9F9BDA9E9B9E99A9E9ADF99F9ADB99F0F9EF0000000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFBDBDBDADEADBDA9C9F9F0FBDADA9DB0F0F9E9DBFFF9FFFFFFFFFE000000B09DA9CBCF09E9A9E9F9BD0F9ADBDB9F9A9E9ADADBE9F0F9F000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFBE9FBFBE9FDBDBCBCBFBC9E9F9E0DBDA0DB09E09E9CB9FFFFFFFFFFFC00009000A9FB99B9F09F9F9B0F0B99E9ADADA9E9F9F9B9AD9A9B0FF00000000000000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFFFDF9FDFBCDFDADA9E90B9C9CBFBCBFDBF0F9FBC9E99E9AD0FFFF9FFFFFFFE0000C0DBDB0DAD0F0BC9BCBCF9BCBC9BDB9BDB9F0B0F0F9BE9E9F9F00000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFFFFDBBBFFB9CFB0BCB0F09FDE9FA90DB9CBD0F9E9CBF99E90F9A90BFF9FFFFFFC00000B9A9ADB9BF0BDBBCB9B9BCBDBBDA9EDA9E9B9F9F0BC9B9F9AF0000000000000000000000000000000000000000000000000000000009FFFFFFFFFFFFFF9F9BDFCB0DCB0DBC9DBDBF0A9E99FF0FCB0FB9E9FBC9EB0F09EDBCF9BF9BFFFF000909CBDBDB0F09BCB0DBDADADB9A9CBDB9B9F9E9E9A9BDBFCB0F9F9F0F0D000000000000000000000000000000000000000000000000000FFFFFFFFFFFFD9BF9FFB9FCB0BCB0DBE9E90DBDB9E09CB09AD0DBDA9DBD9C99E99E9B0F0FFFFFFFC0F0A9BCBCBCB9F0B9DB0B0F9B9E9DB9B0F0DA9F9F9F9E9A9B9DB0FF9F99BBF990C000000000000000000000000000000000000000000000FFFFFFFFFFFB0BFF0FBCDADBC9E9BCB0D09AF09E0D9F9A9F0DA9E09D0A00F0BCBDA9F0F0F0FB9FFF00009CBDB9F9BCBDF0B0F9F90FCBCB0F0F9BF9F0BCB0F9F9F9EB0F9FB0BDAFDBCF9AD09A0CBCB0000000000000000000000000000000000BFFFFFFFFF0BD9FF9F9C9ADA09F09C9CB0BFD09E99FA9AD9E9F9F99F0F9F99BD9B0FD0FF9F9E9FFFF009ADA9B0F0BCB9B0B0F9E9AF9B9B9F0B90F90B9F9BDB0BCBCB9DB0F9F0BD9A9B0F90F0DB909FF000000000000000000000000000000009FFFFFFFBC9F9EF9F0F0BC9A9DA99E9A9CBD0BFD9E90DFDA9F9BCBCBCBDBCBEDBEDF9BF99F0F9E9BFFC00009C9F9BDB9E9F9D0B9F90F9E9E9F0DB0FBCF0BCBC9F9BFC9A9FBCBDABDBC9B9EB9BADBFA9AF0000000000000000000000000000000FFFFFFE90BDADBF9E90D0BC9E90E90F0F90BD0BA9FFB9A9F9E9F9F9F9F0DBDBCB9FBE9EF0BF9E9E9FF009090B0BCB0D9BCBA9BCB0F9BCB99A9F0BD09B9F9B9BA9E99BE9A9FF909DADB0DB9DAD9BC9DBD9C00000000000000000000000000000FFFFFD09BDFB9F0BC9E9A9C9A90F9CB090BDA9F9DF09FD9FDADBCBDBDE9BFBDFBDCBD9DB9FDFFBFD9FE000E000D0B9FBADBDDADB9F0BCBDAD9F0BCBFBCB9E9E9DB9FAD9BDBF9A9FAB90DA9F0B9ADB9BE9ABC000000000000000000000000000BFFFFCB9BCFB0FD0D0A909CB09CB9CB0FDBCBDF9EBDBF0BE9BDBDBF9E9BFFD0FBDFBF0FB0DBF0FFDBE90009090B0B0DB0D9A9A99EDAD9BDA9BAB9F9B9CB9E9F9F0BCBDBADA9F9DA9D9E9B9E9BC9FA9E99F9CBC00000000000000000000000009FFFE99DADB0DF0B0BD00F0B0FA9C0B9DB0DBDA9F90F9FFFDBDBFFDFF9FDBDBF9FBFDBFF9FBCBF9FAF9FBC000AC00C9ADB0BDBDADB9B0F0BDAD9CF0BCFBDF9B0B0F9BDAD9BDAFABDABD9ADB9E9BC9F99E9AF99B0000000000000000000000000FFF99EBDBDFB09E9C00F09FC90DBDB0F0DBADBD0BFF9F99FBFFDB9B9FFBDFBDBF9F9FDBDFDBDDFFDFFAFDBF090090009A9F0BCB9ADADB9F0BDAB9BDB9F0B0FDBDB0BCB9AD0BDF9DA9DABD0BDBD0B0F9E9BD9AD0F00000000000000000000000BFF09E99ADB0DB0009A99F009BDBCB0F9A9CDBCBFDBDF0FF9BDBFDFFFF9FFBDFFDFFFBFFFBFFBA9FBF9F9FCBC009A090E9C9BDB9CB9F9CB9F909DADADA9F9F9ADADBDBF0F0BCBBDA9FA9D0BCB9AB9F9A9BCBE9BF9B0000000000000000000009FE09F9DAD9ADA0D0F0D9E09FF0F0B9F9E9DBA9DBDB9EBFDBFDFFDBF9F9FFF9FBFBF9FFDBDFF9FDFBDFFDAFBFB0000000909ADA9CBBCB0BBCBCBEB9BDB9F9E9ADB9BC9A9F9BDB9DBDA9DBAF9BCBD0DA9F0DB99E90D0F00000000000000000000FF99BCB0F0E90DA090B0A9DB09F9FDE9E9DA9DEBDADFFDBFDBFBFBFFFFFF9FFFFFDFBDFBFF9FFFF9FFBDBFDBCFF009CB00AC99FB9C9BDBC9F9B990FCB0F0F9BDB0F09BDF0BC9ADFCB9E9D90F9BDA9BDA9F0DA99EB9BCB000000000000000000BF00DB0DB9B0F0090BC9D0BCBDA9A9B9E9A9FBF9F9FB9FBDBFDFDFDFF9FFFFFFFDFFFFFDFBFFBDBFBFDBEFBFFBD9F00000900AC9FABC9ADB0BC9E9B9BDB9F0F0F9F0FA9A9F9BF9AFB9E9BEBD0BDADBCB9F0B9F0F99E9BDF00000000000000000FC0BD0F90D0D00F0D09A9F9BDADBDF0F9F9F0D0F9FBDFFDF9FFBFBFBFFFF9FF9FFBFDFFBFDFFDFFFFFFF9FDBDFBEBF0090C0909B0D9B9F90F9BF0BCBCB0F9BDB9E9BD0F9F0F09E9F9E99E990BDADB0B9E99BDADB9E99F0B0000000000000000BF0BDA99ADA0B0909A9F0F09E9FBCB9F9CBC9FBF9FBDFBDABDBFDFFDFDF9FFBFFFFFFBF9FFFFBFBFDFFDBFFBFEBFFDF0000B00A9ADBA9E9E9B0F09DBDB9F9ADA9E9BDA9BDADB9E9FDB90F9BCBCBDB0DADBCBCB990F9EA9F9F000000000000000FC0DADBC9009C9E0F9CB9F9F9F09FDADBF9FBDBDFF9EBDBFDBDBFF9FBFFFFFDF9F9FDFFFFFBFDFFFBFFBFFFDBDF9FAFF0000909C9A9DBDB9E9F9F9A9BCF0FDB9F9F0BDBCB9F0F9B0BEDB0F0B090BDB9DB0BDBDAF9A99DA9EBC9000000000000BFC09D09AD0DA0099A0BCB0F0F9F9BDF9CBF9E9F09FF9FF9FBDBDBFF9FDF9F9FFFBFFBDFBDFFFFBDFFFFFFDBFFBDFBDBDF0900C0009E9A9E99A9E9F9E9B9B0BCF0F9F9A9BDA9B0F9FF9B0F9BD0BDB0F0BDBD09AD9E9F0B9F999AC00000000000FE09A9BC90B0090F0D9DBDBDBDBFCFBB9B9C9BDBFF9FDBDBDBDBDF9BDBFBFFFF9FDBDFFBFFBDF9FFBDBFBDFFFDFFBDFFBFE00B09A9C90DB9F0DBDB0B9F0F0F9B9BDA9E9F0BDBCB9E9FB0F9BC0BCB0F9BCBDA9BF9A9B0F9E9ADADB0000000000FF0DA9F09000C9E9ADB9EB0FBDAF9BBFCFFCBBDADBDBCB9F9FDBDA9FDBF9FFDB9FFBDBFBFDBFFBFFBFFFDFFBFBFBBDBFF9FF0000000B0FBADADB0B0F9E9F9F9BCBCB9F9F0BDBCB9E9BDF99ADBD09BDBCBBDBDAD0BD0F909BDB090BD000000000FFF09C9E09E9B09F09AF99F0F9F9FDDBDB9BDFDBDFBDBDFBDADF9BDB9F9F9FBDFBDFFDBFDBDFBDBDFDBFFBFFDFFDFFF0FFBDF0000D00909DB9A9F9F0F9F9A9ADBDB0F0F09E9BFCB9BCBCF9DA9A9CBDB9D0B0BDBDABD0FBC9ADBDBCB9000000009FFC9B0900000F09F9DBE9F9FF9FABDBBCFF9BE9BC9FFBDBD9B0FDADBDBDF9FF9FBDBFDFBFBDFFFBFBFBFFFBFBFBF9FF9FFBFF090A09E9A9E9D0BCB90B0F9F9F0F9F9B9FB9E9BDBCBBFB9A9DBDA90BCB0BDBDA9BD0B9909BDB0B09F0E00000000BCA09F0C90D090F0FBC9F9F0BFDDFBFC99BEDBFDBF9D9EBFBDB9F9BDBDB9FF9FDFBDBFBDFFFBDBDFDFDF9FFFFDF9FB9FF0FDF000900009F9F0BDB9E9F9BCBCBB9E9ADA90F9F9ADBD0F9E9FADA9CBDBDBCBD0B9E9BDBE9AF9E9C9FB09000000000099F00B0B0BE9BDBC9F0BF9F0BBBDF9BFFDBDB9F9FBBF9C9F9F99FD0BDFB9FFBFFFFFDF9FBDFBFFBFBFFFF9FBFFF9FF9FBFBC0000D09E9B0BDA9E9DA9E9B9BCF99F9F9F9ADADBE9FF9B099BD0B9E9A99A9F0DBC9A99F90F9B0A9CBC0000000000CB0B000DBC9CBC9BFBFC9F9FFDDF9FF9BBFBDFBF9FD9FBF9F9FFBBDB999F9F9F9F9BFBFDBBFDBDFFFFFFFFFFBFFFFBFFFFFF009A0A09CBD0B9F9A9BDB0DADB9AF0BCB0F9BF9BDA9FEDBCBCBD09BDBCBDA9F0B9E9E90F9AD9D9B99BC900000009AD9C0DA09BF99BFDBD9FBFF9BFBBF9FFDFDBF9F9FBFBDBDB9F9FD9F9FF9F9F9DBDFD9F9FDFBFFFBDBFF9FBFDFDBFFDBBFDBFF009009A9EBD0F0F9ADADB9F9E9DBDB9E9ADB0F0BDADB90B9F9A9E9BC9A9F90BDA99BDB0BDB0BADAF09AC000000099A0909DAD0F0F9BCBFBDADFF9FDBFBDBFBDBFFF9FDFBDBFF9F9BF9F09F0F9EBDB9FF9FFBFFFBFFFFDBFFFFBFBF9FBFDFBFFF000C900999A9BDB0DBDB0F0B9FA9ADB9F9BDB9F9E9FBCB9E9A9CB9E9B9DB0BDA9FBCB09CB0F0D9F99E900000009CBC0B0F0B9F9FDADF9FDBDBF9FDBFBDBDFDFFF99BDBBDBDF9FBFFD9F9F9F9F99F9FF9BFBDBDFFBFBFFFFBDBDFF9FFFDFBDFFF000B00B0FAD0F0BCBA9ADBDBCBDBDB0F0F0F0F0F9BF99E99BDA99F9E9EB0F09F909F9E9BDB99AB9CB9F90900000B909C99BC9A9FBDBBDBF9BF9FFBFDFFFBFB9F9FFFBDFBFFBF9F9FBFDBDBDB0FF9F9FBFDFFFFBDFFDFFBDFFFF9FBDBFBFFFF9FF00000C909BB9F9BD9FDA9ADB9BDA9F9B9BDB9F0BCBE99E9E99E0B9BD9F9BDA9FB0B99E9ADAD9CBBCB0F0A90009CBCA9AC0F9FD0F0DFBFDF9FF9BDFF9F9FFDBFF9F9FBFDBDBFFFF9FBBDBDBD99BDBC9DBF9FBFFFFBFBDFFBDBFBFFFFDFFFFBFFFB09C90AD0D0F0F0B0B9F9F9E9EBDB0F0F9BCB9F9BFDBC9B9BC9BDADA9A9E99F0DBDE99BDB9B0B9CB9F9F9CB909B9090D9B9F0BBDBF9F9FBFBDFFBFBFBFBDFF9BFFF9FDBFBFDBF9FFDFBDADBC9CB9BFBDBFDFFFFBFFFBFBFFBDFDFBFFBF9FFFFBC00A0090B0B9F9E9F9E9A9E9FDB0FDBD0BCBDA9E9FB09BE9E9BDADB9F9F99EB9BF0B9ADADA0DBCB9E9A90B0C0BDA09E9ADADBDFDBDBFFBD9FFFBDBDBDFDBF9FFDB9FDBF9FDBFDBF9F9DBDBDBFB9FDBDBDFBFFBFDF9FDFDFBDFBFBFDBDFFFFF9FF009090BC9BCB09B9E9BDBDB9A9F9A9ABDBDA9F9FF9DAD9F9DA9DBC9ADBCB9CBC9BDADBDBD9A9BD0BDBDBDB90DAD009F9F9F0B9FA9DBDFBFDBDFFFFFFFBFDBDBFDBFBDFFBFFDBDFBFFFBDBDBD9FDBDBDBFDBDFFFBFFBFBDFBFFFFFFFFBFFBFFFF0000E00B0F9F9F0F9F0BCB0F9F0F9F9DBCB9F0B0FFA9A9BA9DA9BBD9A9BCB9FBDA99A9A9AD9CB9F0BCB0BCB0990DA9CB0F9FDADFBFDBDFBFFBDBFFBFDFBFBF9BFF9FB9FDBDBFBDBDBD99F9DBDBDBD9FD9BFBFFFF9FDBFFBDBDBDBFBFDFBDFBFFF0090909C9A9E9B0F9F9BDB0F9F0F0F0BBCB9FDBF09F9FC9DA9F0DA9DBCB9E90BDBC9DBD9A9B9E99F99F9900BE9A9CBDB9E9BFBFDBFFFBDF9FBFF9FFBFFFDFFBD9FBDF9FBFF9FBDB9FFFFFFFF9F9FB9BFDFFFFFBFFBFDBDFFFFFFFFFFBFFFFFBF0000AC9A9F9B9CB9E9E9ADB9EB9FB9BDFBDA9ADF9F09A9BA9FBDB9EB0B9CB9BCBCB9BE9A9CBC9BF0BF0F0F9C90C9B9CBD9FD9D9BFDB9FFBFFDFFFFFFFBDBB9DBFBDFBBFDB9F0DF9FDBFFDBDFDBF9DFFDBFFF9FFF9BDBFFFBF9FF9FFBFFFFFFFF00090909CBCBCB9F9F9BDB0F99F09E9E90BDBDBFB0DBDBC9CBDADA9DBC9BDE9F9BDAD9BDA99BF09F09B0909BCB9ACB9FAF9BFBFDBFFF9FDBFBFBFBFBDFFFDFBDF9F9FD9F0D9B9FD9BDB9F9BF9FDFBDBFFFFFE99FFFDBFBFDFFFFFFDFFFBF9FFF900C0A0B9BDA9CB9E9ADADBDBE9BF9F9BFCBCB0BCBA9ADB9B0F9BDA9F9E9B9A9E9BDAF09DAD90F09F0DBD09F00D9BCB9DBCF9FBF9F9DFBBDFFFDFFDFBFBFFBDBBF9FBF09B9D9F9BD9FDBDFDBDBF9FFDFFFFF9009F9BD9FBBFBFBFBFFFFFFFBDFE09A90C0F0BDB9E9BDB9F0B0F9F0F0BCB9BDBDBDF9DBDBCADB9FE99F0B99E9DB9F0B99FB09B0F9BDA9B0B0FB09A0DBDBBDB9E9FF9FFB9FDFBF9FBDBFFFDFBDBDFDBD9DBD9CB99BDBF9BDB9FDB9DBD9BD990000909ADBF9DFDFDFFFFBF9FFFFFBF00000909BDA9E9BCBCF0F9F9ADB9F9BDE9A9ADFA9ADADB9B9F099E9BD0F9FB0F0BDBE909E9F9BCB9F09CB9C9E9DBF0FDA9FDBF9FBFDFDBFFFFFFFFF9FFBDF9F900000909B9CBD9D99C90909090900909009DBDCBD9F9FFBFFBFDFFFFFFBFFFFF009CB0B9E9BD9ADBDB9B9F0BDB0F0BCB9BDBDAF9C9B9AD0DADBDA9ADA9AF0DB9F9AD9BDA90BCB9E90DB09F0099A9DBF9FF9BFDBF9BFBFFBDFFBDBFFFF9FBDB900B090900909009000909C9AD09099CB09F9BDBBDBFBFF9F9FFBFBF9FFFDFBFFF000000CB9E9ADBDBA9E9E9FDA9F9BDB9FCBCB9FBBC9F9B9BDB0BD09BD99BF0F0BDBBCB9DBDB9CB9E90090BF9AD9F0FDB9EDBDBDFFDBDBDFFBFFFF9FFBFDFBDA9D9BDB0D90D0909B9DBD9BD99F9F9F9DBD9F9FDBF9FDFBFBF9FFFFFFFFFBFFFBFC09A9090F9F9A9AD9F9F9B0BDA9E9ADA9B9BCBFC9BA9F0F0BDF9A9F0B0F09B9F909CB9E90B0FB9E90000FD00DABCB9BDF9BDBFBFBFDBFBFFFFFFBFFBDFBF9F9FBFDBDF9BDB9DBD9F99BD9F9F9BDBDBBDFBDFBFF9FFBFFDFFFF9F9FBFBFFFF9FFB00C0A0F0B0F9F9BE9A9E9FF9BDBDB9F9E9E99B9BC9CB90BDA9AD90B0DBDBFCBCBFB9E90F9DB9C9000099B09B9DB9F0F9BDBF9DBDFFBFDFBDBF9FDBDBFF9F9FBFDBF9BFDB9FBDBDBDBDFBD9DBD9F9DDBF9FBDF9FF9FF9FBDBFFFFFDFFFDBFFFFC09A9099BDF9E9BC9F9F9BCB0F0F0BCBCB9BCBFD09BBDAD0B9F90FBD9A9BC9BDB090F9F90ABC9B9F9F90E90BC9ADF9FCBFF0FFBFF9F9FBFFFFFFFBFFF9FFFFFDBFDFFDBFDF9DBDBDBDBDFFFBDFBDBFBDBFFFBFF9FFFFFFFFFF9FBFBFFBFFFFBFF00000CBCB0B9F9FA9A9ADB9F9B9BDBDB0FCB0BEBC9CB9B9CB0F909AD9E9BF0BCB9F0B0F9D9BD9FDF9F99E9CBF9A9E9BD0FBD9DBFBFF9FDBFDBDFDB9FF9FBFDBDBF9F9F9FFF9FFDBDFFFFFFFFBDFF9FFFFFFF9FFBFDBFDBFDBFDFDFBFFFF9FFBF009CB00BDBCB0F9FDBD9ADF0F0F0B0BDB9BDBF99B0B9E9CB9F9E9F9BA9F09F9B9E9BDB09A9DBFFBFFFF090A90F9F9F9BF9FBFF9FDDBFFBFBFFBFBFFF9FFFDBFF9FFFBDBF9FF9F9FFBFB9F9DBDFBDFFFBFFFFFF9FDBFFFFFBFFBFBFDFFBFFFBDFF000009B0F9F9FA9ADABDB9F9F9BDBDADADA9F0F0D9E90B9E9A99A9C9DA9FBCBCB9E90DAD9FF9DFFDBF90F9DF9E9EBDF9F9DBDFBFBBF9FDFBDF9FDF9FFBDBFDBFFB9FF9F9F9F9BF9FBDFBDBFF9FFFBFDFDBFF9FFBFF9FBFFF9F9FFFBFFFBFFFFF09A09F0F9B0FBD9F9BD9AF9E9ADAF0F9B9F0DB09AB9F9CB9F9E9DAB0BDB0DB99CB9F9B9BF9FFBDBFFDF09E9AD9B9CB9F0FBFBF9FDFFFFBDFBFFFBFFBDFFBDBDBFDF9FF9FBF9FF9FDF9FDBF9FBFBFDBFFBF9FFF9F9FFFDF9FFFFFBFFDFFFDFBFFC09C009BC9FBCBE9ADAF9E9B9F9BDB0F0F0BBDBC9DA9A9E90B9A9D9F0BDB0FA9BCB0F0D9FF9FFBDBFFF909F9AD9FBDE9F9FD9FF9BF9FFFBDBDBFDBDFBDBDBFFD9BDBDBF9DBF9FFBFFFBFDFFFDFDBDFBFDBFF9FFFFF9FBFF9FF9FFFFFFBFFBFFFF0090BC0BB0DBDBD9BD9F9F0F0F0BDB9F9F9FB09B0F9C99BDBC9F0B0BDA9F9DADB9F99FBDBF9BDF9FBF0DB0BDBC9F9BDBF9BFF9FF9FF9BDFFFF9FBFBFBFFFDBBFD9E9F9EBDBFF9FF09FFBFFDBFBFFBDBFDF9FBDBDBFFFDBFBDFBDBFBFFFFFF9FF00A009BD0FB9F9AF0BA9E9BDB9F9F0F0BCBF0DB0DB0B9BCB09B0F09DA9DB0BDB9E90FF9DBDFFF9DBFD0B0FDBCBBF9F9F9FFDB9F9FF9FFFFBDBFBDFFDFDF9FBDDBF99DBD9F9F9FF9FBDBDB9BFD9F9FFDBF9FBDFFFFDBDBFFDBFFFF9FFFBFFFFFFC090F0DA9BDEB0F9F9DF9BCB0F0F0B9FDB9DB9CBB0D9E9BDBC9F9FA9F9ADBCB0F9DB9DBDB9F9FFBDF0DBD9ADB9D0BDBFE9BBFDBF9FFBDBDFBDBDBDBFBFBF9FFBDBBFBDBF9E9FBDF9DBF9FDFDBFBF9FBDFF9FFB9FBFFBFDBFFBDFFFFFFFF9FBFFF0000B0FBCBBDF9B0FAB0F9F9BDBBCF0BCFADA9C9BA99ADB0B0B09DA9F9A9BDB0B09FBFFDBFF9FFBFB00BDB0DABDF0F99FD9FBDBF9FFBFBFDBFFFBD9FDFDBD9FBDFDF0DBDB9D9BFFF9EFBFBFDBDFFDBFBDF9FFFFDBDF9FDBDFBFBFFBFFFFFF9FFA0900909BD9A9E9F9DBDA9BCB0D9B9F9B9FBD0B9C9F0D9AD9C9F9A9F09DBCB0DBDBD99BFF9FFF9FDC9F09F0BD9A9F9FF9FBDFBFDBF9FDF9FFDBDFFFBBFBFFFBDBDB9FB0F9FBFD9F9F99DBDFBFFFBFDBDBFFBDBDBFFBFBFFFBFDFFFFFFFFFBDBFC000DADBCBF9F9F9EBDB9F0F9FBEDA9E9EF90B9E9B09BAD9A9B9E9F09FB0B9DA9BDBFFFDFFBFFFDB0B09F0BDBE9F9BF9FBDBBDDBFDFFFBFF9FFB99BDF9FF9F9F9FBDBDF9FDBDBFFF09CB0F9FFFBD9FFFFDBDFFBDF9FDFDFBDFFFFFFFFFBFFD9FF009A090BF0F0B0FB9E9CB9B9AD9B9F9F99E9C99AD9AD9B0DADA90BDB0D9F0B9FDFDBDBF9FFF9FBF09FE9F9E99F9FCBFBDBFDBBDBF9F9F9FFBDFFFFDBF9FF9F9F9F9DB99BFDBFD99F9B9DBF999FFBDBDBFFFB9FBFFFBFBDFFBFBFFBFFFFFFBFFF000090F9DB9F9F9ADBFB0F0F9BE9E9E9BF9A9EB9AD9AD0B09BDBD0BDBA9ADA9BBFFF9F9FF9FFFDBD009ADB9E9DA9F9FCBDBFDBF9FFBFFF9FFBDF9BF9DBFBFFBF9FE9DBFD9BDBF009FDB9DBFC0909BDBDBDFFFDF9F9FFFBFDFFFFFFFFFBFFFFBFD009E90ABDA9E9F9F09F9BDBC9BDB9F0FFE9099E99ADBBDBC909A9F09DBD99DFDBDBF9FFBFF909F0BDBDB0D9BE9FBDB9FBDBF9FFBDFDBDFF9FFBFF9FF9FDFBDFF99B09DBC0009DB9B9FFBDFB99F9DBF9FF9BDBBFFFBDFFFBFFFFFFFFFFFBDBDFE09A00F9DBBDB9E9BFF0BCB0BF0BCB9F9B99F9E9DADA9C90B0BF9F0BCB09A9FBF9FF9DF9FC909FB0DAD0FD9BE9F9DBFDBDBF9FF9DBFBFF9FFF9FF9FBDB9FBDFB90F9FDB09B9D9AD9FFDBDBBFFF9FBFFFFB9FFFDFDBDBF9FFFFFFFFFFFFFFFFFFF0000909AFCB0F9BCB99F9F9F9FDB9E9BFCB0B9B09BDB9AD9F090BD9BDBC9FBDFF9FFA9F9909BFDA9B9B0BAD99BFBF9BFF9FF9FBFBDF9FFFBFF9FFDFBDF9FBDFDF9FFBDBDBDBD9BFD9FFFDFF9FFFFFFDBDFB9BFBFFFFFFFDBFFFFFFBFFFBDBFFF0090E9BF9BDF9E9BCBE9BCB0FA9ADB0F9F0D0F0F09BCB9A99F9F0BC9A9A99FF9DBFFDB9DBFDBFC9CBDE9C9BE9DA9FFD9BF9FF9FDFBFFF9FFDFFFFBDFBDBBDF9BDBDBDBFFDBCBD9FBFFBFBDFF9BFFFFF9BDDFDFFF9FDBDFFFBFFFFFFFFFFF9FFF90E900D0F0B0BDBDBDBCB9F99FBDADB9FB9A99B9ADA9F0DA90B09F9ADBD9FF9FBFF99DBFFF9FF9A9DA9DBFC9FBDF9BFFF9F9BFBF9FDBFFFFBFFF9FFBDBDDBBFDB9FBDFD9BF99BF9FFFDFFBFFFFFFFF99FBBFFBDBFFFFFBFFFFFFFFFFFFFF9BFFC0909A9B9F9F9ADABF9BDA9AF9FBDBCBF90DBCBC9B9F09B9CBD9F9AD9B0BFBDBDBD9FBDFF9FFD09DA9BE999BF9F9FFDB9FBFDFFDFFBDBDBDFFF9FF9FF9FBFDF09FD9FB9FFD9FF9FFFFF9FFFFFFFFFF9FFFDF9FFFDFBF9FFFFFFFFFFFBFFBDFFBF00AC09EDADA9F9BD09E9F9F9E9CB0BDBCB90B09F0F0F0DA90B0BC9B0D9FDDBFFD9BDFBFFFDBF0B0DBD9BCBDBCBF9FBDFBDFBDBF9F9FFFFBDFFFFFFDBFF99F9F99FF99F90BF9FF9FF9FFDBFFFFBFF9FF9FFBFF9FBFFFFFFFFFFFFFFFFFFF9FDFF0909A9B9B9BDAF0BFF9BCB0F9FB9FDBFBCBDBDA99B99B09F9C99BC9BA9BFB9FFBF9BDF9FFBFF9CBBCBE9F9ADBDBF9FBDFBDFBDBFDF9F9FFF9FBDFFFF9FFBDBDB09BFDBDBDBF9FF9FBDBDFFFFFFFFB9FF9FFFFFFFFDBFFFFFFFFFFFFFFFF9FFFFA0009CADADA9DBD90BCB9F9BF0F0B0FF99090B9CBCBC0F90B9BE99AD9FFD9FFFDFFDBFFFB9FD009C999F09F9FBDFBDBF9FBFDFF9FBFFFBDBFFDFBFBDFF9FFFBD9F99BCBDBD9F99FFDBFFBFFBDBF9FFFBFFF9FFDBFFFFDFBFFFFFFFFFBFFBFFBFD09E099BDBDBA9AFFB9F0BCB0F9FDB9BDA9EB9CB09B9B90F0F099E99BDB9BDFFB9F9FDBFDFFF0BDB0FF0BF0F99FBDBFDBDFDBBDBFFDB9FFFFFFBFDFFBF9FDBD9F09FDB9F9DBF9FFDBFDBFFFFFFFF9FBFDFBFFBFFFDBFFFFFFFFFFFBFFFBDF9FFF0009A9E9A9F9F9F90F0BDB9F9E9A9FF9C99C9A99F0D0CB990BCB90F9FF9FBFFDFBDBFF9F99F090FB09F9DB9FFBDBDFBFBFBDFBD99FFFDBDF9FDBFBDFDFBFFFBDBDA9C9F9A9DBDBFDBFFFFFFFBDBFFDFBDFFFDBFFBFFFFFFFFFFFFFFFFDBFF9FF0090CB9F9E9E9F0BF9F9ADBE9BBDE9EB9A0BD9E90B9B9CB0F99ADB09BDF9FFFBDFBDFFFBFF9F0D9F9F9EBDB9F9FFBD9FDBFBDFFFBDBDFFFBFBFDFFFBF9F9FBDBDBDBB90F9F9FBDBFDBFFFFFFFFFFFBFFBF9FFFFDFFFBFFFFFFFFFFFFFBFFDBFF9A0A90F0B9F9A9FDADADB0DBCDA9BFF9C9D0B090F9E90B9D0B0D099FFF09FBFFF9F9BFDF09F09A90F9F99BCB9FBDFBF9BFDFFBDBDBFBF9BDFFDBF9FDFFBFFDFFFDBDDF99F9F9DBF9FFFFFFBFFFFF9FFDFFFF9FFBFFFFFFFFFFFFFFFFFFFDF9FF0D090F9BD0BFDF9ADB9BCBBDBBDBCBF90B0B9DADB090BD0B09DA90F9B99FBDFFFFF9FFF99F00DBDBDA90FDBDFF9F9FBDFDBDBDFFFFDBDBDFBDBFBDBBF9FD9BFBF9F9AB9FDF9FBFDFBDFFFFFFFFFFFBFBFFFFFFBFFBFFFFFFFBFFFFBFFBFFBDBFF0009B0F0FF9B0BF9ADE9BDA9CBCBDBE9CBCB0909DBC0BD0F9A9A99FDFF9FFFFBDBD9FFCB9F9A90F9BF9B9F9B9FBF9FDBFBFFFBFF9BDFBFBDBF9FFDF9F9BF9FDBF9F9DF9B9BDF9F9FBFFFFFFFBDFBDFDBFDBFFDBDFFFFFFFFFFBFFFFFF0BDBFFC09ACBDB9B9E9FD0F9B9ADBDFB9F9BD9A999ADA9A99B09A900D9D09BF9F9BFBDFFDB0BFD9F00DBDBC9FD0B9FDBDBDBFBDBDBDBDBDFFBDFDF9DFF9BFF9FFDBDBF9FBDB0BD9F9BDB9F9FBFFFF9FFF9FFBFDFBFDBFFBFDBFFBFFFFDFFFBFFC99DBFFE0909BCBCBF9ABF9E9F9A9A9F0BCBBF90E9D09C90E9DADA9B9A00BFF9FD99FFBFBFD99BF00009E9B99AD9EBBDFF9F9FDBFFBDFFBFDFBFBFFBF9FDBDBF9B9F9DBFDBDFDBCBDFBFDBDFFFFFBFF9BFF9FFBBDBFF9FFFFFFBFFFFFFBFFFFFF09BFFD090ADA9F9B0F9F0F9F0F9FF9ADB9FF0CB99A9B0BF9B0999C9C9B99FF9F9BFFBDFFFFF0F900C9BDBDF0F9BD9CFB9FBDBBFDBDBF9F9FBDBDB9F9F9FBFF9FDF9FB9F9BDB99F9F99DBFFBFFFFDF9FFF9FF9FDFDA9FDBF9FFDFFF9FFFFFFFF009DFF0F00DB9F9ADF9F9F9A9B0F90BDB0F9DB90BE9C0D9090BCB0B0BC009BF9FD0BDFF9FFBDF9F09009ADA9B9F9FBB9FD9FBDFDBFFFDFBFF9F9FDF9F9F9F9FFBFBF9DBDBC9BCB9F9FBFF99FFFBFB9FBD9F9ADA9B9F9FBDBFBFBFFFFBFFFBFBFF09BFF00A9A9F0BDB0F0F0F9E9F0FBDADBCBBCBD09B9A0F9AD09C9C99B909FFDBF99BBDF9FF9BF00000999FDDBDA9DF9BF9FDBBF9F9BBD9BDADB0B0F9EBF9F9BDBDBF9F0FBCBDF9DBDF9FFFBFFFFDBDFBFCF9BDFDB9F9FBDBDFFDBFFFFFFFFFF0099FF0D009E9FCBF9BBF9BCB09B9E9F0B9FDB00BC9C990BDA9B9A9AD0D09BF9FDBDFDFBFF0BFD00E900F99A9F09FB9FDBFFBDFBFBFDFBFDBDBDBDF9BD99DBFDBDBDFF9F99F9B9FBDB9FBDFFFFF0BFF9E9BDBDB9BDBF9F9FFBFFBFFFBFFFFFFBF090FF0009E9B9BDAF0DBFCBDBCBDFB9FDA9BC9BD9B0B0BD090DAD0909A90FFDBFD9BFBFDF9DF00900E9BCFBDB9F9FDBBFDB9F9D9FDBB9DA9F9F9F9BDB9EB099BFDBDB9F9F9BFDADBDFF9FBFFFD9FC9BD9F9BDBFFFFFFFFFBFDBFF9FFFFBFFFFC9FFBF0A9E9BCBCBD9FBF0B9B0BDAB0F0B9FFBC00B0D0D9A9F0B909CBD009BFF9F9E9FFFFDBFB0000900DB9DBCF9E9BDBDBDF9FBF9BC9FBDB9FBDBFFBDF9DBDAD9AFBFDBDBC9DBDBDBDBFF9BFFADBB9F9F9BDBDF9BDBFFFFFBFFFFFBFFFFFFFF0B9FFDC909BDBDB9FA9CBDBDAD0BDBDBDADBDB99ADB0B0F9090DA9A90B90F99FF9F99FFFB9FDAD000E09ADABDB9F9F9F9FFBF9DBDF9BF0DBDBDBFDBDFBDBF9BDBD99DBBDADB9BDBDBDBDBDFFFF90DFBDBD9FFDBFFD909A9FFFF9909F9FFFFFFFAD0BFA9E9ADADADA9FFB9E9ADB0DADADBDADF9E0990D0900F0B99C90BC090BFF99FDBBDBDF0BD00009009B9DA9F9FBDBFDBDFBF9BAF0DB9BCBDBDBDB0DBD99DBDBBF9FDBDBDE9BDBFBDBFFBFF09FBFDADBFFFFFD90BDBDFBD0000BDBFBFFFFFF00BFADA90D9BF9BDF0BDE9BDA9F9B9F9AF9BF009BCB0B0DB90D0F0BC99B0B9DF9FF99CBDF9BDA0000009DEDB9F9AD9FBDBBFBF9BD999B9E99BC9FF9FDBF9FFF9DBDDBDBDBDB99DBF9DBFBFDFF9BF9909909090000FFFF900000F9DBFFFFFBFF09E90C90DABAF9ADA9BDA9BCB9F0BCF9AD9BCDB9C09D00B09E90B0909AD09C9BFF99E9BDBF00900000000B9ADBDBDBFADBDFDBDBDBCF0D09F9FF009F9FF9F090BE9EBFBFBFBDF0BD9FBDFDBFFE9DBC09FFF00000909FFF0009090FBFDBF9FFFFF09E900A99D9DBDB9FCBDBCBCB09FB0BDBADBBCB9A0B9C9F09A9DBC909A90B099FF9BDBFDBD00090009000F9BDADBD9F9FBBFF9EDB9900000999BF0000009FFD9999909FDBDBDF9BF9FBFBDBFDA99BB09FF009FE000FC009FFF099DBBDFFBFFFD0000A9CA0BE9E9E9BDA9F9B9EB09F9E9DFADF9C09D09A90BC9CB0B0E990F9F00F9FDBDA900000000000090D0BDBDEB9F9FDB9F9BC9E9900000BFC0000090FFFFFD099F9BD0B9BDF9F9F9FFFFFD0FD9D09000090000000FBFF090BBDFBFDFFFF0ADBC9009C99BF9BDABDB0F0F9DBC9ADBA99BF0B9B0BC90F990B09D099E9090099FB9FFD0000000000009CBB9F9BDB9FDE9BFDBFF9B90A9C9C0000009FC00BFF000BDBDFDBBDFDBBFFFFFFFFFB09BFBFBDBD0C009009F9FF000BDBDFADFBFBFE09B000A90A9AF0BCB9DB0F0F90BCBBF9AD9EFBD000D0BCB90E90DB0B0B9E9AD9A009F990000000000000009CB9F0BDFBB9FF9FB99FCBD909A9DE900009000000AD99F9BF09DB9BDFF9FFBF9FFDF090D9DADBF9FFFFF9BFA90B9FBFF9F9BFFFF9000E0900D09D9BDBCBE9F9B9AF0B0D0BDAF9BFBD9A99090F99A9AD9C9C9909A9C90000009000900900909009CBDBDBD0F090BDFE9BDBEDB0DB090FFDBC009BC99BF99F09FADBDFBDFFF9FFFDBFF9E9A99DBD9FBFFBD00D09F9FC9FBFFF9FFFFF09F9E090A00A9DA9BDBCBDE99BC9BBDA99AD9BD0AD9E9CB09E9C9B0B0B0F90C900000000000B0C000000A0DBBD0F9FBFD9BDF909F9F99BBDB09CB99FFF9FFDBFFDBCB09F9F9FFBDBF9FFFFFBFF9F9BD0B090B990909BF9BF9FBFF0F9CBFFBFFBC00F0DAC90B0F0F9E9BF9A9F0DB0D0BDAD9BEF0B99A90B09F9A9B0D09C900B9B0000000000000B00009C09BC90F9BF9DBADA90BF9E90FDFCBDFBDDA909AD09AD090909F9F9BDBDBFFFFF9FFFF9FB0D0FCBDBC9EDBDBFDBFDAD9E9F9A9BFFFFFFF09FB009000D0B9CB9F0F9F0FB0DABDADBAF9F90F0DA90F090D0D0B90BCB99C0B000000000D09C0900000F0BDB9FCBFA9DBDAD9009F9F09BDFBDFBDBFD9BBD99F9F9FF099E9F9FFFDBFFFFF9FFDDFBB9909F9B9BFFDBFDBFDBFBDBFFF0DBFFFF00BC0F009E90BDABDADB9E9B0DBAD0B9AD9CBBF90B99E99FA9A9B9CB0990E9B00D0C09000000A0000909009CBDA9BD9FF9DB9A0D9E909F099ADB9FBDBFFDFBFF90B999FBDBDBF9D9FFFFF9FFBFBBC9CB9F000C9009BDBF9BD0900090009BFFFFF90F09FE000A00D0BDBCB9E9F09DBDAD9AB9FF9CBC9E90A99C909E90DA0B90D90A0000000C09009C00A009F9B9FDBFBDBFAD0D9A090F00BF0D9E99FFDB9FBD090F9CB09DA9BC9FBBF9F9FFF9FDFD9090D0AD0000000000C00900D00009FFFBFF00BF0F00909C9A9ADA9BDE9B0BCB0BDAF9DA9F0B09B90F9DA9ADA90B09D90F0AC90000000900C000009C9A9BCBDB9F9FBC9FB00090F00BD09BF0F090B0D0000F090B0C9B0DF9BFFDFFFFFFFFFFBFBE0000900900000090000ACB0A909BFBDFFFE9C09E90E00A0D0DB9FDA9BCBDBCBDA909ADFBD0BD0E909AD9009F9C9A9E99090090090000000900900B0DADBDAFCBDBD9F9DF0000909009E0990F00D9000000000C900C990909BFBDFFBFFFDBDBD990000900F0D0000909090909C9BFFFFFFF90BF09E9000900A90FA9F0F90B09B0DBCBDABFBD0B99B0F9ADB9090B0D09E9F00000000000900A00000C09F9EF9BDBFDA9FB0BD000000D009C009090A00D09F090090090A09FF99FFFBFFBDFFFFFFF099E000900000C0A000F0C09A9FFFBFFFFFAD0BF0F0BC0090BC9DA9F0FBCBCBDA9B0BD9F90BC9E0D9AD00DBCB09A9A900900000000000009C0B090B0BDB9FDBDB9F9BDFDBF90009000009E0C009C9A0C0000000000D9FF9FFF9FFDFDFBFFBDBFF9090C000009099C9FB09BBC9FFDFFFFFFD09ADF0F0009AC009BAD0B909B090BF0DADAFF0BC9A999AD9B9B090D099C90F000F0000000000000C0A0DBCB9F0B9E9F9FDBF9AD9F000009A9099A90000090900000009909BFFFDBFFFFFFBFFFDFFF9FFDB900000A09EBF009DADBFFBFF9FFFFA9E9FFF09C000900F0D9F9E9E9CBBD09A99F9BD09B0DAC9A9C0CBDA9ADA9A90000009000000900090090099F0DBDBDBFF9BD0BDBE9BD0009FEBCBCBC909000C9000909BDBFDF9FFFFFFBFFFFFFFBDBDBFFFFFBDBD9DF990BDBE99FFFFFFFBFFFD0B0BF00F0B000090B9A9A909A9C90BC9CB0BFBD0DB99BCB9B9090909090D0BC0000C0000000C0000D09ACBDBF9FDBD9FF0FBDBD9EDBF90909F9090BCBC0F000099DBDFFFBFFFFFFDBFFFDFFFFFFFFFFFFFF9FF9FFBFFFDF9F9BFFFFFBFFFFFFA9C90FF0F00D000000E9ADA9C9A90F0B0B0F0DA0B09E0990F0DBCBCBCBCB0900900000000000090000A0D00A9FE9BFFF99F9FADBF99FFF0009000090BF9B99F9FFFBFFBDFFFFFFF9FFFFFFBFFBDFFBFFF9F9FFFFFBFFFBFFFFFFFFFFFFFFFFFBDA0AFBCBCB0A00000909C90DA9C9E09C90D09BC909E99F0F09B0990909090E900000000000000000090C9B99DB99E9B90F9F9FDB09FBCBFD09E9FDBDF9BDFFBDBFFFFFFFFFFBFFFFFFDFFFFFFFFFFFF9FFFFFFDBFDFDFFFBFFFFFFFBFFFBFFFF09BD9CB0F0C00000000000000000090000000F000090000900000000000090000000090090000009000900F0BDFF9FDF9BF0F9BFDAD9F9BFF99DB9FFBDFFFFFFFBDFFFFFBDFFFF9FFFFBFFFDFFFFFBFFFFFFFBFDBFFBFFFFFFFFFFFFFFFFFFFDE00FAFD09A9000000000000000000000000000F000000000000000000000000F00000000000009000000090D0B09FB9AD9DBDF9BDBBF9FF9FFBFFF99FBDFBDF9FFFBFF9FFFFFFFFFFBFFFFFFBFFFFDFF9FDBFDBFFFFFFFFFFFFFFFFFFBFFFFFB90090BE9EDE00000000000000000000000000F0000000000000000000000009000C00000000D00000000BC09BD9FBDFD9A9FBDEDBDF9FF9F9FDBDBFFFFFBFFBFF9FFFFFFFFFFFFFF9FFFFFFFFFFDBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9CB009F0B9A9E9C000000000000000000000009E000000000000000000000000009A000000900000C000900B009AF0DB0BDFBDB9BFF9FF9FBFFBFBDFF99BDFFFDBFFDBFFFFFBFDBFFFFF9FFF9FFFBFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFBCB000BCBDEDF00000000000000000000000000E9A09000000000000000000000C0000900000000A900090C900D099DB9FDBBCBFFDBDB9BDBDF9FDBF9FFFFBDBFFFFFFFBFFFDFFFFF9FFFFFBFFFBFDFFFBDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC009F9F0B0BCB000000000000000000000000BC00A0000000000000000000090009000000000000000000AD0BD0BF0F9BC9F999F9FFDBFFBDFBFDFFBDBDFFFDBFDBFFFFDBFBFFBFFBFFFFDFFDFFFBFFFFBFFBFFFFFFFFFFFFFFFFFFFFFFFFFDA90000BE9F09F0C00000000000000000000000CB00000000000000000000000000000000900009C009000909000BC9BDADBF9FFEDFBDBFDBDBFDBFBFDFFFBFFBFFBFDBFFFFFDFFFF9FFBF9FBFBFFBFF9FFFF9FFF9FFBFFFFFFFFFFFFFFFFFFFBF0090BC9BC0BC0B00000000000000000000000BC0000000000B00000000000000C000C00000000000000000FAC909BC9DBD9FDB9BBDBDFBFFFBFFDFDBFBFFDFFFDFFFFF9FFFFFBDBFFDFFFFFFFFFFDBFFFBDBFFFFFDFFFFFFFFFFFFFFFFFFFBD00900FDAC0BF0BD00000000000000000000000CB009A0C00000000000000009E0900090000000090000000009F09C9BA9BFB0BDFFDBFB9F9BDFDBFBFFFFDFBDFBFFFFFFFBFFBDFFFFFFFFFFFFDBDBFFFD9FFFFFBFFFDBFFFFFFFFFFFFFFFFFDAF0E09AF9F00F0DAE0000000000000000000000BC00000900000C00E00A000009000900000000000000D000900B90BC9F9C9FDBF9BF9DFFDFBFBFDFDBF9FBFFBFFF9FBDBFFDFFBFFDBFFBFFFDBFFFFFF9BF99DBFDFBFBFFFFFFFFFFFFFFFFFFF909009F00ADBF0BD00000000000000000000000E9A0000A00000B009009000000000000000000000090A0090009E90B90FBDA9F9FD9FBF9BFDFDBFBFF9FFFDBDF9FFFFFFDFFBDFFFFFFFFDBFBFFBFF9FDE99FBF9FBFFDF9FFFFFFFFFFFFFFFF9E9A090DB0D0F0BFE00000000000000000000000BC0000000000000000000000000000C000000000000090000D009BC90F909FDB9FBFBFDBFDBFBFFDBFFFF9FFBFFFBDBDFBFBFFFFBFFFFBFFFFDFFF9F0099E99FF9FFDBBFFFBFFFFFFFFFFFFBF0BC0F0A00BE9BCD000000000000000000000000DA00A090000000ACA0000009000009000000000000000C0900BC90BC90BF90BDBF9FDBFDFBF9FDBFFDBDBFBFFDBDFFFBFFFDFFBFFFFFDFFFFFBFBD090B9F99F9FFF9BFD999C9B99FFFFFFFFFCF0900090D09C0BE900000000000000000000000E9C000000000009000CA0000000000000000000000000900A09ADC9BBD9CB99ED9F9FDBDBFDFBFF9FFFFFFF9FFFBDBFF9FBFBFDFDBF9BFF9F9FFC99099F09BFDBF9FF9BFE9000FBCBFFFFFFF9F0090FC0BF0BCF00F0000000000000000000000F0A00000A00000000B0000900000000000000000090000009C09A909C0B9F0F9BF9BF9FBFDBDFDBFFBDBDBFFFBFFFFDFFFDFFDBFFFDFFFBFFFF0B00FBF09FDFBD9F09FF090B9D9099FFFFFF9A0BC0A90BC009B0BC00000000000000000000000AC9000000000000E0000000000000000000000000000000900009CBC9B9CBDB9FDBFDFBFDBFBFBFDBDBFFFDFBDFF9FFBFFBFFFFFFBFFDFF0B00909B9D0BBFFBD9B09F909090CA00FFFFFFFF0F9C909CB0F09AC9D0C0000000000000000000000DA00C00000009A090E0000000000000000000000000909000BD0A90B9CB9CBD9FBC9BDFDBDBDBDFBFFDF9FBFFFBFFBFF9FFFDBFFFDFFB9F9D9900C9F09DF9D0B09FBDAD0BC09009BFFFFFFF900A09A0C90BC9B0A000000000000000000C00000F9E0A00000900000009E00000000000000000000000000000009C090FBCB9BCBCBFF9BFBFFDBFBDFDBBFFDF9FFDFFDFFFFFFFFFFB9B9F900B0C99BF09F09FB9FDA9F000900B0009FBFFFFF0F09C0BC9AC9BEF0F9F000000000000000C0000000F0090000000A000AC0000000000000000000000000000009C09F090F09F9E9B9F9DBDF9F9FBDFFFB9FDFBFBFF9FFBFFFFBFBFFF00C09009F009AF909B9FF09C90900909009009BF9FFFFFFF000B000099AC90DA0000000000000000000000C000E00E000000000009A0000000000000000000000000000009000BF090F0BDBCF9FBFF9F9FBDBBD9FFFFBF9FFDFBFFFBFFFDFFBD099BD0BF09DBD9C9F9E9090B09BC90000BC909E9FBFFFF90909C000D0AC9F0B0D0000000000000C0000000000F090000000000000000000000000000000000000000C0AD00A0909CB909E9DB9F9F99FBFFDBDFFBDFBDFDFBFBFF9FFFDBFBFF099BC9A9C9D0B0B09BC9900090BC0000090000E9BFFFFBF9E00000090A90B00BC9A000000000000A00000000000F0A00A00000000000090000000000000000000000009090F090C0090FD09A9F9F9FFF9F9BFDB9FFBDFBFBFFDFFFFFF9FFFFD0F00FF9CB9A009FD0B00A09DBC00900009A90A90B9FF99FFF9000000E900C9F09A9C000000000000000C00C00000AD00900000000000A00A00000000000000000000000000000009A90DA9BF9F0FBF99BDBFDBFFFF9FBFDF9FFBFFFFF9FF99F0B09F90099C099F000D09D90A09090009E0C009DADF900BFF000DA00900000B00E0DA0C000000C00C000000000000DA00C000000000000000000090000000000000000900000090F09CB09DE90F9BDE9FFFBDBF9F9FBDFDBFFF9FF9FFDBF99F09C9E909BC0009F99DB0B00A09000000B00909C0B9FA09FF0000900900009DBC0B99B0000000000000000000000000AD00A0000000000000000000000000000000000000009C000000A09B0B9DF9FDF9F9F9FFF9FFBDFFBFFF9FFFFFFB0BCBF00900900C0B00BCA0000D00D0D09DAD0D0D00009B0F0909F0909A00000000A00900AC0F000000000000000000000000F0A0000000000000000000000000000000000000000000900090D00D9CBA9E9BBDBFBF9BFDB9FBDBDB9FFBDBFFFDBD99F0909BD0909000999CB0900B0000E90B0B009A9E09F0F09F90A00D0000000909000D9B00000000000000000000000000F0D00000000000000000000000000000000000000900000BC0000B00B09DB9F9FBD9DBFDBFFFFDBFFFFFFDFFFF0090F00000F0000AC009AC0900009C090B00F0C9CBAD099BE900F00C909A009000000E00F0ACBC0000000000000000000000000A0000000000000000000000000000000000000000000000000909C909FB0F9ED9FFBFDBDBDBDBFBDBDBDBF9F9090F9090F9000090909C90900D09A09E90C90090B0D9ADAD90BD0009000000000909090F00D900000000000000000000000000F0000000000000000000000000000000000000000000900900C000ADBC0DF9F9BFBDBDBFBFFBFFDFF9FFF9FFF009F90C090000900F0009A00000AC00000909ADA9C9AC99B0E90A09000A90D0000000E0000B0AD0000000000000000000000000AD0000000000000000000000000000000000000000000000000009009B0BDADF0DBFDAD9F9DF9BF9FF9BFFBFC0909CB00AD000009000B00D00900909000000900090900E9B9C9000909C00000000A900090D90A0000000000000A00000000000DA000000000000000000000000000000000000000000C0000900900F0DB0BDB9FBDAFDBF9FB9FF9FF9FDB9FD0B0DA900090000090009C0900000D0000000000090000BF90C0A009A0A0900A000090000B0A0AD0000000000000000000900E000AD0A0A0000000000000000000000000000000000090090000009E0090B0DBDBDF9BD9BFDBF9FF9FBBDBFFFD090909C009000909C00900900900000000909000000AD9C9C009090C9090A00909000C0090C90D000000000A000A000090E000000DA0909CA900000000009A000000000000000000000000009000009000DB9E9FA9FDBFF9F9E9F9F9FDBFBDBF0BC9A09000000000000090000000909090000000F09000B00B00009A0C0D09C00000000000000B000000000090090000000900000E9E0A090000000000CA0C9A0C000000000000000000000000000000909E99F9F9BDF9BF9BDBFFBDBFC9F9FC9900D000090000009000090000900000000000900009000F90E90E0090A00000000900000090BC00000000000A00A000000CA0000F0090CA00C000E9A0909A000B0000000000000000000000000000000009F00F9FCB9FCBDFF9F9FBFDFBDBF00A0D000900000090000000000900000C00000A0000000BD0009009090E90090000000090F00BC00000000000000000000090000000F000B090A0B000C0A0A00F0000000000000000000000000000000000009F9BCB99E9BDAF9BDBDFDB99FF099C9A90000000090000009000000000900090D0090090BD0AD000F00E90009A000900000000BC0B0000000000000000009E0000000F000000E090C0B0A9C00D00000000000000000000000000900000000090009F9F0F9FDBD9FDBFFAFBCBD900A000000000000000000000000000900000000000000F00000BD0090909000009000000009009000000000000A00000000000000000F000090A0A90C90A09A0A0000B000000000000000000000000000000000900F9F9F99EB9ADBF99D9F9BCBD09090900000000000000000000000000090009000000F090900B00F00009C000000009A00F00F0000000000000000000090000000F000A0A09C00A0A090A0090F000000000000000000000000000000000000009DA9E9AF9DFB9F9FFBF9FDB000AC000009000000000000000000000000000000090090000E9BC9F00000A09000900D009009F000000000000000000000000000000A00909C0A0B0909CA00D0E0000000000000000000009000000000000900009ADBDBDBDBBD0F9F9FDADBC0909000000000000000000000000009000000000000090000090C0B000AD009AC0000A000009E000000000000000000000000000000F0DAC00A09000ACA00D0A0000009000000000000000000000000900000000009BDA9F0FC9E99FADBFDBCB0000090000000000000000000000000000000000000BC0000BC090C00900F0009A0009009CB0B0000000A0000000000000000000000DA009A00A0A0D009A0000000000000000000000000C00900000000000009000C09DADB9F99E9FDBF9F9BC0090000000000000000000000000000000000000000000909000A9009E9000000C090000A00D0000000000000000000000000000000AD00A00D090DA0BC0000000000000000000000000009000000000090000000090A9DBDAFCB090FBDBCBC90000900000000000000000000000009000000009000009A00009000B0000090090000C9A90F0000000000000A000000000000000000F0B0C09A0A0A0000000000000000B0000000900000000000000000000000000009CA9E99BC9BF909C9000090000000000000000000000000000000000000000900000009A9E900900F00000B00900C9000000000000000000000000000000000F00A9A0009C90F000000000000A000000000000900F000000000000000000009000909BC09C00AD0B0F90000000000000000000000000000000000000000090E90000000C00090E900000000000B09A0000000000009000000000000000000000F000DADA00A000000000000000009C000000000000009000000000000000000000000090A9090BC90000000000000000000000000000000000000000090CA900000D090B900E9000090BC90090C00D0000000000000A9A00000000000000000F00F0A00000000000000000000000A00000A0900000000000000000000000000009000000D000D00BC90000000000000000000000000000000000000000090000090000AC00D00000AC000000CA90A0000000000000A00C000000900000000000F000000000000000A000000000000009009C00B000000000000000000000000000090009000900900000000000000000000000000000000000000000000000C90C0009090B000BC090A09A09A90090000000B0000B0CB0A00000A0000000000F0B0A900000000000090A0000000009A00000000909000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000BC0AC090009A0090000C000F000000000A00009A0900000000000000000BC0000C00C00900B00A00000B0000000000009000000FE0000000000000000000000000000000000000000000000000000000000000000000000000000900000000090090000CBC09C009C09000900000000000000E09ACA0000000000000000FA90CA0B009000A000000000000000000C90AC0000000C00D090000000000000000000000000000000000000000000000000000000000000000900090000000000000000909A900000000A000BC00000000A09000090E09000000000000000009C0A90000A00A00000000000000000009000900DA00900000000009000000000000000000000000000000000000000000000000000000000000009000000900E9000000AC0000090000000900000000000000A009A0A9AC00000000000000000EBC000000000090000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000900000C9090B0C0000B00F0009C000000000090000009C09A00000000000000000DA9A000000000A0000000000000000000A000B00000000000000000000000000000000000000000000000000000000000000000000000000000900000000000000DA0000000900A009000CB0B0000000000A000000A0AC000000000000000000F0C00000000000C0A000000000A000000000000E900CB0000000000000000000000000000000000000000000000000000000000000000000090C00000000000090009E90000A09C00000B0000000000000000000A00909A00000000000000000DA00000A0000900900000000000000000000009000900090AC09000000000000000000000000000000000000000000000000000000000000000000000000009000090000909000090009C0000000000000A000000900A0000000000000000000F00D00009A0000A0000000000000000000000000000000009000900900000000000000000000000000000000000000000000000000000000000000000090000000000000C0AC90000CB00000000000000090000000A00000000000A000000000AF00A00000CAF0000000000A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009A90000000B0000000000000000000A000A009A000A9C0A0000000A000F000000A0900000000000000000A0000000000000000900000000000000000000000000000000000000000000000000000000000000009009000000000000900009000C000900090000000000000000000A0000900000000C0A090F000B09C000C0000900A000000000009A000000000000A00000B00C0000000000000000000000000000000000000000000000000000000090D00000000000000000000000000E0BDA909E0900BC0000000A00000000000000009A0000B0A900A00000CA000F00000A0000000000000000000000000000000000009A00000000000000000000000000000000000000000000000000000000000009000000009C00900000000009C0000E0000BC000000000000000000000000A0000A000000F0C00000B0000AD000000000B0000000A00000000000000000000000009000000000000000000000000000000000000000000000000000000000090000000000009000000000000009009009000000000000000000000000000000000090A9E00B0000000EBC0DA0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009C0000000000000000000000000090900BCA0009ACB00000000000000000000000000000A09E0C009A0000000A90A0A900000000000000000000000000000000000000000A0C09009C00000000000000000000000000000000000000000000000000000000000000009C00000000000AD00900CB0900000000000000000000000000000090A0B0B0AC000000900F00DA000000000000000000000A0000000000000000000090A00000900000000000000000000000000000000000000000000000900000000000000000A90009CA0F090B000900C000000000000000000000000000A0B0A0D0000009000000ADA000F00000000000000000090A00000000000000A000A0900000F00AC0B0000000000000000000000000000000000000000000000000000000900009A000000009000000D000B00000000000000000000000000900C0CBCA0A00000A000000000F00F0000000000000000000C90009000000000000000000A0900090000C9C00000000000000000000090000000900000009E000000900090000F000C9C0A90B0C00BC00000BC000000000000000000000000000CA9A9009009A0B0000000000B0000F0000000000000000B000AC0A000000000000DA00A0900000000090000000000000000000900000090C0000A0000000090900000000009000900009C0000B090000B0F000000000000000000000000000A0A90000A00000000000A9009A00A0F0000000000000A00000B0090000A00000A09A00C900CA00B00000000000000000000000000009000090009000D00C90000000000000000090A0B000090000000090C90000000000000000000000A0000009000A00000A0000000000A0C000000F0000000000000D00000CA0C0A000000000000B0A000000000B000A090A0D0000009000000000C000000000D0000000000000000090000000000090AC09C000DBCB00000000000000000000000000009A00A00000000000A00000000A0BCB00F00000000000000A00000090A90000A00000000000CB000000C000900009000A000000000090009000000000000090000AD0000900000D00E090DADA9000090F000000000000000000000000A000900A000000000000000090000E9A9000000000000000000009E00A00000000009000000000B0CB000009E9A00000000C0000009000D000009A00C0000000000000009000BC0ACB0F00A0900000000000B00000000000000000000000000000A00A0000000000000000000C90A90CAC9A00A0F00000000000A09009C0000A0000A00900A000000000000000009A0000A900090000000A90E0000090C0B0000F000000000000090000900000000000009000CB0000000000000000000000A0900000000000000000000C0A00A000A09A00A900F0000000000000A0C00A000000A000000000000000000A00A9E000C0B000000000090A9C009000D0A900000900000000000000000000009000C90900F00DA900000000000000000000000000E00000000000000000AC00B09A9AD0DA00000000F000000000090009A0090C090000000A0000000A90009009000BCB00000000B000E0000000000B00000090E0000900900090900009000C009000000900B0000000000000000000000000000000000000000000909A00B00CA0C0A0A0000000A00F000000000A000000000B0E00000000000000000A0A00000EBC00000CB0000090000CB000A0C0009C00A09000000000000000900000090000A9E0BC0BC00000000000000000000000000A0900000000009A9AC0E00B0E9A090A90000B000000F0000000E9000DA000000000090C00000000000000900000A90A9A00B000B000A090B000BC9090A000B0D00009C09C000000000900000A900D00900000000000000000000000000000000000A00000000AC000AB0B0C00000A000000000000000F0900090000A000000000000A09A00000000000000000009E9C00F0000C0090C0AC0ADA0ACA090DA0C0000C0A000000B00000000D09000CA000000000000000000000000000000000000000000A00B000B0E9C00CB09A090000000000000000F00000A000AD00000000000000000000000000000000000000A0B00000B000A09A00BC00D090CA000900B09090090009000900F00A0A00090000000000000000000000000000A0000000000009000000B0009A0B0A00A00A0000000A000A00000A0000000000000000000000000000000000A900000000000B0BC0A0000000000090009A0A0A900900A9C0A000C0A0000C0E0B00BC9C0000000000000000000000000000000000000000000A00A090A0009A00000000000000000000000000A0F000A00009000000000000000000000000000000000000A900C0A9C90009E000000E0A000C9C0AC0AC00000C0A90C0000A909C0F0A000A0000000000000000000000000000000000000000000000A0090A00A90A09A000000000000900000000F0F000CB000A00000000AC00000000000000000000000000B0B00A0A00A009090A09090E9A0009000090A90B00000000900A0B00F00000DA00000000000000000000000000000000000000000000000A000000000000000000000000A0000000F00009A00A00900000000090000000000000000000000A0C0000F000000000A0090A0E00000B00A900A00000000A90000000C0E9000B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000F00AC00C9000000000000A00000000000000009000900900ADA000000000B0CBCBCA900000000000900DA000090009A00090B00000C0B000A000A00000BC000000000000000000000000000000000000000A000000000000000000000000000F0090A90A0000A0000000000000000000000000A000A000A900000B0000B0CB00A09ACB00BC00000A00A00000000000000000090000B0C000000D00000009AC000000000000000000000000000000A000A9000000A0000000000000000000000ADA0000000E09000000000000A00000090DA9AC00C000000000090000900B00B09A09000000009000000009000000000000A00E0A900A90090DA00000000000BC0000000000000000000000000000C00000000000000000000000000000A0000F000000A9090C00A0090A000000000A90AA0C009A9009A000A9A0000A0000AD0CAC9E000000B00A090090A000000000009090B09000F00A00A000000000A0900000000000000000000000000000B0900000000A00000000000000000000000000AC0000000A0A00000A090090000090CA9C9A09A00A0000000000A0900A9E9A0B09A0000000000C000E00000000000B0CA0CAC0ACB0000000000000000000A00A09000000000000000000000090C0A00000A0000000000000000000000000000F09A0000000000D0000000A000000A9A90A00000C000000900C0000000000000000009A09A0009A9A00000000000000900090BC900E90000000000000000000000A0C00000000000000000000A0BC000000000000000000000A0000000000000F0000000000090A000000000A90000C0AD0F00B0B000000ACB0A90A00000B0F000BC00C00C9A0000C90B000B0000000A09A000A0B00A000000000000000000000000A000000000000000000000009E00000000000000000000000000000000000F0000000000A00000000000000A9A9ADA0000000000000000000000000000000000B00B0A00000B0A0000A0000000000000B09C0CBC00000000000000000000000000A000000000000000000000A00000000000000000000000000000000000F000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010500000000000089AD05FE")}, + {empKey(3),ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E506963747572650001050000020000000700000050427275736800000000000000000080540000424D80540000000000007600000028000000C0000000E0000000010004000000000000540000CE0E0000D80E0000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFB009A00A9009A9AA9A900009AA9ABE9EB0B0BB09A0AB0A9A9B0FA0FA0CBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFE9A09A9EBEBFBB0BB0AB09AC9090AB0BCB0BA9E9AA9A90B000009AAB09009BEB0BAD0EB9E9F9B0C0B0FADB0F0BEB0FCBC0BCBFB0F0BBE0B09BA0BBBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FFA9DABEBFFFADFFADBF0BEBBAFAF0FBBAB9ADABADBE0AB0BCBFBC090E0A9AFA9BC90BB9AFABEE0BB0FA9ABEBE9B0FBA9AB9A9A0FB09E0BDAFA0DBE9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFB0FABFFFAFAFEABCBAA9F09AF0B9BBCBE9EBE9ADA9A990FAB0000BFA9090B09E0BAF00A9ADAB9BCBE9F0BCF0BE9FA9EB9CA90FBEAF0BFBA9ADBA9ABFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FE99A9FAFFFFEBDA9ADBCBFA9BA9EAFACB0B9AF9A9AFAEB0900BB9A0000A9AF0BBCB0B9E9BADAFAB0BABEBBBC9EAA9EBEABBE90B9B0B0BCBE9A9FADAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFBAFBA9BEBE09ABFEBABAB0F0F0F9DBFBBAFAB0FAF0B090BCBC00090B0B0A0BBE9B0BCA9AF9A9BC9E9FAD00ABAB9FA9CB9C90B0FABFF09ADBFEB00B9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCBE9ABDEFBDBBFFCAB0F0D0FB0BBA0ABF0FDA9F0A90009A09A0B00000000D0BDA9AFADA9BDBEBFABABBADABFBDA9E0BEB9EBABE9A9EB0FE9AB09FAFBAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B9E9FABADAFEB0BBDE9BABBA9FEDBBF0BFABC0A900BFE0BC0900B090B0B0BFAFE9A9A900A0B0E9CF0C0B0F0CADABF0F0ABBCB0B0BCBEB0A9AFA9A9E9BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFABA9F9FAF0FCBCAB0BE90EDABBABCBFA090B090FBFEF009A0B00A000F0B00B0BE9EBAF9FBFBBBBABB09FABB0F9AFA9FBCFBCB0E9B9A9FAC9F0BC9F0BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCBF0DA0A0FBEBABBDEF9AFBBADA9CB0A0BCA00BAF0BFFE9BA9009090B09ACBBAFF0BADB0A0BADACBDE9CA0BC0BAAF0FBAEB00B0B9A0F9EB9ABAFABABBEBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0BA9BFBACB9EDAB0BAF9AE9A9FA90900090B0F0BFBFE000CB0A00009A9B0ADB0BE900A99ADABFBABEA99A0BF0F9AF0FF0BFADA0E9BABBEBC90BC9E0BCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9E9AF00FBFEBA9EBBC9A09BCA000A09A9ABCB9BFFEE9AF0B009090BAFBEB9BAF0BAFF9ACB0BFACBDBBE0DBFAF9EB0B0BE909A99B00BF0B0AABCBABDBBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0BA9AFBCBCBBEBBCABAD000090909009AC90B0AA9FF090B0B00A0BC90F090F9EBCB0AF9ABF0B9BAA9CB0A00FAABCBEBDAFAF0E09BE90FBF9CBB0BCA0DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF0FBDA0BABE90CB9B0FA9AB000A00B0A9A9A9F9EBFE0BC00090900BA9BEB0BABCB0FB0BCB0FBEBDBFA9E9BFADBCBA9A0BE90BBB0E9AFA90FA00BE9A9BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A0B0EBFBCB9EBBAF0F0BC90B0090000909A9E0BBFEE900B0DA000900FA0B0B0BBE9B0BEB0B0AB0A0F0BBC0A99ABAD0BFF9EB0C009A909EBA9E9F0BCADBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9BDBFBB00BE0BAC9AFA9ABFE09A00909A0BCBA9AEBE90AB00A90A9ABFBE9F00FC9AEBC909ADB9EBFB0BC0B09AE9CB0F0A0A90B9EB090FAB0FA9A0FA90ABFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0A000CBE9BE99BFA90F090AB00000009090F0BFBFE0B0DA900090BCBA90AB0BEB90BAFADBA0FB0EBEBABEBC9AA9A9ABF90FA009A9EB09E9AFADA9CA9CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB9FB9A9AE9BEFAF9A9ABEB9EBCB00000A0B0BE9AFF0FA090B00A90B0FADB009BAE9ADBAB0FFB0FBF09BF00B0D0F0EB00AB09EB0DA90A9AF0B0B0CB00B9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0A00EBCB9BEB0BA0F0B09AE90B00B00090A909FFF09009A00090A0F9A9BAB0B0BFADA0D0F0B0BA9A9E00AF0A0BA9BC9E909AB09A09A9CA9ADACA90B0BCFFFF9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9B9BB0BE9E9E909F0BCBEB90B9CB00900BB0FAABEE0B9E090B0BDBBAF0E90C9AE90B0BAB09AFDABE9ADB9A909A9E00A90E90D0A09E0B09A9A090A00009BFE000909FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0CACBE0BA9A9AB00B0B0BC0BCAB0B0000009AFBE9A9E090A0009ABEFABBAB009BADAF09ABE9ABBDAFBAFA9E9AD090B0A90A0A9CB0900B0DADB0B000900BC90000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB9ADBE9FA9E9EB00F0F9A9E900BCB0B900F90FBEDA9A09090A9A90BC0DB0B09AFB00BCBCB0BCBEB0BDA9E0BC0A0BC090A9090B09A9B00B0A00009000900000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A9AA9A0BF0B0BFBA90ACB0B0F0B0B00A90ABAFE90ADA0A0090DAFB0BA0BC0AFAC0BCB0BADAB0B0FEBA90B00B0900BCA9CB0A00A0000B09090900000000009000009BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9BF0F9FADA0BBFF0E90A99ABCB090B0E9000B9FE90E9009009AFAA9A9A9F0A909BBB0B0BC9A9EBDB0B9E0BCA9000BA009A0090B0909A900A0000000000000000090009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9A9AB0BBF0F0B0B9EBDAE90B0E0BC9AF0B00E9F0A9A9A090A9A9FAFCBB0B0B00FAF0E9AB0B0BAB0F0BBF0B0A9A909A9090A0000000009090000000000090009000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBCBE9AF0F09AFAF0A9EA90A90B090B09A90B0BAF090009A0909BE9BA9AFBBC0A90F0B0A9CB0F0DAFABE0000900000000A0A9090A9A900A0000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09A9ADBF0BBE9B0BBE90BBCA909A009A9AC9A0BEBA0F0009A0BEBBE09F0009A90BABE090B0B0BABBCB090B000090009090900A090000900009000000090000000009009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9A9F0A00ABE9B0FA09ABC0B9E0A9F9ADA9A9E9D00C90A9000F0BDA0DA0FBBE9E09F9FA0B0E0BCBCBAD0A009000000000000009000000090000000000000000000900000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA9A0B9DA9CB9EB0BDAD0BBC0B09000A90BF0BAA9A9A9009A90BAAFFABBA0A9A9A0A0BDAC9B09A9BE9A909000000000000009000900900000090000000000000000000900FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9ADA0A09AB0A0BE0B0A9E0B00B0B0B0A900F09E09000B000B0BFF0B0F9F9BE9A9E9BEA9BA0DA9E09E9000000000000000000000000000000000000000000000900000009BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE09A9BC9A9CBE9E9F0F0B09A9AD00B09C0B0B0BC9AA9A009A9ADABFADAABABCBCB09EB9C009A9A09B0A00000000009000000000000900000000000000000090000000909C9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09A9C00A90A09A9A0A0BCBA0D0A0BC9A9A900BE9AC90090BCBFAFABCBADA9BA9A0A9BCAB0F0B090A0090000090900000009000000000090000000009000900000000000BE0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FFE90A9A90A90B090909A9A0DBA0990B00B9E09F0A9A09A000B0BFDEBE9FBEBEFAD9000B0090A0F0BCB000009000000009000000000000000000000900000000000009000BF0BFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBFFA09000090090000000909A009A0A00B00A90A0F009A09A9ADA0BADBB0A9F09B9A0F0BADB0A990BCB0000900000000000000000000000000009000000000000000000009FE0BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA909090009000000000000000009A00909A09BC0BFF09AC090090BFF0FAE0BF9ABBEE0B0A9F00A900A090090900000090000000000000000000000000000000000009000900BF09FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF909090000000000000000000000900090BF009E00B0BA0A90B0A9AFA9ABA9BFABAD00BFF0900BA9DA0F0B0B00000090900000000000000000000000000000000900000000009A9ED0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000090000000000000900000009000000B0B0B0BECBDA0090CB9EBFF0F009CBBAB0BEBE909E0A9009000B00090000000900000000000000000000000000000000000090000900BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00909000000000900000000000000000009009009A09BF0A090A0B0ABBEBEFAFEBBCB9ADAF00A9AF090A9A9E9009000900000000000000000000000000000000000000900000090009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09000000900000000000000000000000000000A9A009FEA9BCA9090BB0FFFFABF0BCBEAB0BFEB09E9A0A90009000009000000000000000000000000000000000000000000090090090FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC00000000000000000000000000090000000009000909AF0DA090E0F0FFFEBEDFA9EBA99AF0BF09ABE09009A9A09090000000000000000000000000000000090000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00009009000000900000000900000000000000009A00BF0BA09A99BBFAABFFBB0BE9BCBA90F0FE090BCA9A0009A000000000000000000000000000090000000000900900000000090D0BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9000000000090000000000000000000000000000090A0BC9AD00A000BFBCBCBEF0BAFBCBAB0BABCBAF09009A9A0900900000000000000090000000000000000000000000000000900A9FEFFFFFFFFFFFFFFCBF9FF9FFFFFFFFD99F9FFFFFFFF000900090000000000000900000000000000000000009BEBE90A909E9F0BBFFA9EBC9B0B0BCF09EF0BC9A09A00090000000000000000000000090000000000000000000000090090A99EBFFFFFFFEF9A9BF9FF9F9F0F0BDBFFFB0FB9F09FFFFE00000900000000000000000000000000000000000090BAE900A90BABBAFAFEA9EB0BAAF0FEB00AFBE9A0900909A00000000000000000000000000000000000000000000000000009C0A9FFFFFFFB9DBDFDBFDBFBF9FBDBE999DDB9CB99A990FF090000900000000000000000000000000000000900009FBAF09A09E9E9E90BCB0E9EBDAB0DAF9CAFFE9E0B0A0090909000000000000000000000000000000000000000009000000B0900BFFFE90DB0FBFFDBFDF9FBD9F9BFFABDBFBDBDB0F90900000009090009000900090000000009000000000000A9E90A09F0BFAFAFEBBCB9E9EBF0BAF0AB9BAFA9A0909A00000000000000000000000000000000000000000000000000000000BCBFC9909B0FBDBFFF9FBE9DAF9FDB9DBBDBDBCBF99ADA00000000000000000000000000000000000000000009BEFA09A00BF0BE9A9E0B0A9A9E0B009EF0E0FE9AC90A000900000900000000000000009000000000000000000000000000900B09009ACBF0F9DBDAF9FBDFFBF9ADB9FFBCBDBDBF09F0909090000000000000000000000000000000000000009A9A909AD0B00ADCB0F0900909A9A9E0A90BFFA9EB0A090900090000000090009000000000000000000000000000000009000909009A09909F9FBFFF9F9FF99F9FDF9FB9DBDA9B99DB0B9000000000000000000000000000009000000000000000AF0E90ABCBFBEB0E9A0B00A000900C90000BEF0F9ADA000900000000000000000000000000000090000000000000000000000000090A9F09B9F9BFBFF9BFF9FBBDBDFFB9F9F9FABD9CB0909090000000000000000000000000000000000000909A90A90DA009BCBB0F00F090900B0000B0009AF0AF009A9000000000000000000009000000000000000000900000000000090009009009BFBCBFDBDF9FCB9FBDF9FB9ADF9F9B0D9A9B90000000909009000009009009000000000000009000009E0B0F0A9FAEAB0CB0B000000000900D0009009AF00B000009090000900090090000000000000000000000000090000000000900090BDBC99FBDBFBF9FB9EF9FBFF0F9F0F9ADF9BBD9E9A90900000000000000000000000000000000000000A9A0BCB0ADA009F0EB0009A900B009AC000000000009A0F0090000000000000000000000000009000000000000000000009000000900090B9AFBDBFFDBF9FDBDFF9F9FFDBF9BDB9BD0B09000000900000000000000000000000000009000000900090009009ADA0B90BCB000090900009C0D00000000090B0009000090000000000000000090000000000000000000000000000000090B9CB9DBFF99F9FB9F9B9FFFB9BF9BE9F0BCBD0B9B9090900900000000000000000000000000000000000B0A9A9A9A09A9E0AD0B09009ACA00000090090000009000090000000009000000000000900000000000000000000000000000000090909B9EBDB9FBFFBCFBFFFBDBDFF9EF9F9FDBB9BD0D0B000000009000090000000000000000000000009090090000009A090900A000A9A90909000000C00090000000000000000000090000090000000000009000000900000000000000900900A9DA9F9F9FF0F9BDB9F9BDBEBF9BD9B9E9B9C99A9A09090900000000000000000000000000000090000A00B00B09A9A09A0A9A9090900090000090090900000000009000009000000000900009000000000000000000000900000000000000099A99F9B0F9BF9FDB9F9FFF9F9FFFFBEDBCF9B0F9C9B09A00090000900000009000900000090000000000900090A00090009000000009090B090000000E000000000000000000900900900000000000000900000000000000000000900000000009E9ADBBDF9B9B9F0BF9B9FBF9B99F9F9B9EDBCB909C090900000000000090000000000900000000009009A0009090009000900909A00A0000000900009000000000000900000000090000900009000000000000000000000000000000000909AD9BDB9DB0F9E9F9F9BDF9BDBFDEF9F0FDB9B9B9CB0B9090090900000000000000000000000000000000A000A0000000000000000009090900900C0D09C09000000000000900000000009000900000000000900000000000000000000000000099BDB9EB9F9F9B0B9F9FB0F9F0BB9ADB9B09E9E9A9090A00900000009000000000000900000000000009009090000000000000900090000009A090A00C0000009000090000000009000000000000009009000000000000000000000000090099A9A9E99DB9B9BDBDB9E99F9A9BDBDF9AFDF9B99C9B0090900000900000000000000000000000000090009000000900000000000090000909A000C0D0D0F0909000900000000090000090000000009000000009000000000900000000090000009DB99FB90DADA9B9F9FB9B9F9BDBA9F99A9EDB0B0D9A900900900000000009000000000000000000000000900900000000090000009090009090900CAC0C0000000000000000000000000000000000000000000000000000000000000000009A9A9F990F9B99BD0B9090D0F9FCBD9F0BE9F990F9B0A9CA90000000000000000090000000009000000000000000000000000000090000009000E0C0C9C909000900009000900000000000009000000000000000000000000000900000000000909D9ADAF9AD0BD0BD0BDB9F9A9B90B0DF990BAF090D9009009000000009000000000909000000000000009000000000000000000000090B00F0D009A000C090000000009000009000000000009009000000900000000090000000000000000909A9BDB99099F0B90BD9B0B0BD0F9F9B9ADABD99BDA9A9009000090000000000000000000000000000000000000900900000000090090000900DE900D0D000000090090000000000009000000000000000000000090000000000000009000000BC9BC9B0BDA9090F90B0C9D90B90B09CB99D0B09CB90D0B00909000000000000000000000000000000000000090000000000000000000009ADAD00C000009C00000000000090000090000900000000000000000000000000000000000000000909B0B9C99090B9F9AB9F9B0BBD0B9F9B90B0F0F0B0F0B0909000000000000000000000000000900000000000000000000000000000000900C0DAD090C90C00000000000000009090000000000900000090000900000000000000000000000090B90D9A9ACB0BD0909C90B0BD90BD00A9CBDA999099909000000000090000000000000000900000000000000000000000000000000000000009ECF0E00C09000090090000900000000900900000000900000000000000000000000000000090090F9BC90999D9A9ADA9A90D09A9A9FBD0B909F0AD00BCB09090909000000009000090000000000000000000000000000000000000000000090009C90D0900009000000090000000000000000900090000000000000000900000000000000000009090B0B0B00B90999909A9B09C9090BD0F9A9990B00909A00000000000000000000000000000000000000000000000000000000000000000009E9C00CAC09C00000000000000090000900900000000090009000900000000000900000900000B0B090099C9BD0F0B0F9F990DB9BDA909B90D0F09090A9C9090000000000000000000000000000000000000000000000000000000090000000900C09E9090000090900000900900000000900900900900000000000000000000000000000000990D009BDA9A90B9BDB90B0F9B9ADADB9E9EB9B09ADA990B0000900900000000000000000000000000000000000009000000000000000000000000FC000C0C0090C0A009000000000900000000000000000000000000090000000000900000090090B9090999B9C999CBD99BC9F9BDBDE909DADA99090090090000000090000000000000000000009000000000900000900009000000090090000909C9090090009C0D000000000900000009009090909009000900000000000000000000000090B009BE90F0DB9BE9B9ADF9B9BDBBDBFFF0B9BDAC9A9A090090090000000000000000000000000000000000000000000000000900000000009000C00AC0C000C9009000000000000000000009000000000000000000000000000000000000009A099B099B9B90F99B9F9B9BDBDBDFFF9F0FDADA99A9C900B0000000000000000000000000000000000000900000000000000000009000000000909C9C9A90C9000C0E90090000900009000000009000009000000000000000000000000000090990B0D0BC9F9F99BDF9BDFDB9BFFFBDFFFB099F9AD90B09009009000000000000000000000900900000000000000000000900090000090909000CCAC00C00000CB0D0C00000000009000900000000900000000000000000009000000090009A90E909B999B09BDBDB9F9BBFFDF9FDFBF9FDFCB0F90A9000900000009000000000000000000000000000000000000000900000009090000000090B0C9E90D0D0B0C0CBC900090000000000090090000900900900009009000000000000000009099BD09FBDF9BDB9BDB9FD99BFFFFFFDFFFBFBDB0F99E9A909090000000000000000000000000000000000000000000000000000000909009A000C9F0C0CAC00C09C9C0E090000090009000000000900000000000000000000000000000000909F09A9B09B9F9F9F9B9DBBFFFDBFDBFFFFBDFDEDB9E9909C00000900000090000000000000000000000000000000000000009000000000000090C9CC0C90909C9CE9E0C9C00009000900009009A0000000000000000000000000000000000900B09BDBD9F0F9FB9BDBDBFD9BDBDFFFFF9FDFFBDB0F9BCB0B0090000000000000000000000000000000000000000000000000000009000090900CBCA0F0CAC0CAC90C9CBC9E9000000009000000090090900090000000000000009009000000B9DB09B09B99B9D9F9BDB99BFDBFBFFFFFFFBF9FE9F0F0BC9090009000000000000000000000000000000000000000000000000090000090000909C0DC0F090D090E9CAC0C0C0C900900000009000000000000000000000090000000000000090B09F0DBF0BD0BB99F9BDBD9BBDBDBF0DB99FFFDBFF9BD9B0BC9000000900000000000000000000900000000000009000000000000000000900000FC0FC0C0C0E0C9C0D0F09C9A0000000090000900090000000009000000000000000000009A999B9B9099DB9D9E90BDA9B9D99F9E9FBDFFF9FBFDBFCBADBC9A09000000000000000000000000000000000000000000000000000009000000000C0DEC0DAD009C9CA9E0C0CA0C09000900009000000009000000000000090000000000000909ADAD9E9F9FB0B9B99B909909A9B0999BDBBBDBFF9E9E9BD909B090000000000000000000000000000000090000000000000000900000090000909C9E90FC0E9EC0E9C09C9009C9CAD000A00009000090009000000009000090000000000000BD9B90B9B9B09DBE9DBCBB90F990D9F0F9FDFDBF9FFBF9F9EB9FC9E0000000000000090000000000000000000000000000000900000000000000000BCCDC0F0D0DAD0E9CA0C9C0009CAD0909000090900000000000000000000000000090000D0B09F9DA99BDB999B0999CBB9EBB0B9B9E9BFF9FBDF9FDAD9E9AB099900000000000000000000000000090000000000000000000000000000900000C9ACBCCADEADE0D0AD0AC00D0C0D0A000090000A000000000000000000000000000000090B9F9A9B9F0DB9ADBC9BCB99D099D9F9F9BF9F9FFDFB9EBDBBDBD9FCA0000900000000000000090000000000000000000000000000009000000009090EDCCCBDE0D0C9CAD0CD0BC00E9E0D009A00000909000000000000000000000090000090B9DB9DBDA99B9F9B99BF9B9F0BDA9B09FFFDFFFFFFBDFF9FBCBDBF0B90D0000000000000000000090090000000000000000000000000009000000000CD0BB0CE0FCADAD00F000C0C90C9E0F000900900000000900000000000000000000000BD9EB90B09B9F9F09F9F999D099999999F99FFFFFF9FFFBDB9F9FBC9F9E900000000090000000000000009000000000000000000000000000000000000DACCC9E9C0FC0CAD00DAD09ACBC0D00BC00B00090009000000000000900000000000000B99B9F9BD9F9B9F9BB9F9A9B9BD9BDF99F99FFFFFFFDFF0F09BCBF9CBDA900000000000000000000000000009000000009000000000000000000000090DAD9E9CAD0BCF0DAD0C0C0C90CB0E9C9A000900009000000000000000000000000009BDAD9E9BF0B0BDB9BDD99999D9D9F999DB9DBDFFFFFDFF9F9BFFBDBFBF0F0F090000000000000000000000000000000000000000000000000000000090C0DCC0E0D0EC90CAC0E0DAC90E90C9C0E90900A0900000000000000000000900000099E9B9B99F99BDB9BDB9B99F9990909999099FDFDFFFFFB9FB9F9BDBF0F9F990000000000000000000000090000000090000000000000000000090009000CBC0AD0F0D09ECBCBC9C0DA0D0EDA0BC900A009000009000000000000000000000090B9F9FDBF9BF99DBDA99CB09999B9B09BF99999FFFDFFDFD90B9FFBDF9DBDBE90000000000000000000000000000000000000000000000000000000000009CF09EC0E0C090C9CA0F0CD0E90CDC00E9009000B090000000000090000000000000BDA9F9BDBD9BE999BDF9999900B9F9BFFFF099099DFDFBFBDBDB9FFBFFBDADF0F0900000000000000000000000000000000000000000000000000000000900CDC9E9C90F0E9E0DC0CB0C9CE9A9AD00F00A9000000000000000000000000000909F9F9AF9BFBD9BE9B9909090B9BFBFFFBFFFFFF9D9B9FD90B90BFFDFDBFFFFBF0000000000000000000000000900000000000000000000000000000000000F0C9E9E0AC0C9C0F0909CC9E090C0D00F0090009000000000000000000000000009F99BDF99F9DBAD99CB090B9F9FFFFFBFFFFFFFFF00999BFBD9F9FFFFFFDBDFC9C90000000000000000000090000000000000000000000000009000000900D0CBECCD0D09E0F0C0CAC0BC0F0E9E0BC0900090009090000000000000000000009A9AF9B9BF9BBD9B9B909009BFF9FFFF9FFFFBFFFFF0000999A99BDBFBF9FFFFFFB0A000000009000000090000000000000000000000000000000000900000EDFC9E9AC0DA9C0F0F009C0BC0D009C0BCE9000090000000000000000000000000BDBDBDBF9BFDFBD0F090009BDBF9BFBC0BDBD09B90B909000990BDBDFDFFFFF9FDF99000000000000000000000000000900090009000090090000090000090000FE9EC9E0CCBDCD00DA9CC9E0F0E0FC090090000900009000000000000000000DB99BDB99F9B9DB9090000B090090909990909909D0000009A09F9BFFFBFFDBFFE9E0000000000000000000000000000000000000009000000000000009000DED09CC9E0D0BCCA0AD0CCE9A0D0C9D009E0000090000000000000000000000099A9FBDB9DA9B9FB009000909909B90999B0F9BDEF90999F9A9C9999BF9BDFDBFFF9F090000000000000000000000000000000000000000000000000009A000DAD0FECBC9CADE0BCDCADA90CDCADACACFC90009000090000000000000000000009F909F9FBDBDF9DB90B9F090909099090999099999F9FFFFDB09F0BDBFDFBBF9FDFBDA900009000000009000000090000000000000000000000000900009000C0F00BCACBC0DCCB0D00CE9A0F0CD0D00E0900009000000000000000000000000B9FB99A99B9B9B99E90B9F9BCB9DA9DB9FDBDF9FFFFFFF9FFDB09BDBDBB9FDDBFBEDBD00000000000000000009000009009000000000000000900000900009A9DCFDC9C9C0F0E9CCA0D09CC90CB0F0FC900000000900000000000000000000090F9DBD9BD9F0F9BC9BD9B9DB99F999B9F9BDBFFBFDFFFFFF9A9DB099BDDB9FBFDFFDBCB0000000000000000000000000000090000000000000000900000090CCAD0CA0F0CBC0D0E90DA0CA9ACF0CC0C000090900000009000000000009BF0099BB9B9BFDBB9B9B99B90BC9B99F9DBF9F9BDBF9DF9FFFF9F0F99B9DBBDABFDBDFBFFFE90090000000000000000000000009000000000090000000000900000C9AD0F0DC0F0C9CAD0CA09C90CD00DA9EDE90000000900000000000000000BF000BD0F0F09B9C9F9DA90BD99A9F9B9B99B9DB999BBFFFFDFF9F9F0D0B9DB9D9FBFD9FDBDF900000000000000000000900000000000090000009000900000009CBCDED000F00F0E9CAD0D0E0E9A0F0CC090C00090090000000000000000000909090B99B9BD9BB99A9D9F9B09D09BC99F9DA90F9D09B9FDBDF99A99B99F9DBF9DFBFFFFFBEDA900000009000009000000000000000000000000009000000000C0C0E9EFC90C90C9C0D00A0909C0D0DA9ECE90000000000000000000000000000000BDBD9F9B0D9F9DB9B909BF9BD09B0B9B99F90B9FDFBDFA9F9F9F0DB9B9F9FFBDFFFFDFDB00090000000000000000000900900900000090000000000090090D0D9C9C0AC9ACB0E90CBC9CACADACE9CC900009000000090000000000000000909B99CB9AD9F9F09B9AD09BD9090B999D99CB09B9FFFFDBFDF0BD09BB9F9FBDFFDFFFFDFFED0900000000000000000000000000000000000000000009000000C0F0CADECD0C900D00F00CA909C0D09CB0C909000900000000000000000000000009CBB9F9DB9B99F9D99B909B9B99E99A9B9090D9BD9FFDBDBDB9B99DB9F9DBDFFFFFFFFDBBC00900000000000000009000000900000900000090000009009E9C0FCDA9AC9E0FC0F0CF09CEDA9E0DE0CDA00000000000000009000009000009090F9DFF9B99F9F99B9BD9F909D9F99A99009B090B0B90FBDF9BD90F9BDBDBFFFFFFFFFFFFDE9900000000000000000000000000009000009000000900000000C9C0BCFCD0E9C09E0DA0DE0900C9CA9CB09000090000090000000000000000000090BB99BDBF9F99F9BDA99090B0990900990990BC90099DB9BDB0999F9F9F9FDFFFFFFFFDFBCA0909000000000009000000090090000000000000000090090D0E9CC9CBCAD0E9E0DACDE0DACAD0E9C0C000900000000000000000000000000909A9BD9BDBD9BDFBDBDB9DA9B909B09C90B099CB99F0909B9BC909B9F9B9FBFFFFFFFFFFFFED0900000000009000000000000000000900000009000009009AC0C90C9EBC0DAC9C0F0C9E0F0C9DAD0CADA90000000900000000900000000000000090DBF9F9FBDB9DBDBDB9D09AD909B00D9B0B99F999BDFFD9B9B99F99FFFDFFFFFFFFFFFFDFF00090000000000000000000000900000000000000090000C0DCBCDACDCF9C9ACBC0F0E9C0F0AC0AD0D0C0090009000000000000000000000000009B99B9FBDFBDBF9BDBDBBD990B0999B09D99B99BDFFFFFF0D09DB9F99FFFFFFFFFFFFFFFFCDA90000000000000000000000000009000000000090000909C09C0CDA9E0CA0CD09E0D0CBC0DC9ED0E0F090000900000000000000009000000090909FFF9BDBDBDBDBDB9F9D9A999099BD9B9BDDF9FFFFFFFC9B9F9BDBFFFDFFFFFFFFFFFFFFFB00000900000000000000000000000000000900000009000A9EC9EBCDE9F09C9A0E0DACBC0DA9AC0F0DC00009000000000000000000000000900009BF9BDF9FBDBDBDBDF9F9B9DB099AD9BDBF9FBFFFFFFFFDB9DB9F9BDBDFFFFFFFFFFFFFFD90D0090000900000000009000000000009000000000000000DCDAF0D0F0FCC0AC0D0BC90C9E0CCDAD0E9AD090A00009009000000000000000000009A99F9F9FBDB9BDBDB9FBF9F90B0999BDBD9BBDFFFFFFFF0D9B9FFFFFFFBFFFFFFFFFFFFFFFF00000000000000000000000000009000000000000000009ADADC9EF0FCB0D09E0D0ED0F090B00DADCE0C0A09009000000000000000000000000909F9BFBF9DBDF9F9BDF9D9F9BD990F90BDBD9FBFFFFFFFF9BDF9BDBDF9FDFFFFFFFFFFFFFFCBF000000000000009000000000090000000000009000000000DAFE9EF0FE000C0DAD00E0CAC0DA0D0BCDE909000000000000900000000000000000B0BD9F9FBDB9B9F9F9FBF9BDB9E999BDB9BD99FFFFFFFD9BBBFDFFFBFFFFFFFFFFFFFFFFFBD0900000000000000000000000000009000000000000000009ED0FF0FFAD0C9ADAC0DE9C9C9A0C9CACDA9C000090000000000000000000900900099F9FFF9F9F9F9F9BDBF9DBDBDB99A9D99F9B9FFFFFFFFFFDFDFFBFDFDBDBDFFFFFFFFFFFDF9E00090000000000000000000000000000900000000900900CB0FE0FF0FF90C00D0F00C0A00D0BCADF0CF09090000090000000000000000000009A99F99FBDBDBDBFDB9F9FBDB9BDBD9B0FB9DBDBDFFFFFFFFFFFBFDFBFFFFFFFFFFFFFFFFFFDE9000000000009000000000000000000000000000000000909CFF9FDAFDAC90E9E00DA0D0C9AC09C00F0CE00000090000090000900000000000009A9FFBDF9F9FDFDF9F9F9BDBF9B9BF999DBD9BDBFFFFFFFFFFFDFFFDF9F99FBDFFFFFFFFFFFF0900000000000000090000000909000000000000000090CAC9CAFAFDAFF00C9C9EDAC90CB0C9ACBCFCE90900090000090000000000000000000009F9FDFBF9B9BD9FB9F9F9BD9F9F90BDB99B9DBDFFFFFFFFFDFFFFFF9F9FFBDFFFFFFFFFFFDE90000000000000000000000000000000000000000000009C9EBDCF0BFCB0E9E0E9C0D0E90C9AC9CBC090000900000000000000000900000000099BDBDBBDDBDBDBFD9B9B99F9F9B99F9A99F9F9BFFFFFFFFFFFFFB9F9F9F9BDBFBFDFFFFFFFE9000000009090000000000900000000000000000009090DE9ECD0EBCFCBFD00D0D0ADAD00DA0D0E9CADE90000000000000000000000000090090009BDBFDBBDBDBDB9FDDBDB99BF9DF9F9DB999F9BFFFFFFFFFFDEDF0F9FBFFFDFDFFFFFFFF9F000000090000000000000000900000900000000000000000C90BC9CDABCF0CF0ACD0C0ECD0DAC9CE9C0000900000090000009000000000000000090BBDBFDBF9F9BDA9BBDA9F099A9B09B99F9F9FDFFFFFFFFFCB9B9F9F9DFFFFBFFFFFBFFFE9090090000000000000009000000900000000000090000090FCDCBCBCDCBC090DC9ADAD09AC0C9E0DCBDE900009000000000000000000000000000009FFBDBDBBDBDB9D0909D09E9D99F9D9F9BDB9BFFFFFFF99BDBC9090B90B9BDFFDFFFFFFD000000000000000000000000000000000000090000009000C0000C0C9E9EFCAC0A0CC0CBCC9E9E0D0AC00000000000000000000000000000000000909B9F9B9BDB9BD9A9A990B0990B0B9AB9BDBDFFFFFFDD0BC990A90B090B9FEFF9FBFDFA9000000000000000000000000000000000000000000000000090DAD0DAD0FC99C9C9CB0F0C0BC0C0DACD0FC9090000000090000000000000000900000909FCBF9F90F9A99D99A9090A90909D0D9BDBF9FFFFEB909A09909C99F9F9F9FFFDBFBDB00090000000000090000000000900000000000000900000900C0C00CDAC9EF0CAC00C90DAC0BCBC0DACD00E000900090000090000000900000000000090BBDBCBFB9D9B0B09099A9909090B9B9DBDBFFFFFD0E909000909A09B0FDFBDEBCBDBC09000000900000000000000090000000000000000000000000900DA0CDAD0FF0D09E9CA0D0D0D0DE0D9ADF090000900000000000000000000000000090099B9B99DB9A9D090B009000A909099FBFBDFFFFDA9909090B9A999CDFBF0F9999B9A0900000000000000000009000000000000000000000009000000C90CD00DE9EC0CAC9C0CDACACACA0D0ECC000009000000000000000000000000000000090B9F0B0B0BD909BC90BD09990B090F99BDFFFFDF090B009090000AFB0D099A09ADBC900000000000000000000000000900000000009000000000000000C90EDE9ED0F0D00ADA0C9C9C9CDAC9E9ED09A0000090000000000000000000000000000909D090900B0F00090BFA00909A99BFDBFFFFE99F00090F0009090DF000099F9B0B0000090000000090000000000000009090000000900000000909C90CC9C0DEBD0CAC9C0D09AC0FCBCDAC9E9CA0D09000000090009000000000090000090090B0B90909090B0900009DF00909099BBDBFFF9E099090BC9000000A9009B090F0D0900000000000000000900000900000000009000000009090C0000C9A0CBCBCDCAD09C09A0E09E00C0ADF0CCB0DA000900909000000000000000000000000000990B0B000BC0000F0BE009A909A9F9FFFFFF99A09009A00909090009E909B90B000000000000000000000000000000000000009000090000C90F0D0C0D0CDCBE09C0E0BC0D0D0C9EDADC0EDBCDE090B0C9A000000000000000000000000000909AD090900900909A090990090B9DBBDBFFF9E009009000000000009E909E9CB009000000000009000000000000009090A90900000900000DA0C0C000D0CBCBED0CBC9C0BC0CA9E090DCAFDAC0E9E000AB0C0000000000000000000000000000909A90099C000000900000900099B9FFBF9FF90909009009000009FE90A99A90900090000000000000000000900000009000000000000090009009E9C0E9CBC9FE9CAC9C0DA9C09CECA9C0EDBE9C9F0DDEB090000000000000000000000000000909ADB0B09090000099B009090F9B9F9FFBDF9CA090090009C9FBD00990BCB009000000090000000000009000900000009009009000000090C9C00C9C0C9CF0F90D0DA9CADC0FCA90DCBC9E0DE9ECBFAFC000090000000000000000000000000090909009BDAD9CB9A0009A09B99FFBFF9F0B9B0900909B0B9A0009BCBF0909000000000000000000000000000009000000000900090000C00009C000DACBCFCCACAC0CE9CAFC09CFCAC9ECDE9ECBDAFC090A000000090000000000000900000909A90B09009A0B000909009B09FB99F9FF9F00900900000900909A09009000000000000000000000009000000000009000900000900909090D0C090D0C9CBCB0D0DAD09CAD0ADE009CBED0F0EDBEFFF900D9C90000000000000000000000000090990D9E0909900900909909DB99FBFF9F90DB0090909090090B09090900000000090000000090000000000090000000900090000000C00C000900E0DACF0FE0E9C0ADAD0FCFADEDE9C9ADAF9EDFBEFEF0A9A009000000000000000000000000000A9A990B000990900000B090F99F9B9E9B0900000009090090000000000000000000009000000900000000000090000000009009090D09C0C0AD0D0D0DE9FC9CADCC00E0F0DADACADEFFDEFBFAFFBDADFED0000000000000000000000009009099090A9090900000900009FB9BF9FFF9F9DAD000000000090909000000090000000000000000000000090000090009090900000000E0C00909C000C0F0DE99CAD00BCF0D0FEF0E9DEBEBEBFEFFFFFEDFADA09A09000000000000000000000000000909090000090000999B999F9FBF9DA9B99A90900000000000000000000009000090000000000009000090000900000000000009C909C00C09CF0F0DE9EE9C0FCC90DAD0D0ED0E9FDFFFFBFFFEFFADFAD009000000090000000000000000909B0009000900009090BE9DADB9F9F9FB9F0F09000090090000000000000000000000000090009009000000000B00900090090009C000C00D090C00D0CF0FFCBED0BCE0C0ADADBEDE0CBEFFEFFEFFBFFEBDA0009009000000000000000000000909B9A09000090CA9C9D9B9B9F9FBDF9F9DB9F9BD9F090090000000000000000009000000000000000000900D00D00A90000009009CD09C00C0C9C0C9E0F0FADCBEC90DADC0CAC9ADCBCBFBFFFFFFEFFBFE0D000A900000000000000000000000909C99A9DBDA999B9FB9FDF9BDBDBFF9BBDB9E90B00090000000000900900000000000000000000000000C9A9FA9ED009090000C0A00C09C00B00BCE9CDADDFAFC9EDE0C9E9C9EDCBCFDFEFFFFBFFFFEFE9000900000000000000009000009090B9B9B9DB9BF9FBDFB9DB9B9FBFFFF9FFDBDF9B9009000900900000000000000000009000000000009000FADFCBDE90F000090090D0D00C090C0DC09CFADFEADBFE9E9CBC0E9E9EADC9AFFFEFFEFFFFFF9E90009A090000000000000000000009099ADB09F99BF9FBDFFBFDF9F9F9FFF9BF0B9FC9009000000090000000000000000000009000000000900DEBFEFBEF0DCB0000C0000D090C09C00FCB0DFA9DECBFEBEDCBC90C0DCACFC9FFFFFFFFEBFEFEFE9009000000000000000000009090B0D9BDBDBF9DBF9FB9BD9FBFBF9FB9FF9F9F09B00000000000000000000000000000009000000090000090DFBFFE9EDACC9CA909C000C090C00D00DCF0DFCADBF0FFFABC0EDADA9CBCBEBFFFFFFFFFFFBFC9000009009000000000000000000909B90B9B99FBD9FBDFFBFBDBDBDBCB9DA9BDB0009000000000000000000009009000000000000000000000FAFFFBFDAD0F0C9C0C09C900C09CDACD0E9EBC9C9ECFFF0FCDE90CC9EFC9EDFEFFEFFEFFFFFCB000909A00000090000000900000909B09F9FF9FB9FBDBDB99F9FDFFBDBD9A9D090909000000000000000000000000009000000000900009000DBDFAFCFADAD0C9C0C90C000D09C000DAC9CBCFCBCF9E9EF0FA9EDEBCC0BCDADBFFFFFFFFFEFBC0900000000000000090000000090909DA9B99B9DBDFBDBDBF9F9B9BDBDBC9B09A90000009009000000900009000000000000000900009000090FAFFBF0FC0C9CAC90C090D000CA0DC0DADEDF0EDACBCF9EFDECBC9CBDCCADCADFBFFFFFEFFFEF00009009000000000000000000009A999F0F9FBBDB9FB9BDB9F9F9CB9A9B00909090000000000000900000000000000000000900000000C0000DFFFFFF0FDE0DC0CB0C000D0D0D00F0CD0F0FFFADFCF0F9FAFBCFACCADADADCFAFFFEFBFFFFFF0F09009000900000000000000000009A99B9B0DDBDBDBDBF9DB9EB99F9D0090B000000000000000000000000000000900900000009000900909EBFFEBCF0C0D009C0C90D000C00C9C9CAD0F0FFFFAFBFEF0FFCF0CDADAD0C0DADFEFFFFFBEFBEF0000000000000900000000000090909DA9DBDBBB9B9F9F99BBD99DB09B0909009090090000000000009000000090000000000000009000000CBFFFBDF0DAD0E9C009C00C0D00D0C0CADCF0FFFBFFFEFBFFEBE9EF0D0DE0F9EDADBFFFEFFFFFFDF0909009000000000000000090009A9BDB9F9F9DBDBDB9DBC99A9B0F09A000900000000000000900000000090000000000090090000009000BDFFFFEBEDC0D0C09C00D09E09E0DAD0DADADF0FEFFFFFEFBDF9E9EBCE0DCCC00DECBFFFFFFFFFAFC0A090000000000000000000000090090B9B9BBDB9BDBB9B9BD0990909090090000000000090000000000000000090000000000000000090CBFFFFFCBCBC0D0C09C00E09C0D00D0E0D0F0FFFFFEBFFDFEEBEFFDEDBDADA9FCAD9FCBFFEFFEFFF090000000000000000900000000909B9BDBDBC9B9F9BD0F9F9A9F09A900090009000900000000090000000000000000090000009009000009EFFFFFBCBCCBC0BCC09C9C00D0CD0C9DE9CF0FFFBFFDAFADBDFBFAFACECADCC0D0ECBEFFBFFFFADAC090009000000000000000009009B0D099B99BDB9BE9B9B0D9900900909090000000000000000000000000009000000000000000B009CC9FFFFEFBCFCC90C9C0A9C000CD00F00FCE0DE9FC9EFCBEDADAFAFEFFFFBDBDADADE9C9CD0FFFFFFFEF9C00900000000000000000000090099BCBDBBDBDAD99BC99A009B09000000000000000000000000000090000000900000090009000000BCFBFFBFEF9E9ED0C0D0C09CD0A9C0CD0D0DE9EFBE9EBC9EDEDFFFBFFFFFEFEDADADE9E0FC9EFBEFFFAFBC00000000000000000000000090A09B09C9B9B9BBD9BC999A090090900090000000000000000000000000000000900000000000900009EFFFFFBFE9C00D0C0C9C00AD0C9C9ACADA9E9CFCFC9EF0FBFAFFFEBFEFFBFFFFFE9EDBCBCBDFFFFBFFCA9000009000000000000000090999009B9BC9BD0D0B90B00900090000900009000090090009000900000090000000900000909000900CBFFFFFFCBEDED0E90D0AD0D0C9CACD0DCDE9EFB0F0FE9FBEFFFAFFFFFBFFFAFFAFFFBEDF0FEFBFEFEDA90090000000000000000090000000A990D09BD9B9B9A900B09090090000000009000000000000000000000000000000000900000000DBFFFFFFEBF0D00D0CC00D0C0C9E0C9C9E9E9EDBCF0F9FFEFFFBFFFFFFFFEFFFFFFFBEFFBEFDBEFFFFBFFC0000000000009000000000009090900B9B099A909C9090900000000090090000000000000000000900900009009000900000000900ADBFFFFFFFCF0CF0D09AD0D0F0C9C9CADCF0DEAFFFFEFAF9FAFEFFFFFFFFFFFFFFFFFFFFFFBEFFBFFFEFBF090000090000000000000000000909009CB0D9BDA99A090090090090000000000000000900000000000090000000000000000900090EFFFFFFFBF0F0C0CC0C0C0C0D0C9E9C0E9EBDF0FFAFBFFEFFFBFFEFBEFFFFBFEFFEFFFEFFFFBFFEFFFBCF00090000000000000000000009000090B099A9C9900900090090000000000009000900000009000000000090090090009000900090DBFFFFFFFFFE9E9CB0D0DAD0D0E9C0C9E9C9CE9FFFDFFCFBFFFFFFFFFFFFFEFFFBFFFFBFFEFFFEFFFBFCFB0900000000000000000000000000090090B090B09A9009000000000090000000000000000000090009000000000000900090000B0EFFFFFFFFFFE9CD0D0C0C0D0E0C9C0CDADCFEFBFFAAF0FBEFFAFFFFFFFFFBFFFFFFFFFFFFFFFFFFFBFEFBFC0A0900000900000000000009000900900909A90B00900090090009000090000000000900000000000000900009000A0090090AD0FDBFFFFFFFFFFFE0C0C9CBC00D0D0D0F0CDA0DADADFFBFEFFBFFFFEBFFFBFFFFFFFFFFFFFFBFFBFFFFFFFEBF09000009000090000000000000000000900090909000900000000000000090009000000090000000000000000009090000000DADAFFFFFFFFFFFFF9F0F0C0C9C90C0CAD0DACDE9CFFAFFEFFBFEFFFFFFFEFFEFFFFBFEFBFFEFFFFEFFFFFFFFFC90090900000000090009000000000900009000090909000900090000000000000000000000090090000000900000000009009AFFFFFFFFFFFFFFFFECD0C9C90CAC9E9C0C0D0F0FBCBDFFFBFFFFFFFBFFFFFFFBFEFFFFFEFBFFEFFFFFEFBFAFCBC0B0000000000000000000000000000000009090000000000000009000000000000000000000000090900000909AC9090009EFFFBFFFFFFFFFFFFE9F00DAC0C9C9C0C0D0F0F0DECBFFAFFFEFFFBFEFFFFFBFFFFBFFFFFFFFFFFBFFFBFFFFFFBE9000B00000900000000000900000000909000000090009000090000000000000000900009000000000000900000900009000BFFFFFFFFFFFFFFFFFC0FC0D0D0C0C9C9CAD0CCFADBFCBFFFFFFFFFFFFFEBFFFFFFFFFFFBFFFFBFFFFFFFFFFFEFF0900090000000000000000000000000000000009000000000000000000000009000000000000009000000000900009000090BDAFFFFFFFFFFFFFFBFC0D0C0CB0CBC0E9C0DA9CDACBFCFFFBFFBEFFBFFFFFEFBEFFFFAFFFFFEFFFFFEFFFAFFBDAC009000900000090000000000009000000000000009000009000000900000900000000900009000000000009009000009000CBFFFFFFFFFFFFFFFEDAD0F0D0C0D0C9C0D0EDCB0FDADBFFFFFFFFFFFFFFFFFFFFFBFFFFFEFBFFFEBFFFFFFFEFFE90900090000000000000000000000000000000000000000000000000000000000000000000000000090009000AC0090B000DBFFFFFFFFFFFFFFFADAD0C0C0C0D0C9C0DAC9CADEDAFFFAFFFAFFFBFEFBFFBFFFFFEFFFFFFFFFFFFFFFFAFFFFFAD00009A00000000000000000000000000000090000000009000000000000000000009000000000009000000090909A000000BCBFFFFFFFFFFFFFFF0DC0D0DAD0CB0CBC0D0E9DCB0FDADFFFFFFFFFFFFFFEFFFFFFFFFFFBFFFFFFFFFBFFFFFFFFEFC00090B09090000000900000000000000000009000000000000900000900000900000009000090000900000000009090900BFFFFFFFFFFFFFFEFF0DAC9C0C9CC9C0D0C9CCADEDAFFAFFFFFFBEFFBFFFFFFEBFFFFBEFFFFFFEBFFFFFFFFEFFFF0B0900000000009000000000900000000000000000900000000000000000090000000009009000000000909000909000000DFFFFFFFFFFFFFFFFBCF0C9E0D0E09C0D0F0E9AD0F0FCBDFFFFFFFFFFFFFFBFFFFFBFFFFFFEFBFFFFFEFFBFFBFFF0FC000909000000000000000000000000000000000000000000000000000000000000000000000000900000009000000B09FBFFFFFFFFFFFFFFFFDE9ED0C9C0C9C0F0C0C9CDCF0FCBFFAFBEFFFFFFFEFFFFFFFFFFFFFBFFFFFFFFFFFFEFFFFEFFBDE9000009000000000000000000000000900000000009000000000090000000009009000000900000000000000009000AFFFFFFFFFFFFFFFFFEF0CD0C9C0D0C0D0C9C9CE0F0FCBF0FFFFFFBFFFBFFFFFEFFFFEFFFFFFFFFFFFFFBFFFFFFFBFEFBCA9090000900000000000000000000900000900000000000000090000900009000000009000000009000B0090900009FFFFFFFFFFFFFFFFFFFBF0ADAC9E0DAC0DAC9E90F0F0FCFF0FFFFFFEFFEFFBFFFBFFFFFBEFFFFFFEBFEFFFFFBFFFFFFBCF9C0A09000000009009000000009000000000009000000090090000000000900000900000000000000900000000099FFFFFFFFFFFFFFFFFFFFFCFD0C9C0D0C9C0D0C0CDC9CF9ADAFFFFFFFFFFFFFFFFFFFBFFFFFBEFBFFFFFFFFFFFFEFFFFBEFBCA909000000900000009000000000000000000000000000000000000000000000000900000009009000909009090FFFFFFFFFFFFFFFFFFFFFEBC0C9CAD0C9C0F0C9CBCADEBCFFFFBFFFBFFBFFBFFFBFFFEFFFFFFFFEFFFFFBFFEFFFFFBEFFFFCF90C009090000000000000000000000000000000000000000009000000900009009000009090090000000000000BFFFFFFFFFFFFFFFFFFFFFBCBCBC9C0D0E9C0D0E9C9DE9CFBCBCFFBEFFFFFFFEFFEFFFFFFBFFFFFFFBFFFFFFBFFFFFFFFFFBFAFB0900000009009000000900000000000900000000090000000000000000000000000900000000009000090909FFFFFFFFFFFFFFFFFFFFFFDFC9C0C9C0D0C9C0D0CAC0DE9CFBFBFFFFFFFEFFFFBFFFBFFBFEFFBFFFFFFFEFFFFFFBFFFFFAFFFFDE9E0909000000000000000000009000000000090900009000000000000090000009000000900900009090000FFFFFFFFFFFFFFFFFFFFFFFFADE9CBC0D00D0E9C9C9CFA9EF0FCFFFFFFBFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFEFFEBFFFEFBEBE09000090000000900000090000000000009000000000000009009000000000000000900000009A9000000FBFFFFFFFFFFFFFFFFFFFFFEBCD0C9C0DACDAC9C0E9ED0DCF0FBFFAFFFFFFFFBFFFFBFEFFFFFFFFFFFFEFFFBFFAFFFFFFFFFFFFFFDBDA09000009000000009000000009000000000000000000000000000000000900009000909090000000090FFFFFFFFFFFFFFFFFFFFFFFDFCADAC9C0D00D0C9C9CCBCEB0FEDADFFFFFFFBFFFFEFFFFFFBFFBFFFFEFFFBFFFFFFFFFFFFFFFFBEFEFADBC09000009000900000000000000090000000000000000000000000000000090000000000C90C9C90CBFFFFFFFFFFFFFFFFFFFFFFFBE9D0D0CBC0D0C9CAD0D0C9CDE9FBFBFFFBEFFFFEFFFFFFFBFEFFFFFFFFBFFFFEFFFFFBFFBFEFBFFFBFBDBCA9000000000000900000000000000000000900090000900000009000900000000090000B000B0A00BFFFFFFFFFFFFFFFFFFFFFFFFEFCED0E9C0DAD0E9C0F0ADE9ADE9EDEFFFFFFEFFFFBFFFFFFFFFEFFFBFFFFBFFFBFFFFFEFFFFFEFFFFFFEFE9009000000000000090090000000000900000000000000900000000000000000900009000BC0009FFFFFFFFFFFFFFFFFFFFFFFFFFF9E90C9C0D0C0C9C0D0CDC9EDE9EFBFBFFFFBFFFFFFFFEBFFFFFBFFFEFFFFEFFFFFEFFFFFFFBFFFEBFEFBF9EF0090000900000000000909090900000000000000000000000000000000900000090090009090AFFFFFFFFFFFFFFFFFFFFFFFFFFFE9CF0C9E0C9C9C0DAC9CBC9E9FF0FCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFBFFFBFCFF9EDA0090000000900000000000000000000000000090000000000000009000090000000090000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9CD0C9C0E9CBC0D0E9CFCBC0FFBFFFFFFFFFBFFFFBFFFFAFFFFFFFFFBFFFFFFFFFFFBFFEFFFFFFFFFFBEFFAD090000009000000090000000900090000090000000009009000090000000000909000090FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFBCF0E9C0D0D0C0D0C9CCBCBCFF0FCBFFFBEFFFFFFEFFFFFFFFFBFEBFFFEFFFFEFBFFFEFFBFFFFFFFEFFAFFDBCF0C000000000000900000090000000090000000000000000000000000090009000009090CBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF0C9CAD0E0D0D0ADACBC9CBF0FBFFFFFFFFBEFFFFFFFEFFFFFFFFFFBFFFFBFFFFFFFFFFFFFFEFBFFBFFFBEFBFA909090000090000009000000000000000000000090000000090000000000000090000BBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FD00D0C9C0DACD0C9C0FED0FDE9EBFFFFFFFFFBFFFBFFBFFFFFFFFFFFFFFFFFFFFBFFFFFFBFFFFFFFFFFFBCFCBCA000000000000900900900090000000000000000000000000000900090090000090CBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFC0CF0C9C0D0C90C9CADD09EF0FBFFFFFFFFFFFFFFFFFFFFFFAFFFFFFFBFFFFFFFEFFFFEFFFFFFFFFFEBFFEFFBFCB09090000000000000A000900009000009000000000009000009000000000090900BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF9F0C9E0D00F0CDAC9D0EDE9CFADE9FFFFFBFFBFFFFFFFFFFFFFFFFFFEFFFEFFFFFFFFFFFFFFFFEFFFFFEFFBFFCBC9C0009000090009090900000000009000000000090000000000000900909000ACBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9E0C9C0DACD0CF00DACE9CBCBE9FBFFFFFFFFFFFEFFFFFFEFFFFFEFBFFFFFFFBFFFFFFFFFBEFFBFFBFFFFBFFEBFFFFBE9009000000000000B00900000000000000900000000009000900000000909BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAED0DAC90C90C90CD0C9DE9CF0DFCFBFFFFEFFFEFFFFFFBFFBFFFFFFFFFFFFBFEFFBFEFBFFFFFFFFFFFFFFFFFFFEBCBCBCBC0000000090090000009000000000900000000900900900E0009000000CBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBCD0D0ED0CBCC90E9CA0DE9EFAFBDEFFFBFFFBFFBFFBEFFFFFFFFBFFFBFFFFFFFFFFFFFFFFFBFFFFFEFBFFEBFFBFFFFFBE9AD0900900A9009090000090000000000000000000000090900009000DBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBEDADE0D00D0C90E9C0DCDE9E9CDBCFBFFFFFFFFFFFFFFFFFFFFEFFFFFFFFBFFFFFFFFFFFFFFFFFEFFFFFEFFFFFFFFFBFBFFFF0B009A0990C9AC00090000090000090090090009090000009000C9FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDAD0C9C0DE0D0C9C0DA9E9CBDEBEFBFFFEFFBFFFFFFFFFFFFFFBFFFEBFFEFFFFBFFFFFFFEBFFFFFFFBFFFFFFFFFFAFFFFFBFBFEBC09FACB9E9BC9000900009090000000000900000909000090B0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBCBC9CBC09C0F0C0F0CD0DE9E9F9EDAFFFFFFFEBFFEBFFFBFFFFFFFFFFFFFFFEFFFEFFFFFFFFFFFBFFFFFFBFFFFFFBFFBFFFFFBFB9A9DACB9EBFA9E0C0900000000000900000900000090090CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9E0C9CC9C0D0D0DACF0DE9ECFBFFFFFBFFFFFFFFFFFFFEFFFFFBFFFFBFFFFFFFBFFFBFFFEBFFFFFEFFFEFFEBFFFFBFFFFBFFFFEDABDBFEFDADE9FBCA9A909009090009000009000000F0FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FC9C9E09C09CBC0D0D0DADE9BCF0FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFEFBFFFBFFFFFBFBFDE9FBFBFFBFFCBDAC00A09000000000090009090F0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF9E0D0C9EC9C0C0F0CBCBCF0FCFBFFFFFFEBFFFFFBFFBFFFBFFAFFFBFFFEFFFFFFFFFEFFFFFFFEFFBFFFFFFFFFFBFFFBFFFFFBFFFFAFBEFFFEDAFEBFFEDBF09C0009A90900000000ACBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC0D9E0D009CAD0D0C9CE9E9EDBBCFBFFFFFFFFFAFFFFEFFFFFFFFFFEFBFFFFFFFFFFBFFFFFFFFBFFFFFFFFFFFFFFFBFFFFBFFFFFBFFFFBFBFBFDBDFAFBE9EF0BDAD00C0AD09000D09FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE0D0CDCC9C0DAD0F0DCDE9ECFBDEFFFBFFFBFFFFFFFFBFFFFFFFFFFFFFBFEBFFEFFFFFAFFFFFFFEFFFEFFFFFEFBFFBFFFFFFFBFFBFBFFFEFFAFFBFFFFFF0FCADAF0BFCAACC99AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0BD0C9A009C09C0D0CDADA9E9F0FBFBFFFFFFFFFFFBFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFBFFFFFFFBFFFBFFFFFFFBFFBFFFBFFFFFFEFBFBFBFFAFFFBEF0FFADBFCBFCBFDFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FCCCBCDCFC0BCC9CADADADCFDAFDEFFFFFFFEFFBFFEFFFFEFBFFFFFFFFBFFFFFFBFFFBFFFFFFEFFBFFFFFFFEFFBFBFFFFFFBFFFFBFBFBFFEFFEBFFFBFFBFFBCFBCEBDEBCFAFEBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9A9C00000DC9AC0D0C9CFADADEBDBFFFBFFFFFFFFFFFFBFFFFFEFFFFEFFFBFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFBFBFFFFFFFFFFFFFFBFBFFFFBFFFFFFADBCFBDEBDFBFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9FCD0DEDCD00CC9CBCBCF0DCBCBDEBFFFFFFFBFFFFBFFFFFFFFFBFFFBFFFFEFFFEFFFFFFFBEFFFFFEFFFEFFFFFFEFFFFFFBFFBFBFFFFBFBFFFFBFFFFFFFFEFFEBEDEBCAFCBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E9E0909AC909C90CD0CF0BCBDEBDEBFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFBFFFFFFBFFFFBFFFBFFFFFBFFBFFFFFFFFFBFBFFFFFFFFFFFFFFFFFBE9E9EBCBFDBEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F9C0DCCCC9ECCACC9ACF9EDEDE9EBFFFFFBFFFBFFFFFFBFFBFFFFFFFFFFFBFFBFFBFFFFFFFFFFFFFFFFFFFFFFFFFBFFBFFFFFFFFFFFFFFFFFFFFFFFFFFAFE9EDBCF9EDAFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE9C00B09C909C9BCD90CF09E9FDFBFFFFEFFFFEFBFEFFEFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAFFEFBFFFBFBFBFBFFFFFFFFFFFFFFFFFFDBDE9ECBCE9EDADEFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD0FCDCC0CCD0CC00ECF0CFCBCBE9FFFFFFFFFFFFFFFFFFFFFEFFBFFBFEFFFEFFFEFFFEFFEBFFEBFFEFBFEBFFFFBFFFBFFFFFFFFFBFFFFFFFFFFFFFEFBEF0FCBDE9FCBCFBFFEFBEFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBECF00090B09A090DD09CF9CBCFE9FEBFFFBFFBFFFFFFBFFBFFBFFFFFFFFBFFBFFBFFBFFBFFFFFFFFBFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFBFBCF9EDADA0F0ADA9E9ADBEDBEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED90DCFCCCCCCDECCACDE9CADCB9EDBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAFBFFFFBFBFBFBFFFFFFFFFFFFBFEFFBECB0DCEDCEDCEDCEFCE9EDBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0EC0009090900909CBC0CED0FCFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFBFFFFFFFFFFFFFFFFFFBFFBFBEDBFCFAD9E9DAD9E9DADBC9ECBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9DEDCCDCCCDCCD0DCBDBDADADADEBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE9E9CAC9E0DAC9E0DAC9E9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0CA900B00B00B00E00C0C0DADADBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDEFDEFDEFDEFDFFFFFFFFFFFFFFF000000000000000000000105000000000000A9AD05FE")}, + {empKey(4),ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E506963747572650001050000020000000700000050427275736800000000000000000020540000424D20540000000000007600000028000000C0000000DF0000000100040000000000A0530000CE0E0000D80E0000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00FDB9BFBFFBFB9FBDBBBFDBF9FBFBFBFBFBFBDBFBDBFBBFBFBFFD0000000000000000000000000000000000000000000000000000000000000000BBDBF9BDBF9BB9BFB9FBFB9FB9BBFBBBBFBBFB9F9B9FB9BF9BF9BFBBFBBFBDBF9FBDBF9F9BDBFBFBF9FBB9BDBB9BBF9BBFBFBFBFBFBFBFBFBFBFBFBFFBFBFBBA00000000000000000000000000000000000000000000000000000000000000009BFB9FBB9BBDBFB9BFB9BFFBDBF9BDBDBBDBBFFBFBFBFFBBFFBBF9F9BDB9BBFBBFBBFBFBFBBFFB9F9BB9FBFBB9FBDBFBFBFBF9FBFBDBF9BFBFBFBFBF9FFBFBD00000000000000000000000000000000000000000000000000000000000000000BFB9FBB9FBDBB9BBFBDBF9BBBFBBFBBB9FBBDBB9BFBF9BBDBB9FBFBBFBFBFF9BDB9FB9BDB9F9FBFBBFBFBDB9FB9BB9BFBF9FBFBDBFBFBFBF9FBFBFBFBFBF9FB000000000000000000000000000000000000000000000000000000000000000009BFFBBDBB9B9FBDBDBBFBBF9FBDF9BDBFBDFBBFBF9BFBFFBFBFBDBFF9BFB9BBFBFB9FBFBBFBBF9BDB9FBBBFBBDBDBF9BDBFBFBFBFBFBFBFBFBFBDBFBFBFBFBF0000000000000000000000000000000000000000000000000000000000000000009BBF9BDBF9B9FBBBDB9F9BF9BBBFBB9BBBBF9BFBFB9BB9FBDBFBB9BFB9FBF9BDBFB9F9F9BDBFBFBBFBDBFB9FBBB9BBFBBBDBFBFBDBF9FBFBF9FBFBFBFBFFB0000000000000000000000000000000000000000000000000000000000000000000BDB9FBB9BBFB9BDBBBFBBFBFBDB9BDBF9F9BFF9FBDBFFBFBBFBDBFB9FBBDBFBBB9BFBBBBFBBFB9BDB9BF9BFB9F9FBF9BFFBFBDBFBFBFBDBFBFBFBFBFFBF9BF0000000000000000000000000000000000000000000000000000000000000000009BBFBB9FBDB9BFBBBDB9F9B9FBBBFBB9FBBF9BBBFBFB9BB9F9BBFB9FBBDBBBDBDBFB9FBDB9FFBFBBFBFBFB9FBBBB9BBF9BFBFBFBFBFBFBFBFBFBFBDFBFBFF90000000000000000000000000000000000000000000000000000000000000000000BF9BDB9BB9FB9F9FBBFBFBFBDF9B9FBBF9BBFBF9B9FBFBFBF9F9FBBDBBF9FBFBF9FBDBBFB9FDB9F9B9F9FBBDBDBF9FBFBDBBFBDBF9FBF9FBFBFBFBFBFBBBE00000000000000000000000000000000000000000000000000000000000000000009BFBBFBDBB9FBBB9F9B9BDBBBBBDB9F9BF9FBDBFBFBB9F9BBFBB9FBBDBBF9B9BBB9BBDB9FBFBBFBFBFBBBDBFBBB9BB9BFBF9BBFBFBFBFBFBDBFBFBFFBDFF90000000000000000000000000000000000000000000000000000000000000000000BDBDB90B9FBB9F9FBBFBFBF9F9FBBFBBFBBFBBF9F9F9FBFBDBDBFB9FBBDBBFBF9FBF9BAF9BFBDB9BDB9BDBFB9BDBBDBFBFBFFFBFBFBFBFBFBFBDBFBFBFB9A00000000000000000000000000000000000000000000000000000000000000000009BBBBBFBF9B9FBBB9F9F9B9FBBB9F9BDB9F9BDBBBBBFBB9FBBBFB9FBBDBBDBB9EBB9FBF9BFFBBFBFBFBFBB9BFBBF9BB9FBDBBBDBFBDBF9FBFBFBFBFBFBFF000000000000000000000000000000000000000000000000000000000000000000000BDBDB9B0BFB9F9FBBBBFBFBDBDBBBFBBFBBFBDBDBDBDBF9F9F9FBBDBBDBBDBF9F9E9B9FA9FDB9F9B9F9FBFB9F9BF9FB9BBFFBFBDBFBFBFBFBFBFBFBFBBF0000000000000000000000000000000000000000000000000000000000000000000009BBBDBDB9BDBBB9F9BDBB9BB9BDB9BBDBDB9BBBBFBBB9BBBFBB9FBBDBB9FB9BBBBBF0B9FBFBBFBBFBFBBDB9FBBF9B9BFFFB9FBFBFBFBFBFBDBFBF9FBDF9A000000000000000000000000000000000000000000000000000000000000000000000BDBBBB9FB9BDBFBBFBBDBF9F9BBFBDBBBBFBDBDB9F9FBDB9BDBB9FBF9FB9F9F9E9BBFA00F9FB9F9FB9FBBFBFDBBBFB9B9BFBBFBF9FBDBFBFBFBFBFBFBB0000000000000000000000000090BCBC9F0A9E000000000000000000000000000000009BF9F0FB9BFB9B9F9BDBB9B9BBF9BBFBDFBDBBB9FBFB9BBF9AF9FAB9AB9FAB0B9FBC0000FB9FFBB9FB9F9BDBB9F9BDBFBFBBF9FBFBFBF9FBF9FBFBFBF9000000000000000000000000DA0F0BCBF0FDF9FBFEB00000000000000000000000000009BB9BB9FB9BFBFBFBBDBF9BF9BBDB9BB9BBF9FBB9B9F9E9FB9B9F9FBDAB9DBFA0000000F0B0BDAF9FBBFBBFBFBFBB9BFBDFBFBFBFBFBFBFBFBF9FBDBE00000000000000000000009ABDFDFFBDFF9FAFDF9FDFFAF00000000000000000000000009F9F9A9FBDBB9BBDBB9BF9BF9FBFBDBF9B9B9FBF9ABB9B9FBFA9B0BBDBEB0000000000F0000090B0BDADB9F9B9F9FB9FBBDBBDBBDBFBFBFBFBFBFBF9000000000000000000009EFFDBFA9FDFB9FDF9FAFFBF9F9E9090000000000000000000000BB0BF9B9BBDBF9BBDB99BB9BB9B9BB0BFBFA9A9BDBCBFA9A9BF9FBCA0000000000000B000000000000B0FBFFFBFB9BFBFBFFBFFBFBFBDBFBFBFBFB0000000000000000009BBFFBCBEBDFFFADFEBBFBDF9F9FFFBFFAC000000000000000000000BDBF9BBFBDB9BBE9B0BFBDBF9F9FBDBF09B9BDBCBBB9B9BDBF0A00000000000000000F00000A00000000009A9BDBFFBF9FBBF9BFBDBFBF9FBFBF9BC0000000000000000BEDF9FDFDBDFBF9FF9BDFDFBFFFFDBDFCBDBCB00000000000000000000B9BBDB9BBFBDB9F9F9B9BB9BBB9AB9BBF9E9A9B0F9FBE9A000000000000000000000F000A0000000000000000B0B9FBFBF9FFBFBFBFBFBFF9FBFBB00000000000000BC9BFFEBFBFDAFDFF9FFDBEBDF9E9FFFBFFDBDBCBDAC00000000000000000F0BBDB9FB9BFB9B9BDBBDBDBDBD9E99A9BDBFBF9A00000000000000000000000000D000000000000000000000000B0FBDBBBF9FBF9FBFBBFBFBD00000000000000BCBFFDBDBDE9BF9FADFBCBDBDFBFFBF0BDF9BEBCBDADB0D0000000000000099BD9BBFB9FB99E9BB9BD9BB0B0BB9BFBF0B00000000000000000000000000000000A000000000000000000000000009BFBFDBBF9FBFBF9FBFBDA0000000000000ADBDFDAFFDFBFFDFBDBADBFBDB9FF9FDFFF9FFD9F9A9F0F0A90000000000000BFBBBF9BFBDBFB9F0FB0BADBDBBDBE0000000000000000000000000000000000000F00000000000A0000000000000000BFBBFFBFBFBFBFBF9FA000000000009C9F0FBFBFDBE9FDAFBDFFDBDBCBFF0FFFBF9FF0FBF0F9E9F9F9E90000000000000B9F9BF9B9BB09B9BBDBF9A9BE9A000000000000000000000000000000000000000F000000000000000000000000000000FB9FBFBFB9FBFBFBF00000000000A9E9F9EDFBFDBF0FDBFADBFFF9FDADF9FDFBFBDF9F9F9F90BCBCBCB0000000000009A9FB9FBBC9F0FBDA0000000000000000000000000000000000000000000000000D0000000000000000000000000000009FBBDBF9FBFBDBBF00000000009BCB9BCFBFBDFBDBFBFFDFBD9E9F9BDBBFFBFDFDBFBCB9E9EBDB9F9AD0F00000000009FBB9FB9FBB0B00000000000000000000000000000000000000000000000000000A0000000000000000000000000000000BFFBFBFBFBFBFF90000000000FCBDEFF9FDFEBDAFDFBDFBDAFBF9ADB0FDBFDBFBF0DBDE9F9D0BCBCBDA09E00000000B9B9FB9F0000000000000000000000000000000000000000000000000000000000F000000000000000000000000000000009BFBDBFBDBFBB000000000009BCFBCBFAF9F9FDBBDEBBCBDBD0BDA9F9BDFBEDADBF0B9F9AFBDB9F0B9F09C00000000FBF9FBA0000000000000000000000000000000000000000000000000000000000F000000000000A000000000000000000000BFBFBFBF9FFE0000000000E9BDFBDFDBFFFBFDFBDFDBDADBF0BD00F0B0DBDB0DBDADAD99E9E9BD0F0F0B0000000099BB9F00000000000000000000000000000000000000000000000000000000000D000000000A00000000000000000000000099FBDBFBFBB000000000909FEBFDAFBFDBDF9BFCBFAFBDBC9BCB9F09CB09A9DA09099A9E9B9F0BF9B9E9E0000000BF9FB000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000BFBFBFBFBD00000000000BCBDAFFFDFAFFAFFDBF9F9C9E9BC90F09FBDBE9DA9BDBFA9F090F09F90FCB90D000000B9BB0000000000000000000000000000000000000000000000000000000000000F0000000000000000000000000000000000000BFFBDBFB0000000000C9FFFDBDBFBDBDF9FAD9F0FBF9E9FBF9FF09A909A9C9A90D0BBDB9F9ADB9B9E9AD000000FBC0000000000000000000000000000000000000000000000000000000000000F000000000000000000000000000000000000009BFBFBFC00000000B0A9BFFEFFDFFFBFF9FBEFBD0F9FBDBDA99F0DBDBDA9F9E9B9C9ADB0F9ADADA9F0AC0000B9A000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000FBFBDBA00000000009FFE9F9FAF9FDBFF9F9F0FBFBDFADADBE9FBCBDBDF9E9BCF9BDBCB9E9BDB9E9BDA00000F000000000000000000000000000000000000000000000000000000000000000E00000000000000A00C090C000000000000000009BFBFBD000000009CFEF9FBFFFDFFBFD9E9E9F9FBDEBDBF9FDBC9FBCBF0BDBDB9BCBCB9F9BC9ADB9CA90000B00000000000000000000C0000000000000000000000000000000000000000000F0C00A000000A0900A000A0000000C0000000000009FBDA000000000B9BFFFCF9FBFBDBAFBDBFFBDFF9DBFDF0BFBFDBFD9FDBDADADB9B0F0BDBF9ADAB9E0000000000000000000000CA000000000000000000000000000000000000000000000F0A000C00AC090C0000A000AC0AC000A0A0000AC000BFBF00000000A0FDBCBFBFFF9FFDBDAF9BDFBCBFBCBBDFD9F9FDBFDBFDBDBDADF9F9F0F0F9F9DE99E0000000000A000000C0A0000000E0000000A0C0000000000000000000000000000009000000A000000A00C9C0C900090A0900C9AC00000E9AFB000000000DAFFFFDFF9FFF0FDBDBFFFBDF9FDBFDBFBFFFFBDFBF9F9F9F9B0F0F9F9F9FCBB9FA900000000000000000000000C0A000CA000C000A00000000000000000000000000000E000A000000A00000A0A0A0C0AC00C0C00000B00A000DBF000000000BFFADBFE9FFDBFBBEBFFCBDFBFFBFDBDBFDBDBDFBFDF0F9FBCBDBDBCB0B0FBBCDAD00000000000000AC0A000C0A000000000CA0000000000000000000000000000000000F0000000000000000000000A000A00A0A0AC00C0C90A09BE00000000FBDFFF9FFBFADBDF9F9FBFBDF9FDFBFFDBFFFFFFFDBDBFDE9FDBCB9F9F9F9FDBBDBE000000000000000000A00000000CA0C00000AC000000000000000000000000000000F00000000000000000000000000000000000A00A00C00C0B000000009EBFCBFBDFDFBCBFFFFBDFFBFFBFDF9FFDF9FFF9FBFFDBF9F9ADBDE9E9E9B0FCBBD9F0000000000000000C000000AC00000A00AC00000000000000000000000000000000B0000000A00000A00000000000000000000000000A00A00C00000000FDFFBFDFFBE9FBF9F9FDBF9F9FDFBFFF9FBFF9FFFDF9FDBF9FDBCB9F9F9F0F9BDDAF9E00000000AC00000000E00C000A0000C0C00A000000000000A00000000000000000C0000000000000000000000000000000000000000000000A00000009BFFADFFBCF9FBFDFBFFBF9FFFFFBFDF9FFDFDFFF9FBFF9FDBCB9F9F9CBDADB9FFBF9E9000000C00000CA0000000A0000C0E00A0A0C000000000A00000000000000000000F0000000000000000000000000000000000000000000000000000000FBDFFBDFBFFBDFBFF9FDFFF9F9FDFBFFDBFFBF9FFFDBDBE9F9FFDBCBF0F9F0F0BC9FBDF0000A0000A00000E0000000E000000C00000000A0000000000000000000000000B0000000000000000000000000000000000000000000000000000000FEFBDFF9F9FFBDF9FFBF9FFFFFBFDF9FBDBDFFF9F9F9FDBDBD9ABF9DBDBCBDBDBFBDBBCBC0000000000000000E00C000A00A000C00A00000C0009000000A000000000000F000000000000000000000000000000000000000000000000000000ADBFFEBFEBFF9FFBFF9FFFF9F9FDFBFFFDFFF9F9FFFFF9F9FDAFDF9FBCBDBDBCBD9FEDFBDA00000000000A0000000A00C0C0C0A0A000000000A00E00A00000000000A0000C0000000000000000000000000000000000000000000000000000009FFDF9FDBDF9FBFFDBFFBDBFFFFFBFDF9FBDBFFFFF9F9FFBCBF9F9FE9F9F0F0F9EBF9FBDAD00000A00CA0C0A0C000000000A00C0000000A000000000C00A000A000000000B000000000000000000000000000000000000000000000000000000BBFAFFBFFFBFFFDBFDBDFFFDBF9FDFBFFDFFDF9F9FFFF9FDBD9F0FDBDBCBDBF9F9F9FBDFBDA0000C00000000000AC0CA0A000A00000000000000000000000C00000000A00F000000000000A000000000000000000000000000000000000000000FFDBDE9FBDBDBFDBFFBF9FFDFFBFFDBFBFBFFFFFFBDFF9FFBFBDBF9FBDBE9FBCF9E9FBDFBDBE000000000C000000000C0C0C000000A00000A00A000A0C0000000A000000F0000000000000000000000000000000000000000000000000000009FBFFFBFBDFFFFFBF9FFFFFBFBDFDBFFDFDFDBFFDFDFBDFBDFDFBDADBCBDBDADBDBFDBDBFDAD00000AC000A00E000A009A0B000A900000000000C000000A00A00000000000000000000000000000000000000000000000000000000000000009EFDE9BFDFFFBF9FDFFFDBDBDFDBFBFDFBFFBFFDBFBFBDFBDB0FBDBDBDBDADBDBDBF9F0FFDBF9E000000A0000000E00CA0C0C0E0C0E00CA0B0C0000000000000000000A000F000000000000000000000000000000000000000000000000000000BFBFEDFBF9FDFFBF9FBFFFFFBFFFDFBFDBFDFBFFDFDFBDEBDF9DAF9F0F9F9EBDBCDBFF0BFF9E9E0A0000000C000000000A0A000A00CA00C0A0A0A0A0A0000000C00000000F0000000000000000000000000000000000000000000000000000000FDBFF9FFFBFBDFFFFDF9FDBDBDBFFDBFDBFDF9FBFF9FBDFBFFBD9E9F0F0F9DBFBFDBDF9FFF9F90C000C00A00A00C0A0C9C0BC0D0A9CA9A0C0D0C9C0D0E0E00A00A000000B0000000000000000000000000000000000000000000000000000009BFE9BFF9FDFFFBDBFFBFFBFFFFFDBFFFFFFBFFFDF9FFDBDF9DFFBDBDBDBDAF0FDBFFBDBDBFF9EB000A000000C00A00CA0A0C0A0AC0A0C0CB0A0A0A0A00009C000C0A00E0C000000000000000000000000000000000000000000000000000000FEF9FDF9FFBFF9FFFDBF9FDF9F9FBFFDFBDFFDBDFBFFDBFF0FBA9CBF0F0F0BDBDBF9FDBCBFDBCBD00000000000000009000D0A90C9AC90A00C0C00C00C0BCA0ACA0BC0D00B000000000000000000000000000000000000000000000000000000BDBFFBFFBDF9FFBDBFDFFBFBFFFFDFFBFFBFFFFBFFDBFFDBF9FDFBD9F9F9BDBDBDFFBFF9DBFFF9E9AC00AC0A00B000A0000A00C0A000AC9E0B0ADA0E9AC000C900C00A0A0F000000000000000000000000000000000000000000000000000009EBCBDF9FFBFF9FFFDBFDBDFDFBDBFBDFFDFDBFDFDBFDBFFDBF9BDBEBCBCBDADADB9FDBFBE9FF9F9E90000000C000C000E00000A000C0000000000090000ACA0AC9A0C00C0F00000000000000000000000000000000000000000000000000000E9FFBFBFF9FDBFF9FBF9BFFBFBDFFDFFFBFFBFDBFBDFBDF9FDAFCBD9DBDBDAD9F9EFBFFDF9BDBFFDBCA0000000000A0000000000000A000A0C0AC0AC0AC9009C00AC0B0E9A00000000000000000000000000000000000000000000000000000A9BF9FFDF9FFBFF9FFFDFFDBFDFFBFBFBDFF9FDBFDDAD0F99E99DBDEBFADBCB9F0BF9FDFB9EDFDF9ADA9C00A00A00000000000A000A00000000000000000AC0000000000000F00000000000000000000000000000000000000000000000000000FDEF9FBFFF9F9FFBDBF9FBDBFBDFDFDFBDBEDBF9FBDBD9AD99E9099C99E9BDADBD9FFBDFF9BABFFDBF009000000A000000A0000000000A000A00000000000A00AC00AC0000F00000000000000000000000000000000000000000000000000090BBDBFFDF9FFFFBDFFDBFDFFFDFFBFBFFDEDBD0DFDFFFFDDAD99DBC9BC9F0DBDADBF9FFBDFFDDBDBFD9FAC00000000000A000000000000000000000A0000000000009000A0000000000000000000000000000000000000000000000000000000ADEBFDBFBFF9F9FFBDBDBFBDBFBDFFDF9F9D9FDBFFFDF9F9D9AD0D9BC9A9DBCB9BCBDBDFFBDBBDBFCBE9DA0000000000000000000000000000000A0000A00000000A0000000F0000000000000000000000000000000000000000000000000009FF9FDBFFDF9FFFFBDFBFFBDFFBDFE9FBD0F9E99F9C9090D0B0D09A0C9C9D00BDADBDBFBDBDBFCDBDBF9DA000A00000000000000000000000000000000000A000A0000000000A000000000000000000000000000000000000000000000000000ADBFBFFDFBFFBDBDFFBDF9FFBDFFBDF0DBD0D9FD09B9FDB9BDFBFBDF9AB00BD09DBDBE9FFFFBDBBDBDBEBDBC0000000A0000000000000000000000000000000000000000A000D0000000000000000000000000000000000000000000000000009ADFDBFBFDF9FFFBDBDBFF9FFBDFF9DF0D0B09BFFDFFBFFFFFFFDFBFFDFF009E909BD9F9BDFFFDEBDFD9F0B0C000000000000000000000000000000000000000000000A00000F000000000000000000000000000000000000000000000000000FFBEBFDFFFBFF9FFFFAD9FFF9FF0DF0900BDFFFFFBFFFFDBFFFFBFFFFBF0F0090DBCBFDFDBF9FB9F0BBF0FDB0000A00000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000009BF9F9FDBFF9FF9FF9F9DFAFF9FF9F9090F9FFBFFFFFFFFFBEFFFFFFFFBDFFB0900B09BDB0BFDFBDFDBDEDBDA00A00000000000000000000000000000000000000000000000000D0000000000000000000000000000000000000000000000000ADAFFFBFF9FFDBFBDF9FB9F9FFBC90E9EF0FBFDFFFFFBFFFF9BFFFFFFFFFFFC9E090DBCBFFDBF9FF9ADB9BDA9FC000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000BDBDFF9FFBFFDFFBFBDE99F9EDBD90B9FF9FFBBFFFDFBFCF0DB9F9BDBDADBF090C0099099F9FF9FFDBCF0FDA9AC0000A000000000000000000000000000000000000000000000F0000000000000000000000000000000000000000000000000BCBFBF9FFFDFBFBDFDF9BDBBF9F00A9FFF9EFFCF9E9B0D9B9DBC90C90DADBC900A909CADBF0FBDFBDBDB9F9AD9F0A000000000000000000000000000000000000000A00000A0000000000000000000000000000000000000000000000000000BDBFDFDFF9FBFDFDF9F9ADAFC9F090DFFE9E9B990BD90D9E9CB09AD99E909090E9909A99BE9DF9F9F0FA9DADF9E0000000000000000000000000000000000000000000000000000F0000000000000000000000000000000000000000000000000ADBFBFBFFFDBFBFBFBDF9D9B90C00B09F099C9E9DADF0BDDBDFDDBAD9BDADBF9F0F0DBC99FB0BDBDBD9EBDA9E9BC00000000000000000000000000000000000000000000000000E000000000A000000000000000000000000000000000000009FADFFDF9FFFFDF9FDFBDBAD0F99BD0F09FE9BF9F9F9FDFBFDBFBFDFBCF9F9CBDAD9B0DBDA9DFDBCBDBD90BDA9EDA90A0000A00000000000000000000000000000A0000000000009000000000000A0000000000000000000000000000000000CABDFBDBFFFBDBFBFFBF9E9DB9ADADBF9FF9BFDFFFFFFFBFDFBFDF9F9FF9F0FBDADBC9F09A9FB9ADBDA9AF9DA9F9A9C000000000000000000000000000000000000000000A000000E0000000000000000000000000000000000000000000909A9BDFBFFFFF9FFFFDFBDF9F9BCBDBDBE9FF9FFDFBF9FDBDFDFBFDFBFFFDBDADF0F9DA9E9BF9DF0FDBDB9FD99A9E9E9CA0000A00000000000000000000000000000A000000000A0000900000000000000000000000000000000000000000000BFFFFBFDF9F9FFFBDBF9FF9E90FBDBCBDFFDAFDBFBDFFBFFBFBFDFBDE9F9ADBDBDBD0BDB90D0FA9F0B9F0F9BCBD990F0BCB00000000000000000000000000000000000000A000000000E000000000000000000000000000000000000000009ADBF9F9EBFFFBF9FDFBDFF9E9FBD9DADBDB9FBDBFDFFBDFDBDFDFBF9FBDFFFFF0FB0FBFCBCB9BD9F99F9E9F9E9BDADA9AD00CA00000000000000000000000000000000000000000000000B00000000000000000000000000000000000000000ADFFDFEFFDF9FDFFFBFDBF9F9F9C9ABD9F9EF9DFF9FBDFFBFFFBFBDFFFDBF9F9DB9DF9C9BD9DE9A90F0BDBDBDBDBC9BC9CBCB0D00000A0000000000000000000000000000000000A000A00C0000000000000000000000000000000000000000BDFBFFBF9FBFFFFBF9FDBF9F9F9E9B9D9BE9F9FBF9FFDFFBDFDBDFDFFBDBF9FF9FADE90FBDA9A9BDBF9BD9F9E9F0F9BCB0B0BC9A00A0000000000000000000000000000000A000000000000B0000000000000000000000000000000000000009FBFFFFDFFDFBF9FDFFBFD9F9F0F9BDA9AD9F9F9E9FDBFBDFFBFFFBF9FDAD9F09E9DB9BF90BDBD9DAD0F0FBDBDBDBDAD9BC9E00FC00000000000000000000000000000000000000000000000E00000000000000000000000000000000000000ADFDFBFFBFBEFDFFBF9FDBEFF0FBDFCBDF9A90F9F9FBFDBDB9DF9F9FFFBDB9E9F99E9FC90FD09CB0BDB99F9EDBFDBDBDADCB09CB0BC0000000000000000000000000000000000000A000B0000900000000000000000000000000000000000009FFBFBFDFFDFFBFBDFFFFBF9F9F9DBB9F9AD9DB9E9F9E9FFFFFBFFFFF9DFBCBBDADA990B9F9A9F9DBD0DAF9DBBDBCBD9F9BBC9EBC0DA00000000000000000000000000000000000A0000000000E000000000000000000000000000000000000BBFBDFFFFBFFBDFDFFF9F9FDF9FBFFBDFDBDF0B0F9F9F9FBDBDBDBD9F9FFADBDCBF9F9E09C909F0BAD0BBD9FFBDFDBDAF0F9C9A900BA00A000000000000000000000000000000000000000000009000000000000000000000000000000000000DFDFFBDFFFDFFBFFFBFFFFBFBFDBDBDF9BF9BF9D0BDBDF9FBDADF9FB9F9F9FDFBF9F0F9F0B0F09D9DBBD9FBDBDF9BF9F9F9E9AD0F00D000000000000000000000000000000000A0000000A00A00E000000000000000000000000000000000000BFBFDFFBFFBFCFFBFDF9F9FDF9FFFDBFFDFFDFBF9CB0BDBDFFF9AD0DADADFBF9FDFF9F09F0F9FA9A9D0BC9FBDBFFDBF9F9F9F9AD0ADAD00A00000000000000000000000000000000000A0000000B000000000000000000000000000000000009F9FDBFFFDFFFBF9FFFBFFEFBFFDBDBDBDBF9F9F9FB9DF9FBC99FDBFFBDBFBDFFFBF9F0F9E99E99C9F0BDFBFDBFDBFD9EDADADADA0D00A0000000000000000000000000000000000A00000090C00C00000000000000000000000000000000000BFFFBFF9FFFF9FFFF9FFDBDBDBDBFBFFFFDBFFF9FD9E9A9F9FBFFBFDBDFFFDFFFFDFFF9F090FB9E9B0F9F9FDBFDBFDBFF9F9F9F90F00BC00000000000000000000000000000000000000000A00A0B00000000000000000000000000000000000FDBDFFFFFBFFF9FAFFFBFFFFFFFFDFDBDBFDF9FFFBF9F9CBD9F9FDBFFFF9FBFFDBF9F9E9F0F0DE90DB9F9FBFDBC90BDB9F9F0F0E90BC0900000000000000000000000000000000000000A0000900E00000000000000000000000000000000000BFFBF9FFFFF9FEFDFBFDBDBF9F9FBFFFFDBFFFBFDF9F9B9EB9CFBFDFBDBFDF9FFDFF9F9F0D0B99E90F9FFD0900BCBC0D0BCBD9F9E90B0E0E0000000000000000000000000000000A000000000000900000000000000000000000000000000009FBDFDFFBFDAFBDBFFCFFFFFDFFFFFDBFBFFDFFDFFFFFFDF9DA9BDFBFDFDAF9FE9FBDFCBCB0BCBC09F9E90BCBDBDBDBF0BC90FADA9CAC09000000000000000000000000000000000000A000A00A00E0000000000000000000000000000000000FDFFBFBFFDFBDFFFFFBFBF9FBFBDBDBFDFFFFBFFBFF9F9F99ADBDF9FDBEBD9FF9FFDBDBDBC9C90B9F0F9CBDBDBDBFDF9FFD0F09DBCB909E0000000000000000000000000000000000000000000000B0000000000000000000000000000000000BFBDFDFDBF9FFF9EBDFFDFFDFFFFFFFFBDF9FFDFFDFFFFBFFDBCB0F9BDBDBFF9FBDBCBDADB09E9C9AD0F9E9F0FBDFBFFD9FBDE9A0BCCA00000A000000000000000000000000000000000000000000C00000000000000000000000000000000000FFBFBDBDADBFFFFFBFBEBFFBDF9FFBDFBFFFFFFBF9F0FDBDBDBDBCF0FDBDADFDFBDBDBDADA90B0F09BCBDBDBDFBDF9FBE9F0BC9C0B9DAC0BC0000000000000000000000000000000000000000000F0000000000000000000000000000000000BDFDFFFFBDBFFBF9FEDFDFDBFFFFFDFFFDFFF9F9CDADF9EDBCBDADB9F9F0FDBFBDFBFDADBC9E9C909E9F9F9FF9BDFBFF9FF9FDBF0BCA0090000000000000000000000000000000000000000000000A0000000000000000000000000000000009FFBFFBDBDBFFFDEFFFBFBFBFDFBFFBFFFBFB9FCFFBF9BDFBDFFF9F9F0FBFDBF9DF9EDBDBC9A9DA90F9F9FF0F9FFFBDF9FF9FFBDCBC090F0ACA0000000000000000000000000000000000000000000D0000000000000000000000000000000000BDF9FFFCBDFDBFBFBDFFFDFFFFDFDFFBDFCDCBBF9FDFFFBDF9FFFDEBDBDFBFDEFBFDBDADA9D0E9E9BDBE9BDBFDBDFBFF9FF9BCBBDBDE0000000000000000000000000000000000000000000000000A0000000000000000000000000000000000BFFFFDBDBFBFFDFDFAF9EBF9FBFFBFDFF0BBFDF9F9F9F9FBFBDBFFFDBDBCDBF9FDBFDBDBDE9B909E9E9DFDBDBFFBDF9FF9FFDFDE9EBFCB0F000000000000000000000000000000000000000000000F0000000000000000000000000000000000FDBFBFFF9FFFEBEBFDFFFDFFFFFFFFFE9FDF9F9FFFBFFFFDFDFBDBDFEBCBBDBF9FFDADADA90C0F99F9FBFBFFF9FFFBFF9FF0FBDBFDBDF0C00000000000000000000000000000000000000000000000000000000000000000000000000000000BFBFDFFF9BFFFBDFFDFBDBFFBDBDFF9E9FBF9FBF9FBDFDBDBFFBDFFBFFD9F9CBDFFDBF9F909E9B0BE9FBDBDBDBFFDBFDBFF9FBDBFCBCBE00AC00000000000000000000000000000000000000000000F0000000000000000000000000000000009FDFFFF9FC9F9EBDABFEFFBDFFFFBEF9FDF9F9FDBDFFBFFFFBDFFBDFF9EBCFBCBF0FF9F0FDA90C9D9F9FFFFFFFDBFFFFFDBFBDFCFBDAD9E0900000000000000000000000000000000000000000000000000000000000000000000000000000000BFBF9FFBFB0F9EFDF9FBDFFBDFFFD0FBFFFFFBFFBFDF9FBDFBFDFF9FFFDBFDBDFF9FFDB00DA9ADADAF9F9FF9FFF9FDBCBDADBBF9E9FACBC0A00000000000000000000000000000000000000000000F000000000000000000000000000000000FFDFDFFDF9DBC9B0BEFFDEBDFFBFDBFFDFBDB9F9FDFBFFFFFBFFBFBFF9FFFCBFBDBFDADFDBAD0DABDBDFBFF9FF9FFFBFFBFDBCF9E9E9DBC00000000000000000000000000000000000000000000A00A000000000000000000000000000000000BDFBFBFFBCADABCFDF9FBFFFBFFDAFDBFBDFFFFFFBFDFBFDBDFDFDFF9FFBCBDBCBFDBFF0B0D00BDDBDBBCF9FDAFFBDFF9F9ADB9E9F9EBE9009C0000000000000000000000000000000000000000000D000000000000000000000000000000009FBFFFFF9F99A9CB0BFFFFBDFFDBFDBFFDFFBDBDBDFBFFDBFFFBFBF9FFBDFFF0DBDBFDBDBCB00D0BADADDF9EBFDBDFBD0F0FDAD0B9E9F09EC00A0000000000000000000000000000000000000000000E000000000000000000000000000000000FDBDFFFFFFF0A9ED0FBFDFBFBFFADFFFBFBFFFBFFBDFBFDBFDF9FFF9FFBBDFCBCBFDBCF9F0DAADDBDBA9BF9F9FFBDFFBDB0BDAD0F9E9F09A0000000000000000000000000000000000000000000000B000000000000000000000000000000000BFFFBDFFFFAD009ABFDEBFDFDFCDAF9FFDFDBDFDBDFBDBFFDBFF9F9FBDFDBFF9FBDBFFBDADA9DABCBDFFF9FFFFFFEBDFADFC09B0BCBDADAC0000000000000000000000000000000000000000000000C00000000000000000000000000000000BFDBFFFF9FDF09AFADFFBFEFBFBFBFFFFFBFBFFFBFBFFFFFDBF99E9FBCBDBEF0DADFFDBDE9F09E9DFFF9F9EFDBF9DBDF0FDA9BF0FC9FADA900000000000000000000000000000000000000000000000B000000000000000000000000000000009FFFDFFFFFBDE000DA9FFDBDFFFC9F9F9FDFDBDBDFF9FFFFBDBFFBF9DBFFF9BE9FB0FBFBDB09E9EB9F0FBD9FBCFFEF0BD0BC9C9F0BF0DBC000000000000000000000000000000000000000000000000F000000000000000000000000000000000B9FABFFFFFADA0F0DAFFFFFBDBFEFFBF9BBFFBFBDFFFFBDFFCBD9FAF9F9FFDF0DFF9FDF9EFDAD0DAD90CAF9E90909CA9FCB0BCBD0BF0CBC000000000000000A0000000000000000000000000000000E000000000000000000000000000000009FF9FDFFDBFDF0000BF9EFFBFEFC9BDE9EDF9FFDFFBDBDFFADBDBE99DFFFF0F0FFBDFBF9E90000B0F0ADB9C00E0E0009CA90D0BC0F09BF0000000000000000000000000000000000000000000000000900000000000000000000000000000000BD09FBFBFFCB0000BF0FFBDFFDBFBCFBFB9AFFDBFBDFEFCBDBCBC99EB09B9FBDA9CFBFDA00090BCD9FDB0CF0F0900E9CA9C00BD0B09EC0C000000000000000000000000000000000000000000000000E000000000000000000000000000000000FBF09FFDBFBE900C0FBFFFFBFF0DBFDFDFDBDADBDFBDBFDA9F9F0F99FEDE9FE9EBFDFFD09ADADBE9BFFFB0CADE0000AD09A9CAC9CA99A0E00000000000000000A00C00000000000000000000000000F00000000000000000000000000000000BDF9F09BED0D0E00B00FDBEDFBFFE9FBFAF9AF9FFFCBC000C0CBCB0F09B9BDADBDFBFFBFFF9F9E9FFFFFFDA900000000000DA90B09CAC0900000000000000000900A0A0000000000000000000000000B000000000000000000000000000000000FBFCBC09A9A900000BDAFDBED009EBDF9FFD9AD0A0C0A90A0BDF0D0BCF0DAD0FFBDFBFDBDE9FBDADFBFFC0EDAD0AC09ADA00CBCBCB000E00000000000000090C0090000000000000000000000000A0C000000000000000000000000000000000BFCB009AD0000000ADAF9FAD0BFFBDE9FF0BED0BFDB09CAD0DA0DA9E9B9EBCBF9FFFDFBFFBF9EDBFFFDB0F90BCAD09ADAC9CBC09000DA00000000000000000A00E00000A0000000000000000000000B0000000000000000000000000000000000FBC0000000000000000A0FBFCFFCFBFE9FD9BFDFFFFE0000AD009F9FCF9CBDFFFBFBFFDBD9F9BDBDBFEDACBCBDACBC090A909E00E9AC9C000000000000CA00090000000000000000000000C0A00C0F000000000000000000000000000000000009A0000000000000000CBDEBFEBF0F9FFAFE90BFFFF0F00000BCBCF9BCB0FBF9FDFF9FFFFADF9E9F0F9FF0F0CADBCBCAD0E0000F0F9A0A0000000000000909E000000000000000000000A000000A0C00000000000A0000000000000000000000AC000000000000000BDBDAFDE9FFF0FDADF9EF9FBFDFDADADBCBCB9EDAC9FDFFFBFDFF9BDF9BE9F9BDA9CE9E9C0000000090F9F9F0C9C000000000000000E0000A00A00000000000000C00A0CA000B000000000A00000000000000000000000090000000000000009E9EBFFFBFFDADEBFF9E99EDFFEFADF9F0F9F9EDA99FBFBDBDFBF9FDFBFFDBF0F0F9F9BC0A00F0DBCBCF0F9E9EB0A00000000000009A000000000000000000000A0A0D00000C0F0000000000000000000000000000000000000000000000000009FFFFCFF9EBFCBCF0F9EFDBE9DBDAFD0F0E0F09CBE9F9FFFBFDFFBFBDBDBDF9FF9F0BCB9D9F9E9AF9FBF0F9E90C900000000000000C09A00000000A000000000000A0AC0A0A0E000000000000000000000000000000000A0000000000000000A00BCFBFEFDFBFCBCF0E9EADBEBCFDAE9F9F00E9BDFBDF9FDFBF9FDBDFFBFBFF9F0F9F9EFADADBC9AD0DADA09E9A0E000000000000A900000000000000000000000C000A00D00D000000000000000000000000000000000000900000000000009CBFFBFDBFFADBBCBDFBDFDFEDCB9AD9E0C00F0FCB9FFBFBFBDFFFBFFBDFDFDBFBF9E9E999BDADBC9A9BF09F0000090000000000000000000AC00000000000000000AC009E0A0B00000000000000000000000000000000A0000A00000000000000BDADFEFDADFCFFFAFDEBFFFDBFCEDAC90BDBDB9FFF9FDFDFFF9FFDBDBFBFBFDFDF9FB9FEF0F9ADADAC09E0E0DA9E000000000000000000000A00000000000000A090ACA000CE000000000000000000000000000000009E00000000000000000BCBFFBFBFFBFBDBDFCBDEF0FFFCBDADBCBC9A9FFF9FFBFFBDBFFDBFFFFFFFDFBFBFFDFF9F9F9FF9F9F9BC909A0C0000000000000000A000000000A000000000000CAC90C0CA0F00000000000000000000000000000000009E00000000000000009CBCFDEDBDEDBCBDBFBD9F9ADBCBCB0BDBFDF9FFFBDF9FFFFDBFF9F9FBDFBFDFDBFBDFFFFFF0FF9FEDBADAC9A9E9E0000000000CA00000A000000000A0000000000A0A0A900B000000000000000000000000000000000A00000000000000000A0BFFBFBFEFBFFBFBCBCBE9EDADBDBDFDBF9FBFF9FDFFFF9F9FFDFFFFFFFFFFBFFFDFFBDBDBDFF9EF9FCDADBC9E9C0000000000B0000000000A0000A00000000A0000C0900E0E00000000000000000000000000000000AD00BC00000000000000DADAFDFFBDF0FDEDBDFBDBFBDBFBDFBFFFFFFDFFFFFBF9FFFBFBFBF9FFFF9FFFFDBFBDFFFDBF9FF9E9BBF9ACB00A000000000000900000000000A0C9000ACA0000E0A0E0E00F0000000000000000000000000000000000A00000000000000009ADBDFFADFBFFBFBFFBCFFFDFFDFFFFFFDFFDFBF9FBFDFFF9FDFDFDFFFDFFFFDBFFFDFFBDBFDBFCBDBEDC9EDBCBD0000000000000000A00000000000ACAC0009CA009C0009A0F00000000000000000000000000000000B0C0009A00000000000000FB0FFBFE9FFDF9FFBDBFFFBFFDBFFFBFFBFFFFFDFF9FBFBFBFBFBDBFBFFFFFFFBFBDFADBFCBDFADBFBE9E9ACAC000000000000A000000000000000000A0A0000A0ADAC0C090000000000600000000000A00000000000A9A000000000000000A90FFBDEDBFF0FBE9FDFFFBDFFBFFFDFFFFFFDFFBFBFFFDFDFDFDFFFFFFDFBFF9FFFDFBDFDFBDBFDBCBDBF9EF90B00000000000000000000000A0DA0A000C0CA00C00000A0AE00000A000000A000A0000000A000000A9AD00C000000000000009CBDBCFFBFDFFFFDFBFFBDFFFDFFFFFBFDFFFBFDFFDF9FBFBFFBFBDBFDFBFFDFFFDBFBDFBFBDFE9ADF9ED0F090AC0000000000000000000A0000000D0C0A90A0C0A0AC0F0C0F00000000A00000000000A00000000000000A0A000000000000000A9EBFF9EFFBF9FBFFFDFBFDFBFF9FFFFFBDFFFFFFFFFFDFDBDFDFFFFBFF9FBFBFFDFDBF9FDAF9FFCBDFBF0DAC90000000000000000000000F00000A0AC0E0C0A90C9A000B0F0000000000A000000000000000000000E00C90000000000000000009E9BF9FFFFFFFF9FFFFFBFFFFFFDFFFFFFFDBF9FBDBFBFFBFBFBDFFFFFFFFDFBFBFDFF9FDBF9FBEB9E9FA9A0000000000000000000B0C000ACA0009A00A90CA0A0CAC0C0B00A000000000A00000A000A00000000909A0000A000000000000009E9EDFFF0FFF9FFFFBDFFFFDBDFBFFBDFFFBFFFFFFFFDF9FFDFDFFBFDBFDFFFBDFDFBF9FFBFCF9F9DE9FC9C0000000000000000000000A000009C9E0C0F0CA00C0CB09A0AE0000A00AC00000000000000000000A0C0AC9A0000000000000000B0A9FBE9FFF9FFFFFFFFFBDFFFFFFFFFFFBFDFFDBDBDBFBFDBFBFBDFFFFFBF9FFFBFBF9FF0FDB9E9FEBDA9A00000000000000000000C0000A9C0A0A000A00A0CB00A0CAC0CF0000000000A0000A0000A0000000000A00000C09000000000000000D9E9FFBFFFFF9FBDFFFFFBFFBFFBDFFFDFFFBFFFFFDFDBFFDFFFFFFF9FFFFF9FDFDFFDBFDBEF9F9BDA9C0000000000000000A00000AD0000A0C00E9E9E0CB0CA9CA000B0F0000000000000000000000000A000009E9A000A00E00000000000000A9E9EDBDBFFFFFFF9FFFFFDFFDFFBDBFFBFDBDFBFBFFF9FBFFDBF9FFF9FFFFBFBF9FBDBBDBDADE9FDA900000000000000000000A00000C009A900A000B0CA0CA0DACAC0900A00000A0000A0000A000A0000000A000C0B000090009000000000B0F9FBFFFFDBFDFBFFFFDFFFFFFFFFFFF9FDFFFFDFDF9FFFDFDBFFFFFFFF9FFFDFFFFDEDFBDADBFF0A9C0000000000000000000AC90A0A0A0E0CAD0F0E0CAD0A0CA0900AE0000A00000AC000000000000C00000009A9AC000000A00A000000000C9AFE9FBFFFFBFFFFFBFFFBFFBFFDFFFFFFBFFBFFFBFFDBFBFFFFFBDBFFFF9FFF9F9FBB9CBDBDB09D0A0000000000000000000000C00D00000B00A0E00B00A9CB00E0BCF000000AC00000000A0C00F000000A00AC0C900B0A000C00000000000BCBDBFCF9FFFFFDFFDFFFDFFFDFBFFF9FFFFFFDFBDFDBFFFFFFBDFFFFF9FFFFBFFFFBDDFBCBDADF0A900000000000000000000000A0000BCBC0E0C00BC0E0CA00CB0E0AF00000C00000000A00900000A00A000C90A00A0C090090090000000090B0F9FBFFFBDFFBFBFFFBFFBFFFFFBFFFFFFDFFFFFBFDFBDFFDFFFFF9FFFFFFFDFBDFBE9F9FF9B0D0C9A00000000000000000A000000A0000A90B0F00A00B0CA0AC09C0B00A000000A000C0000A0A00C00000000A00A09A0C0A00A000000000000DADADBF9FFBFFFFFFFFFFDFFFDFFFFFBDFBFFFDBFFBFFFFBFFFBFDFFFBDFFFBFDFBDBFDB0BCF0A9A0000000000000000000000000000CAC0E0CA0CAD0E0CB0F00ACA0E000000A0000A00000000000000000A90C90C0000A000000000000000ADA9FBFDFFFFFDFFDFBDFFFFFBFFFDFFFFFFFFFBFFDFFFDBFDFBFDFFFBDFFBFDFFBF9FF9ADFDB0F90C0000000000000000000000A9C090A90B00BC0B00A90A0C00ADA0DF0000000000000000AC000C0A0000000A0A9000B0900009C0000000009A9F0FDBEBF9FFFBFFFFFDBFFFFFFFBDFFFFFFDFBFF9FFFFFFFFFFFBFFBFFDBF9FDFFDBDFA9A09C0B0090000000000000000000000A0E00CAC9E00E0DAC0E9CA0E00C0AF000A0000000000A000000A00CA00C00000A9AC00CB0DA00B0000000000CBDAFFDFFFFBFFFFFFBFFFFFFFBFFFFFFFFFFFDFFFFFFFBFFFFFFFDFFFBFFFFFBDFBEBDF9D09B000000000000000000000000000000B000A00F00A0CB00A09E9ADAD0D0000C0000A0C000000A009000000A0009C00C9A0A00000000000000000B0BF9FBFFBDFFDBFDFFFF9FFDFFFFFFFFFDBFFFBFFF9FDFF9FFFFFBFDFFF9FF9FFBDF9F0B00AC000A00000000000000000000000000C0F0DAC0E9CA00E0CAC00CA00AA000000A0000A00000000E00A000090BCA00B000900000000000000009C90F9EFDF9FFFFFFFBFFFFFFFFFDFFFBFFBFFF9FFDBFFFBFFFFFBDFFFBF9FFF9FFBDFBFDBCF99000900000000000000000000000A09A0A00A00A00AC9E90B00F0A90E0F000A000000000000A00000C0000E00000B0C0A00000B0000000000000A0F0F9FBFFFF9FBFFFFDBFFFFFFFFFFDFFFFFFFFFFFFFFFFFFFDFFBDFFFFF9FFBDFFBDFADF0B00000000000000000000000A000000C09C9E0CBCBC9A00AC0E00E0CA9CF00000000000000A00C000A0000900E00000B0900E90000B0000000000090BCBCBDBFFFFFDFFBFFDBFFBFFFFFFFFDFFFFFFFFFFFFDBFBFFFFFBFDBFF9FFBFDFBD009D9E9000000A000000000000000000000A00A00F0000AC0AC9A09E09A0CA0F000000000A0000C0000A000A00A00900F0F0CA000000000000090000090A09F9FFF9FBDBFBDFFFFFFFFDFBFFFFBFBFDBFF9FFFDFFFDFFF9FFFDBFDBFF9FDBF0F0EB0A00000009000A000000A900F000000000A0CA00E9E0B0DAC0E00BC0E90EA00000A000000A0000A000C00000000A00000B0090000000000A000A0000D9F0BE90FEDFFFFFFFFBFDFFFFFDBFDFFFFFFDBFFBFBFBFFF9FFF9FBFDBFF9FFBF0BFB9C09000A0000009000000000A0000A000000D0B00F0A0C0CA00A90AC0A9CA9F00A0000A0000000000000A0C000E000D0BCBC9C0A0000000000000090A00A0BC9FF9BFADBDBFBDFFFFFBFFFFBFFFDFBFFFFFFDFFF9FFFF9FFFDFBFDBF9FDB0DBCF9AC0000900A0000000E00000000C000090A0E0CA00CBA0A0CBC0E90F0CAC0F00000000000000A00000900000090A0A0000A0A009A0900000000000009090DB09AD0BDBFFDFFFFFFBFDFBDFFDBFBFDFBDF9FBF9FFBDBFF9FBFFDBFFDFBAD0F0B0C900900A0009E00090000A09E0A00C000E900A9CAD0C0D0B00AD0AC0A90B0D00000C0000A000000AC0000A00000090CB0BC9090C000000000000000000ACA0DADAF9EFDBFBDBF9FFFFFFFBFBFDFFFBDFFFFFDFF9FFFDBFFDB9FF9EBDFD0D9D09A00A00D00D000090E0009C0C09000A00A00E9CA00A0A0AAC0E00AC9ACAC0EA000A000A000000000000E0000A00E00A00C0BC0A0A0000A00000000009AD0909A0909CB9BEBDFFFFFF9FBDFFDFFFF9FFFFBF9FBF9FFBDBDBDBEFF9F9DA90B0A0AC00D00A000A0000A00000A09A0ACB009000F00A09E0F0D0C9A90AD0AC900F0F0000000000000E000000000C00000000A9ADAAD0090C0000000000000000A0000F0F0BDEDBDAF9FF9FFFFFBFFBDBFFBDBFDFFDE9FDBDAFDAF9F99E9AF9E9C09C9090A90D0B0000B0C900A00000D000E0E0F00AC9E0000A0A0AC0E00E90ACA00F0000000000000000A0000A0000000009C000D0AC000B000000000000000000F00090F0B9E9F9FF0FFBDBFDF9FFFF9FFEDBF0BF9FBF9FDBF9E9FE9FFD0F09A0000AC90CA00C00000000090D0AD0A000900A0BC0A00E9E0CAD0CA09E00E00BCB0F0000A00000A00000000A0C0A0000A0C000A0AD9A9A00000000000000000009009A0F00DA9BCBD0FBDFEFDBFFF9E9E9F9BC9FD0BD09F0BF0F9F09F09AD00E090A0000A909A0009000A0ACA0A00A0D0A0E9C0C0AD0E9A09A0A0B0F00BCBCBC00EA0000000A0000000000000000000000A0A090F0A0C0000000000000000AD00000AC900FADDE9E9FBDADBDBF9F0F9F9F0BCBCB0BDAFF0FF09E9ADFADBCB0900000D09AD0AC009A0CA90C900C900000AC0000A0BC0A0C0CAC90E0C00AC0000ACB0F00A0000000000A000E000A0000A0900090ADAFC9A000000000000000000A000009EF0900B9B9E90F9BDBCFADBC9ACB9CB0BCBDA909F09DF90DA900090E0000000A000C900000000C9A009A00E900009A0E9E00AC9A0A9ACA90A0E9A0E0F0AC0F000000000000000000000C000000E0000C0090AC9ADACA000000000009009AC00090E0DAC0CA9E9ADACB90F00BF900E9C909E9CBF09FAA0F09CA9F0A9000900090F00A000000090A0C0AC00000E09A0C9000F09AC0F00C0CAD0B0C9A9A0C9A0D00000A0000A00000000A000A000000ADA09ACBDB0C009090000000000AC000090BE909A09A90090F0DB0E909F00CBC90B0F090BC90F0D9F09E09C00D00A0000A0000900000A00A0009090BC000000C0A00E000E0A00ADA9A00E0E0E0C0F0E0DA00000000000CA000A00000000000000000AC90A0E9A9E0AC000000000009000A0C90E0000000F0E90B0C9A9E0C9B09AF0F0F0AD9ADADBE0A009A00B0009C00C90F0000C0A09C90C90A0E000B000B00BC0E900E090DAC00AC0E909A009A0A90AF00A0000A0000000000009E000000A900A09A0E9E9ADE9C90A9000000F00AC0000BFE900000F0009CADABC9CB0BC0E9C0DB00F9A0DB00090D000090CA0000A90E0000A9A0D00000A0C00000000000000B00ACA9ACA00B0E0B00ACAC9EAC9CAC0F00000000000000000000000000000CA9C00CB009E09AFAE9CA0000D000000009000FEC09E00090A9C0D0BE9CB0BC9A9E0CB00C9A0CBCBCA09000E90000BC00B0F009C000000A0900B000BC000000CAC0E90BC009CA0CA9C0AD0A9A00DA0BCB0D0000000000A000A000CA00A000000000A0A90CBE9AE9C9F0B0DA00000000000AC9E9A9000000A000B0AD0DA0F0CBADA9B00DA90D0B09090CA00900009C009AD000F0000B0AD00E0000F000A00A0A900B00E00BCAA0F00E0AD0AC0CBCA0CA00CA0000A000000000000000000000A00A0009C00A090C90BAF0FCA00A00A0000BC09A9FC0A00000C9CBCAD0E9ADADB0D0D0C0B090E0BC0A0E9000000000A0B0E000B000A9C00000000000000000009C0CAC0E09AC009C00F0BC0ACB0B0A9CB00FAF0000000A000000000A0000C0000009CACA9E09CBEB0E9C0F0BDAD00000000000ACBCBC0000B00A00909A9AD09E0DA0A9AD0CA090009C90A000000000D0C09000C9A9CA9AC900A9000A00E009C0A0A090A90E0A0E0BCA0C00E90E0CAC0AC9E00F000000000000A000000000A0000000090009A0000CB0EB0FE0F0FAD00000000090E9E9E9000090F0A0E0C0BE09A0DAD000B0D0E90A0000000000000A009A00DA00CA90C00AC000C000900000A9CBCA0C0E00D0B0C0ADA0E90E09A0DA9CA00F0D00A00000000000000000A000000000A0A0A0C00BCB0F00F09F0F0DA0F0000000009E9E00C00CAC00D090B0C09E0F090ADAC0A0000D00AD00000000090A00DA00900000B09000000A0CA000A000A009E0B0F0A0CA9AC00E90E0BC0F00E09EA00A000000000000000000A0C00000000A0D09C0B0BCA0F00F0AF0E9EBED0A90A00000F0E900A0B0090B0ACA0DA9E0900E0D0090090000000000000090AC0D0DA090CA90AC00CA00A000090009C0F00D0E000C0AC0B00CA9E9CA9AC0A00E90E090FF0000A00000A0000A0000000A0000090ACA9E0C09ADADA00D0BDE9C9AD0C00900000A9EE090CBCAC0C090F0C09E0F090A000B00CB00B0000000000009A0A0000A000C900A0C09000000A0C0A00E0A90ACB0AC9E0E0BCA00AC0CBC9CB0CA9E0E0F00000000000000000000000000000C0900A9A0E090F0F0E0E0B0FACBE9A0C0A000AD0900E9A0090A9E000B0E0900CA9CB0C0CB0000000000000A0000D0D000C000A0A09090AC0E0000D0A09A090CAC9AC09A09C9E00DA0DA9A0AA0CA9CA0DA0D0000000A00000A0000A00A0000A09A00AD0C0900E00DAD09AD0E9CBC9AC90AC00000ACA090D0F0AD009E9C090E0B000000B000000000000000000F0A0A00A9000D09C0E00A900000A00000C0CA0B0A0CA9E0CA0A0BE00F0CAC9C9E9CA0DA00CB00000000000000000000000C0000A00F000B0E0A9AFA9E0AD0E9AF00AC9A09090C0BC9000E0A00DA09E00A00E90C09E000000000000000000000000D0000000A9A0A0900C000B00900A000B0A9C0C0CB0E00B0E90C09E00A90E0A0AA9CA0BCBE0A00000000000000000C000A0009C0000A0C0900C009EDBCA09AD0BC9AC000C0A0000E00009C9E00F09AD0F00090A00000000000000000000A09A00000000C00C0D0CA0A9000C00AC000BC00C0A0B0AC00DAC00E9ACA0CBCAC9E0C9C0AD0E00F0000A000000A0000000A0000000A000BC0B0E0F0A9E00ADADBC0AD0AC9A9E0A0900009E9000A09AC0FE0A009E00000000000000000000000090C0DA000090A09A00A90C00ADA0000000C00A9A0C0CB0ADA00ADA0C0BCB0CA9A00B0A0F0A09E0D000000000000000A0000000000AC0B00090090009009F0AD0E9ADA9CBAC0090CACA00000A0000C09A09C9CA0000000000000000000000A00000000000A00009C00000A900C000BC0B0B0000C00B0A0C0A0CBCA0F0BC0ACBC0CAD0E9CAC9E00BB00000000A00000000A000A000090000C0ACA0E0A0E0E0ADAADADA0DA0D0B0CA000900A000009A09AC9E0A09C0000000000000000000090C0CA09A000C00CA00A09E9C00A9A0AC00000C00A90A0C00E9A0DA009C0AC0AD00A0BC0A0CA90A09E0E000000000000000000000000000E000B0009000D0909ADA9CBCBDA0CB0ACB009000A000C0000C00CBE9C00000A000000000000000000000B09A0C09A090009C0000A0BC0C0909A9E000A0C00CB0E90AD0AD0EA0E90F00AF0E09E9CBCAC9CA9CF0000A00000000A0000000000A0000E0000A0E9A00E0000DA00E0EDA00F00CB0E0AC90C9A0000A0B000A000A000000000000000000000A00000C9A0C9ACA90A09A0D000A9A0E0C0009A0900A00000AC0AC0A90DA0E00E90090E00A00A9A0ACA0D00000000000000000000A00000000000CB0C00C0B00A00A0DA909ADAC00F00C90000A000000009C0F0D000000D000000000000000000C090E9A0C9A0C90CA000C0A0E90C0D00A0000C0E0BC0A0CBC9ACB0CACAC90E09E0E0E9CBCBC0C0F09E0B00000000000000000A000000000A000A000B00B0C09C0B0DAC0FACAD09A00B0A0DA0C0000000000B000A00C00A0000000000000000090B0E90C9A0C900A09C90A90900CB0A0B0CA0E0A9000000A00AC90CB009A0E90E0B0F00A0AC0B0A0CA09E000000000A000000000000000000000000C0AC0A0A000C0A0BA0DA9EE0D00C0CA00000A0000000000F0D0000000000000000000C0BCAD0E9E9AC9A0A009CA0AC00CACB00C0C09009090C0E00AD0CAD0E0ACBCACB0CA9AC00F0F0CB0CBC9AD0EF000000000000000000000A00000000000A00000D000A0000C0DA0F09AF0F0B009ADA00C00A000A0CA0A0F0A000000900000009A0BCB9AD9A9E9AC90C9E000090B0A90CA90B0E0ACACA0A000C00A90A90AD00A90CA9C09ACA000B00E00A0CA00F00000A0000000A000000C0000A0000009000E0A00009CA9A0B0C90FAD0A0ACAF0C000900000000009C0000000000E00C0DAC0E0D000C0A0C00000000A00AD00AC0D0A00CA000090000D000A90AC0E0CADACBCACBCA0AC0BCBCACAD0BC0E90F090000000A0000000A000A0000000000000E900900AC00000C00CBAE0C0ADDF0D00FADA0A000000000A09E0AD0CA0000A0B009A900ADA9A9C9A9C09A0000900A0CB00AC90B0C9ACACB0A00A000000A90BC00A00DA009AD0BC00AC900AC0B00E00E000000000000000000000000000000000000AC0000A0A0A9E9AC9C9AF0A0ADACB0DAC00D000000000000D00A9C900090CA0C00E90000C0A0CA0A0CA900CA0CB000090AC00A009000C000C000CA90E0CB0F0F0A0F0E0AC0A0E90E9E90E0DA09EF0000000000000000000000A00000000A00A000A000000D000E9A0AC00D0F0E9ACA0DADA0AC0CA0000000A0F0A0E0F0E90DA9E90ADADA90DA9C900000E0009000BCAC09AC0DACA00000C000A0000E9AA0E000E900A9C0BC0B0EA0A0E90A0CA00F000A0000000000000A0000000000000000000000000000A0D0E9E9A9E0A009ADADA0DA9C90A0000000A00000D09A0BCADACB0E0D00000E000A0E09CB09E0A0E0009A9E09A0090C0A000B00C090E00C9C0ADA0E0F0E0AC0F0C90D0E00E9E90F0900000000000000000000000000000000000000000000A000A0BC0ADE09CADAC09A0DA0E0A000000AC00C00C0A0E09C09AC90E09A0CBCB090F0090A000E09C090F0AC000AC0AC0A000A000000A000F0A0F0AC90B0C0BC9A00A0E0A90E9A00E00E000000000A000A000000A000000000000000A000000000000000F00BE0B0A0BCAC9ACB0D0F0D0E00000000A9009CA0AC00A0900CB000C0E00C0AD00E0900B0E00D00CA900900090C00C00A0000F0A0DA0C9ACAC0BE0AC0E9CB0F0CB0C0CB0CAF000000000000000000000000000000000A0000000000000000000E909C0F09CB00E90C0A000A09000A9A0900E0000D0A90D00CA00F0A0090A900A0090A0E090F0ACA90C00E00A00A00000C00000C9A0CB0E090AC00D0A90A0C00A0CA9A0CA90F00000A000000000000A000000000000000000000000000000000000ACAF00E00CB00E9AD0E00000E00C00A0000AC9A0D0E0A0A9000090A0000AC0DA00C900E00009000A00000C00000A0000ACA0A0CB00E90EADACA0AD0E9E0AD0F00E0DA0CA900000000000000000000000000000000000000000000000000000A0DA909E09EB0FE9AC0A90E0A009000C00C9000AC00A0BC90C0E00E0D0E9C09A00C0B00F00BC00CA000A000A0000000A0000900DAC0E90E9000B09CACA00BC0A00E90A0DACE000000000000000000000A0000000000000A000000000000000000000E0E9E00C009E0BD0E090C000A0A000A0E0900F09C00E0A90F090A000A00000B00E00000A0A0009C0009000CA000000000E0A09A0CA0CADACACA090DAC9E0CB0CAD0A90FA000000000000000000000000000000000000000000000000000000A00B0E9E9ADA09E0A09ACA90AC9C00B00000E000E0AC909C0000E000A90AC9E00F00BCAC0000B0CA00A0C00A00000000A000D0E00F09E90AD00ADA0E00A00F0CB00AC0E0D000000000000A0000A000000000000000A00000000000000000000000D0090A0CA0DE0F0F0C90CAD0A0BC0CBCB000F00090ACA0A0A000D00C090A00C0000009AC00C0A000000A00000CA0000CA0A09AD0AC0AC0A0F000E90F0DA00A0CBC9A00B000000000000000000000000000000000000000000000000000000000A0A0E9CB0DA090E0F0ADA90AC9CADA000E0F00BC0ACB0D0D0C9A0A0A00AC09A0AC0B0E00A90000000000000000000000090CAC0AC0BC9A9C0A9E90E00A00E9CA00AC9EE0000A000000000000000A00000000000000000000000000000000000000C900AC0A9CAC900BCA0CBC9EB009E9E900AC0AD0000A0A9AC00D0DAD09AC009A0C00900CA000C0A000000A00000000ACB09AC0B0CAC0E0F0C0E00AC9E90A0DAC9A00F00000000A000000000000000000000000000000000000000000000000000A009AD0A09A0E009C0BCA09CF0E000E0D0000ACBC0C0C09ADA0A000A000A0C0B0AC0000000000000A000000000A0000CAC0BC0A09A0900A900F09A00E0DA00AC0F0D000000000000000000000000000000000A00000000000000000000000A000C0AC0ADAC0F09E0A9E0DACA009ADA9A0ADA0090A9A9A0C000C000AC0F00DA0C000A000000A00000000000A00000000A90AC0F0DAC0E0E9E0E0CAC0F00A0CBC9A00B00000000000000000A00000000000000000000000000000000000000000000000BC009A00E009E90A0B0ADAC000CBC00DACA0C00C0A0A00B0E90000A00000900CA0000000000000000000000A9E0CAD0B00A00F0B000A90A90B00F0CB00AC0EE00000000000000000000A00000000000000A000000000000000000000000A00000B0E00CB0BCA0E09C0D0009ADA00BCA000C900A0000D0000000A0000000ACA0000A0000000A000A0000A000000B000AC0E0DA00CACBC0E0E0CAC0A0CAC0A90F000000000000A0000000000000000000000000000000000000000000000000A000090E9AC00BC09E0A0AE90C00090090F00A0E0DA09000AC00C000000CA0000000000000A0000000000000000000E9C0B09A0CB0AD00A0900B00BC9A09AD0E0D00000000000000000000000000000000000000000000000000000000000000000C0AE9C00AC00A000D0D00A0A00E0E0E00F0000000E000000A0000E0000000000000000000000000000000000E0E9A0AC0E0DA0C0A0E9CACAC0E00AC0E00A00B000000000000000000000000000000000A000000000000000000000000000000A00D00A00900B0D0B0A000C90C000900000000A00000A000000000000000000A000000000000C0000000000A00900CAD0A09A0C0B0D00A0B00B00BC0B00AD0FE0000000A00000000A0000000000000000000000000000000000000000000000000A0F009ACA0C0AC0C000A0000B0000ADA000000CA00000000000000000A00000000A000000A00A000A0000000ACA90AC9E0CA9AC0A0E9C0F0CAC0A0CAD0A00F000A0000000000000000A0000000000000000000000000000000000000000000000000E000000000A00A0000A0000E0000CA00000000000A000A0000000000000000000000000000000000000C09CAC9A00B0CA09E090A000A09AC9A00AC9E0D000000000000000000000000000000000000000000000000000000000000000000000A000000A000000000000C000000A0000000000000000000000A000000000000000A0000000000000A000A00A0A0C9E0CAD0E0CAC0F0E9CAC0AC9E00A00A00000000000000000000000000000000000A000000000000000000000000000000000000000000000000000A00A00000C0000000A00000000000000000000000000000000000000000000000090E9C9C0A00B00A00B09A000A00A9C0A09AC9EF00000000000000000000000000000000A000000000000000000000000000000000000000A0000000000000000000A00A00000A00000A000000000000000000A000A00000000A0000000000000E00A0A9E9E0CAC0F0CAC0CAC9E9CA0BCAC0A00F000000000A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A00000000000000000A0000000A00000000000000000000A000000000F0C0A000B09A00A90A909A000A0C009AC9E0D000000000000000A0000A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A000000000000000000000A00000000000000000A000A9ACACAC0E0DAC0E0E0E0E0E9E9ACA0A000A00000000000000000A0000000000000000A000000000000000000000000000000000000000000000000A000000000000000000000000000000000000A0000000000000000000000000000000009E0C09090A090A09A090009090000000D0C9EF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0000000000000000000000000A000000000000000000A0000E009ACACAD0E0E0E0CACADACACACBCADA0A0A0F000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A00000A00000000000000000000000000000000000000000000A000A000000000000E0090A00A90090A090000A00000A00CBC0D0D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A000000000A0000A000A00000000A000000000000000000000000009A00E0E9CAC0E0E0DACACAD0DADAC0DA000A0AA000000000000000000A00000000000000A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A000000000A0000000000000000000AC0DA9000A09A0900A000B000A00090A00CA9C0F0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0CADAC9E0CACAD0CBC0CAC0E0E0E0E0BCA0AF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090A000A000B000A000A090B090900090000D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ACADACBCADACADACBCADACACACACADACADACA0F0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010500000000000020AD05FE")}, + {empKey(5),ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E506963747572650001050000020000000700000050427275736800000000000000000020540000424D20540000000000007600000028000000C0000000DF0000000100040000000000A0530000CE0E0000D80E0000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C00000000000000A0000000000000000000000E900000000C9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000D000C000000000000000000000000000000000E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A000A90000000000000000000000CA0000000E900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0000000C00000000C00000000000000090000000000000000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0000000000A000000000000000000000CA00000000000000000A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C00000000000000000000000000000000909000000000000000C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000CA0000C000000000000000000C000000E0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A000C90000000000000000CA9000000E90900000000000000D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A00000000000000000AC0000000000009000000A0000E0E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000900CA0000000000000000B00E900CB0900000000C00000C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C000000E000000000000000000CA00F0000DA00000000000000000CACA90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009CA0000000000000000C0000000A90CA9C9090000009000C0C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0000C0B0000000000CA000E0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0000000000000000000000000000CA09009000000000000E9000000000000000000000000000000000000000000000000000009A0000000000000000000000000000000000000000000000000000000000000000000000000000000000C0000C90000000000000000000000000C000DA09000000000000C9CE0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C000000000000C0CA00000000CB0D000000000000000EA9000000000000000000000000000000000000000000000000000A0B000000000000000000000000000000000000000000000000000000000000000000000000000B00000000000A000000000C0000C0000C0D00C0F09CB0B09000000000000DE0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000D0000000000000CA9E9F0B0F0B9CBCB0D0000000000000000CF0000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000000000000000000C00000000000000000000000CB9C0D0A9C9ADCB9AD0B090000000000000EFA9000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000C0A90000000000A00000000E0DE90CB9AD9CB0DB0F0DBC900000000000000000C000000000000000000000000000000000000000000000000000E00000000000000000000000000000000000000000000000000000000000000000000090000B0000000000000C0000000C9CA90F9AD0DA9AD9AD90F00BCB09000000000000C00000000000000000000000000000000000000000000000000000A9A0000000000B000A00000000000000000000000000000000000000000000000000000000000000000000000009000E0F0BDCB0F9A9A9C90BC9AD0F9C909000000000000000E000000000000000000000000000000000000000000000000000000000000000000A0000000000000000000000000000000000000000000000000000000000000000000000000000C0C90DAD0B0D0C9C9CBCBC9A9CB9CB0E900000000000000E0900000000000000000000000000000000000000000000000000B00B00000000A000900A00000000000000000000000000000000000000000000000000E00000000000000000000A0B0E9A9AD0F0B9A9A90909ADF99CBC99CA900000000000C0C0000000000000000000000000000000000000000000000000B00A0000000000000A0000B0000000000000000000000000000000000000000000000A000900000000C0000000000C0CB0D0D9A99C0D0D9E9E9C900FCBDBE99C09000000000000A0000000000000000000000000000000000000000000000090C00900000000000090000C000000A0000000000000000000000000000000000000000C0900000000000000C0000C09090DA9AC9CA9B0B009090B0F90BC0D9E9B00000000000000D0000000000000000000000000000000000000000009A900A0090A0000000000A0A00000A000000000000000000000000000000000000000000000000E0E000000000000000C000ADADA90DB9A9D0C90F9E0F0D90F0DBFADAC9000000000000CA000000000000000000000000000000000000000900000000DA0000000A0A0B0090000000000A0000E90000000000000000000000000000000000ACB000900000000000C0000AD090090DA9C0D00B9E900990B0E90DBC0DBDBC90000000000DAD000000000000000000000000000000000000C0A000000CB0090000C0F090000A000000A900009A000A900000000000000000000000000000000C90000000000000000000CA9000ADADA9CB9A9BC0090B00E90D9EBC9F9EDADB0900000000CACA0000000000000000000000000000000090A0B0000B0DA000A000ECBA00A0A009000000000A0000000C0000000000000000000000000000000000000000A0000000000000000090D09090B0C9009A90C0D090F0A9C9ACB9ADBCF000000000000D00000000000000000000000000A0B0CB0C000090000000090D00FF0C9E9090A00000000000000000CB00000900000000000000000000000000CB000000C900000000000090CBCB0BCBC9C9B00F0D00B909AD009C9BC9FCDBE9B0900000000EDA00000000000000000000000ADAC90090009000ACB000090E00A00EBEA0ACA9000000A00A009A00000E90000EB000000000000000000000000000000000000000000900E0090009C909A9A00C909A9D00AD009F0BC0ADA9BC9FCB000000000000000000000000000000000EFDFBDADA00A009A9000009A009A90000C9EF9ACA0000A90090000000000ACB0000F000000000000000000000000CA0000000000000E000E9000A9F0B0F00D09DA9AC900A9D0A9E00D0BD9ADE9FE9FC9000000000C0000000000000000009EFFFFEBCB0090090A0009009A000000C0000AEF0E090000A000A000A0000000CB00000CB0000000000000000000000009000000000000009E0000909C09C909A09CA9C900F90CA9C099F0F00ED09E0DBCBB00000000CA000000000000000CEFEFFFFFBDF0DA0000090C0A0000090E09A00000DEBC9A00B0000A90A000000000FE90000A90000000000000000000000000000000000900000090F0BCB0B0B09AD9A09090B900A99090F0009AD9A9E9F9EDE90000000CA90000000000000CFFFFFFFEFDEFA0B090E9000B0090A9000900090000A0EB00A009A9000009000000000FF00000E0000000000000000000000CA000000000A09A9E00000D0909C90BC900C9ADA9CAD09000F90E9F0DA0DAD0FADBDE90000000C000000000000CEFFFFFFFFFFBF9FD00A900A0000000C00A0009E00000000000DAE000A0A9A000000000ECBC90000B00000000000000000000090900000000C0C0000F09B0B0DA09C0B09A900900900BC9F0009000B0DA9DAD0F0E9F0000000000000000000CFFFFFFFFFFFFFFEF0BC9000090900F09000900A09000900000A0E09ACA090000000000000FFB00000CB0000000000000000000E0E0000000009A0000909E909C909A900D090F90E9A09090090BC990C9A9CBCBCDBFDA90000000E000000CFEFFFFFFFFFFFFEFFFF9FA9A09A9C0A0000A090009C0A0000A09A00FA9AC90B0A00000000000EFE9A0000A00A0000000000000000C0900000000000090ADA9E90DA9A9C90F90AC900090C909A09AD000E09E9CBCBCB0EDADB000000C0000CFFFFFFFEFFFFFFFFFFFEDFAC0D09000000900900A0CA000D000000000000CFFEAC00900000000000FFF9000009000A00000000000000ADA9000000000E000909099A909090A90A99B00B009000C09C009A90900F0F09EDF9EDFC000000000EFFFFFFFFFFFFFFFFEFFFFFBFDBB0A00000B000A00900090B0A0A0000A00000ACADB0A0A0000900000C0FFEB0000A0000000000000000000C0E000000000900DA9CBC9C9AC9A90C90C0090090A90B09A90BC0090F0090F09A0F9A9B000000E0CFFFFFFFFFFFFEFFFFFFFFFFFEBC9D00DA90000900A009000009000A0090000000F0E09000000A000000FEFF900B00000000000000000000CA9000000000000B909B09A90990C90B09A90F090C900000000009AC009E0F0FCFDADFCF900000D0FFFFFFFFFFFFFFFFFFFFFEFDF9FFA0B0000090F009C9000A0D0E000900A00000A000A9A000000000000CEFFE9A00000A900000000000000CB00000000000ACBD0F0C9099A90A900900900000A900090900900000ADA090D0B9A9E9E900000CAEFFFFFFFFFFFEFFFFFFFFFFFEBEF00D09A09A000000000A0900A900A00009000900A00000000090000000FFDB00000000A0000000000000000F0000000000990B99B90BC0DAC90B0C0BC909090009000000090909000D0E9EDEDE9E9F000000CFFFFFFFFFFFFFFFFFFFFFFFFFFDBF9A000000900090A00900090000000A0A00000090000C0000E0900000E0B000A09A0D0B0000000000000AC0F00000000CADBCBC0BC99A0990009A90909A009000090090000000000A90B00B0FCBCFB0000CFFFFFFFFFFFFFFFFFFFFFFFFFFFADAD0B0909CA09A09090000A09E0090A0900000A0A00000A90000A00000B000B0000000A00000000000000DE900000000C9B90999909A090A09090900A0009000900000000000009000C0C9EDE9F0F090000EFFFFFFFFFFFFFFFFFFFFFFFEFBFFFB0B00CA0009000000A0DA9C0090A00000A9A00000000A9000B000000000000000000000000000000000CE9A0000000E9BD0F9A0E9AD90D090BC0009090900900000000000000000909A9AD09AE9E9E900CFFFFFFFFFFFFFFFEFFFFFFFFFFEDB9CBC90090900009A009000000B000090A0C00000B000000A000D09A00A00A0000EB00A0EB00000000000CBC09C000099F09B9C99909009A900090B000000000000000000000000000000C0A0FD9E9E9000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFA900B000A90B0009009000B0C00A0A009A9A90000A00000000A00000900000CAF0B00CFFF9000000000ACBE000000E99F9CB9E9A909A900F09A0090900090000090000000000000000000DA0AD0F9E90CEFFFFFFFFFFFFFFFFFFFFFFFFFFFBFDBCB000900000C900E00A9000B0090000A00000A0090009A0E909A0B00A090EBE90000FFFFB00000000C0F0900000F9BF9B99090D0B0D0990900900000000009000000000000000000900900D0AD0EDA0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDADB009A00C9A90A0909000C09000A090A00000009A0000E0F9ACA00000000A0090000EFFFFFF00000000FE9E0009DB9E9DADADA9A9C90B0000090090090900000000000000000000000000C00BC9E9B0DEFFFFFFFFFFFFFFFEFFFFFFFFFFEFBEBF0CB0090B000009A000000BC00000A09090A00A0C0A000F0ECBDADA0A9000B000A0CFFFFFFFFB00000E0F00900CAFDBDB99909C909A90090B0000000000000000000000000000000000009AC00E9ECFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC9B0C0000000090000090B000B00B000A0A000000A9000000B0CA009000A0000A000FFFFFFFFFFFF9000C0FA0009F9F9BCBDA9A90B090DB000909090000009009000000000000000000000009C09A909FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFBE9B909A9009A0B0090A0090000000B000000009000E900A000A9E0AC0A09A09000EFFFFFFFFFFFFF000AF09000F9FBF9B9A9D090D00B000900000A0909090000000000009090000000000000A9C0DACCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFCBC00A000A9000C0B00900CA09000A000A90000A0A00A00C90A9CA900B00000000FFFFFFFFFFFFFFFB00C90000FFFBD9F09D9A90B0B9090900900909A00000000000090000000000900000E090C0B00FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCBD0B09090D009090C0000A090A0A09009000A00000B0DA00AC00ADEFA09E0B00EFFFFFFFFFFFFFFFFF90CA000CF9F9FB9F0BC9AD09C0090A900900000909000900090009009A90900A90090C00B0CAD0CFFFFFFFFFFFFFFFFFEFFFFFFFFFFFBFBA90000A000A00A009A090009009E00A00A90090000A00000B00000EDF0000EFFFFFFFFFFFFFFFFFFFFB0D0009FFFBDBDBD9B090B09B009009A090900000090090009C0090000DA90009009A9C09000B0CFFFFFFFFFFFFFFFFFFFFFFFFFFEFDEDADBCB090B0909090090090A000A90B00A000A00A0009A9000CA9A90AA0FFFFFFFFFFFFFFFFFFFFFFFFF9A000EFBFDBDB09AD9E909009009000900009090B00900DA09090D090000909C9C09CA9E9AD0DAFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFBDA090C000000A00A000A0009009CA00009A0D0000B0ACA000A9C00AC9CBEFFFFFFEFFFFFFFFFFFFFFFFBC090FFFDBBCBDBD0A9090090A90909009090A09009009009A909A9E9099AD0B0A9CA9C00C00ADEFFFFFFFFFFFFFFFFFFFFFFFFFFFFBCBC9A00B090B090909090C900A9A09E09A0000A900C009C0000E0BC9A0ACBDFFFFBFFFFFFFFFFFFFFFFFFBC00FFFBFDB909A9909ACB0C90A00090A00909E990A9A90C90BC9090B0C9090D9E09CBCB0BC90DFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB0B009000A000CA000A000A09009E09E00A0A9C0B00A0A0B0009E0A0D0F0AEBEFBCFFFFFFFFFFFFFFFFA90B0CFFFF9BDFBD9E9909009009090BC9090F090AD90D09B9AD0BCBD0DB0DB0F0B9FE9CBCD0BCA0FFFFFFFFFFFFFFFEFFFFFFFFFFFBEFF09A090909C909009009000000C90E9A90900A9C0B0900000A00F0DA000D00CB0EFFFFFFFFFFF9FA90000C0BFFDBFDB9CB090E90090A9090000909A90F990ADA9E9C99F9BD0B0C9B0D0FDE9DEBDA9AC90DEFFFFFFFFFFFFFFFFFFFFFFFFFAFDB0DA90A0000A000A9000A090A9090A9A000A00D0A90C0A09A000E0EA9CBFABCB0B09E9CAD0A900A00000000BCFFBFDBBDB99E990B9090900F909A0F0DB09CBD99C90BDA90D0BD09B0DBF9CBDEBDECBC9ADA0FFFFFFFFFFFFFFFFFFFFFFFFFFDBADB0C0909A909A9000090000000A9C0C9A00A0A09E9A90A0090900D0A0C0DEB0C00A00B0A900000090009000CFFFDBFDA9E990A900A9000900F0990B9CBCB9ADA9BD09DBDBD9ADBCB0D0FBCFFDADBCBC90DACFFFFFFFFFFFFFFFFFFFFFFFFFBFDA009A00C000000D09A009A9090CA90AC909090E00C0E9000A0A00AFCB0E0F9A0FFCB00000A9A90000CFA900CFFBFBDBDB90E9090D0090B09009E0D0A99BC99AD90BDA9090BC90D9D9AD0D0F0FDADF0BCB09ACFFFFFFFFFFFEFFFFFFFFFFFFFAFDB00909A90B0900A0090000E00090A90A0A00090B0B00B0A0000AD09E09A00CFFFFFFBCB0000CA9ACB900000FFFFDFBDBCB99C0B090A9C90B9A99A9DBC9B9E9B0F9B9DBDBDBDB9A9E99F9F9FFADF0FDADAD09EFFFFFFFFFFFFFFFFFFFFFFEDF9ADA90A000000A090900A0909009E0D0A09000A0E09C0F0C909A000AFE9AC90FFFFFFFFFFFFFFB9D0DA000000FFBDBBDB09900A909090900B0C9DA9DB09F9C909D90D9A9ADBC9BC9F99E9E9EDADF0FF0BC909E9FFFFFFFFFFFFFFFFFFFFFFFBFAD0900090090909000A0D00A00B090A0D0A0A9009CA9A00B0E009A9C0BE90A0CBEFFFFFFFFFFFFF0BEF90000CFFFFFDB9DBCB09090A090A9C99B0DB09F090B9F9ADB9F9D909B0D9F9EF9F9F9F9E9FE9EDB0F09ACFFFFFFFFFFFFFFFFFFFFFFF9F0B0AD0A9A0A0000B09009090900E900AD0000A0A0D090C0B0F00CA0C0FEFDA009ADAFFFFFFFADA0CFFF90000FBF9FBDA909090DA99C0990B0F0B09F0BDBD090F990F9A9F90DB0B0D9EBD0FCBCBD0DF9EDB9E0D9AFFFFFFFFFFFFFFFFFFFFBFE9F0D00900909C9A000000A00E00090E900A9E00C0B0A0CA9C00BCBCBFFFFFFFFBC00000CB0BC9A9CFFFF00000FFFFFDBDBCB0F009009B0C90909DBC990909BDB9ADB90D909F00D0DA9990F99F9DAFF0F90FC9F0ACDFFFFFFFFEFFFFFFFFFFFFBF0B0B00090000A09C9A9090909A9E090A0D009A0B0C90A9CA9FCBCBFFEFFFFFFFFFFFFEBCFE9E9EBFFDA9000CFFBDB9A9090900B90B000B0F0BCB0B9E9A9F0B09D99C9A90900900B0D9EDBDE9FCBD0FD0F09B00D9EFFFFFFFFFFFFFFFFFFFF0F0BC900A9A0A90900A0000AC0A09090AD09A9A0D0C9A0BCA0DA09EFDEFFFFFFFFFFFFFFFFFFFFFFBCFFBDA0000FFFBFD90F0B0090C9090909099909D099D09D9DB0B0B900F009A90D0B09BC9BCBBCBF9AF0FADADA09EFFFFFFFFFFFFFFFFFFFFFF90A090009C0A09090909090D00CA900AC00DA0B0C9C09CB0DAC9AFFFFFFFFFFFFFFFFFFFFFBDFF9CFFF99000FFDFBBC909090090A9CB0DA9E9ADA9BCB0B0B909D9D0D9009000C9A9C9E9BCBDDBD0FCDBD09090DA9CFFFFFFFFFFFFFFFFFFFFBDE9DA09C0090900A000A000A0B0A90E909A900D09A0B0F00FADAFCFEFFFFFFFFFFFFFFFFFFFFFF9EBFFF0000CFBFBDB9A90F09A090900909090D90D099D9D9CB9A909A0900090900CB090DBDAF0FF9FADA9E9E9BCF09EFFFFFFFFFFFFFFFFF9EB9A009A0B0A00A9090909A090009C090A0C0E9A0E0D0E00F0D0F0FFFFFFFFFFFFFFFFFFFFFFFFEBDFFF0B0000FFDFB9C0DA9090DB09A90B909A9A9A9E9A9A9B9C99A9090909000009090DADAD9FF9F0DB9E9C9AC90BCFFFFFFFFFFFFFFFFFFFF9E909009000909000A00C0900F00BCA0D0B090C909A090F00FADEFFFFFFFFFFFFFFFFFFFFFFFBDF9ACB90000CFFF9F09B09090B000090D0A00D09C9999D99C09BD09C90000C9090900F0BC9DFFD9E9FF9E90B0D9E9CBCFFFFFFFFFFFFFFFFBF0B0F00B000900A0DA909A900E90900900A9C0A0B0E0DACB0F00F0FEFFFFFFFFFFFFFFFFFFFFFDFFEFBDEF90000FFBF9F09BCB0D099BC0B090D0A90B0E0B0DB99C90B090B9090A9090F90D0FA90FAFDF9AD9E9CB0A9E9CBEFFFFFFFFFFFFFFFFFFF90B009E0A90D00000000B000A09E0AD00A9C9C090A09C00F09EFFFFFFFFFFFFFFFFFFFFFFFFADBD0B090000EFFFDA9BC9999A9A00990909090909999D9B090B9F9DB900D099CBDA90F9F9DFF9FDBDEDB090B0D9E9EBD0FFFFFFFFFFFFFFFFFBDAF00900900A00B09A090009AD0000909E90A09ACBC9CB0F0FEFFFFFFFFFFFFFFFFFFFFFFFFFF9E9E9C00000DFB9B99C90F0AD90D09A09A09A9E9E90F0B0D9BC909A9CBDB9BCB90DBDF9E9FBCFDBCBB90BCBCB0E9CBD0FFFFFFFFFFFFFFFFFBCF900BCA90A900900009A009E000E90CA000C9AC900B0A0F00F9EFFFFFFFFFFFFFFFFFFFFFFFFFFBDA09A0000EFFF9CA9A90990090900D009C90909A999D9BC9BDBD9F9909C99C9F9CB0DBDEDBF0FF9DAD09F0C99BCADADEFFFFFFFFFFFFFFFFBA9E9090090090A90B0009A009A990B09C9A0C90E9C0D090F00EDFFFFFFFFFFFFFFFFFFFFFFFFFFCBDB009000DFBDBBD99F9E9B090B90B09A90B0909CA9B09BD099A99E9DB9DA9F0F9DBEDBDFDFF9FE990F09F9EADBCBCB9FFFFFFFFFFFFFFFFFDA9A0090A9CA900C09A009000C00E000A009A0A90A9A0F00F0FBEFFFFFFFFFFFFFFFFFFFFFFFFBFF0F090000EFF90DA9E9099C90B0C9090909D0BC9B9D0DBCBDBD9DB99A9CB9D090DAD9FCFADE9F99DA90DADA9DADBDBCFEFFFFFFFFFFFFFFF0F9C90B0A9009009A9009C0A9A900909AD0F00C90E9C0F00F0FDEFFFFFFFFFFFFFFFFFFFFFFFFFFFFF900000CFF9ADBD990BDA9B0D09B09E90B0090B0909A99909BCB900909000090909CADF9F9F0BCA9DA9BDBDADACBCB0FFFFFFFFFFFFFFF9FB0B0BC0900B00B000090A09C00B0AC000000B0E900B00E90F0EFFFFFFFFFFFFFFFFFFFFFFFFFFBDBDE900000FFBDA9ADAD9AD09B0B00909AD09B0909CB99DADBC9909090000990000000909C000D0990090DA9CBC9FDBCF09EFFFFFFFFFFFFFEDAD00900F00B0CB090A09000BC0D09ADA9AC090E9C0F09E00F9EFFFFFFFFFFFFFFFFFFFFFFFFFFFEBFF90000FFBDF9DB9AD9BD0D09BCB090B0D000DB00F099009000000909C0AD0BC909C0A909000009A0F9FA99F0F0F9F0F9EFFFFFFFFFFFFBF9A9A0B009009000A90009A90B00A00000C9AC090A900E99F0EFFFFFFFFFFFFFFFFFFFFFFFFFFFF99E9A090CFBCB9FB0D9BC9A9B9C990DA99DA9B900990900090090D09C9C999909009A99C9E9A900000D009D0F0F0F9E9F0FDFFFFFFFFFFFBF9E9C0900B009A9090C0B000C00009C90F09A00B0E9C0F09E0EFFFFFFFFFFFFFFFFFFFFFFFFFFFF9EF0909000F9DBDA9DBBC9B9D0A9A09A9C9A900009000000909B0B0909A9B0F0F9F9FD9E9BDAD0B090DB0BF0F0F9F9EDBCFFFFFFFFFFFFFFFCB090B0A900B000A09A90CB0B09E0A0A00000D0C900A90BC0F9EFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0BE90B900AFF9F9BC99BD0B9D09BC99A9909909000909BCB09D9BDBD9D0909909E9AF9FCBDB0D0DAD09C9B9DADADFADBCFDFFFFFFFFFFFBFBDA90D00B0CA90D000090000A09D09C9A9E0A9A0F0C0E0B0E9EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD99FD099C9BCBDADBDADBD0B99E99E99E90000000009C990F0BC909A9A9E90E909D9CF9F0FDB0A90BF09EDAF9FDADF0F9FFFFFFFFFFFFFFADADA0A90C9090A09A90A9090DA00A000C00900C900B09C0F0EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF9B9B909FDBDBDB0BF9B0BC0B90B90B9090D0A909E0B0F99999B9C90D090900BCBCBDADFCB0D9DED09E9BD9E9EDBEDBCBDFFFFFFFFFFFFDBB09C90A9A9A909A00A90AC009009C9A90B0CA9A0F0CE9E9ADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDBDBD09CFBDBDADBD9FCBD9BD0F90F9090A909C00990D09AD0AD0B09000009D09CBDADBCB9E9ACB9B099EDAF9F9EDBCBDBFFFFFFFFFFFBFBC9A9A0900000A0900D0009A9AC00A0CA0C0900D009A9A0DE9EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBDB99B0BDB9EBDBDBE9B09A9A99E909CB090909A9009A9090D909090090900ADADAD9ECBDE9D9BD0C9EF9BD0DAFDBEDE9FFDFFFFFFFFFFCBAD009B0A9E90D009A009A00009F090909A0ADA0BC0D0DA0ADFEFFFFFFFFFFFFFFFFFFFFFFFFFFBFB9F9F999FFFBDB9E9F9F9F9D0DA99B9B9090DA90909AD0BDA909E99A90000909090DADBDA9DA0FCB90990F0FBFD0BCBF9E9FFFFFFFFFFFFBDBAB00C9C090A0BC090A0909CA000E0AC00D000D09A0A0D0F0EFFFFFFFFFFFFFFFFFFFFFFFFFFFF9DB9F9F9CFBDFBDA9F0F9F0B0B99F0D09090009C909E9090990B0900D0909000C9E9AD0DADF0D9090090F99F9C9EFDFD0FDFF9FFFFFFFFFFFF0D09B0A9A0B0900B009C0A0A900A90909A00E9A0E0D0F0F0FBDFFFFFFFFFFFFFFFFFFFFFFFFFFBDB9F9FB9FBDFBDBDF9F9FBD9F9CB09B9F9A0900B0A9090B0DAD0990B0090090DB090DCBCBD0B9CA90BCB9CFADBFBDADAFF0F9FEFFFFFFFFB0B9A9E009009C00A900B00A9C9009C00CA0D0900D09A0F00F0DEFFFFFFFFFFFFFFFFFFFFFFFFFF9DBDBDB9DBDFFBDBCB9BDAF9A90B99F0D0909909009D0909C9090900D0900C90000D0F0BDAD0BCA990C90C9B9DBCD0FDBDADFB0DFFFFFFFFFFFCBD09CB0BCA9A90A9C0090000F0A90B090A0E9A0E0D00F0BE0FEFFFFFFFFFFFFFFFFFFFFFFFFBA9F9FBDB9BDFBFF9F9F0F9DF9F9CB09B9B0900F0900009E09A90B0DB0909A909090A90DCBDADD9DA9909B9C9E9FBFDBEFFDBCFF0DFFFFFFFFFBF0BA0900090090D0A900A90B000000C0AC9090C909ACB0C0DADBFFFFFFFFFFFFFFFFFFFFFFFF9D990D9BDBDFFFDBF9F0F9FB9F9B99F90D0DB99090090B0990D09090090909090909D0F0BD0DB0B09CA90D0B9DBCDFBCF9CBEF909FFFFFFFFFBFDBC90A9E9A0B0A0900A900C00B00F0A90900E0B00E090E9FADEFFFFFFFFFFFFFFFFFFFFFFFF9B0B0FBBF99BFFFBF9E9F9FB0F9AD9E9F99B90CA9009009C90B09F9099090D090BD0F0F99C0FF0F9D099C90BCDADFBEDF9FBFD9FBDEFFFFFFFFFCBA09AD0900D0C09A09000A90BC900900ACB0900F09CAD0A0DADEFFFFFFFFFFFFFFFFFFFFFFFF9909DBD9BFFFFFF9F9F9FBDF9F9BDB99AD0B9990990B009A9C900090009A9B9D0B99D9EDB9099090B009A9D9BDBFDF9FFCF0FADF9DFFFFFFFFFBD9F00B0A9B0B0B09CA9090000009A09C900CAD009E09AD0F0FAFFFFFFFFFFFFFFFFFFFFFFFFB90909FBD90DFFFFF9F9F0DBFF9F0BDAD9B9C9A9D009090009099909099C90DA9D9E9ADBFDAD0F9E90900DA9E9FD9FFF0FF9FDB0FBFFFFFFFFBFBEB0B009C000090CA900E090F0B00C90A0CB090ADA09E0ADAD0DEFFFFFFFFFFFFFFFFFFFFFFF90DBDB9090DBFFF9FF0F9FBDB9FBDB9F9BD9B9D9A9B0909090B000B0DADB9F9DB0F9FFDFDFDFB909090F909D9F0FFF9FDF9EDADFF9CFFFFFFFFFF9BC9CB0B0F0B0B0909090A000000B0E90B00AC9009E90DA9EBEFFFFFFFFFFFFFFFFFFFFFFFB9DB909090B0FFFFFF9FDBFDBDFF9F9F9BDB0D9A999C90C909090DB9DB9BDBDBB9DBDBDBFFFFBD0DA90990DF0BDFFFDFFFBEDBF9F9DBFFFFFFFFF9BCA9A900909C0000A0A000909AD0009C009C90E0F00ADA0DBCFFFFFFFFFFFFFFFFFFFFFFFF90999909090DFFFFFBF9BDFBFF99F9F9BD0DBBD9F0909990B000909DBD9F9DB9DFBDBDFFDFFFDFB990DACBB9FDBDFFFDFDFDBDE9EFBDFFFFFFFFBFEB9000E9A0A0B0F09090B0CA0009E00A9A0A090009E90DAE0F0FFFFFFFFFFFFFFFFFFFFFFFB9CBCB090909FFFFFDFFFBDF9FFBDF9F9BB9D9B09DBDA9A909D909A99BD9FBDB99DBDBDFFFDFFF90DA999D0DADFFDFFFBFFBDBDB9FD0FFFFFFFFE9BCBDB9009C900900AC000909CB000909C9C9CA0F0E09EAD0F0FFFFFFFFFFFFFFFFFFFFFFFF99B99990909FFFFFFBF9FDBFF9FDBFDB9DDBBD9F9A999D09D0A09C9DBD0BD9F9F9BDBDBDFFFFDBDA99CBCBDBDBFDFFFFFDEDFEF0DFB9FFFFFFFFBFF9A00CB9A9A9E0A9090B00A000009AC0A0A0B0D0009E090F0FFEFFFFFFFFFFFFFFFFFFFFFB9C9DB90090F9FFFFFFDFFBDF9F9BF9BDF9B9D9B9D9BC9A99A999B09A9B9DBF9F9BDADFDFFDBDBD090DB9F9F9FDFFFFFDFFBFDB9DBF9CFFFFFFFFFF0BC90B00000090909A00009090BAC0B09C90C0A90F00F0F0F0FFFFFFFFFFFFFFFFFFFFFFF999FB909900DBDFFFFFBDBDBFFBFF9FDB9F9F9BD9AD9B99C9090D099D0DB9C9F9F99DB9FFFFFEDBDBF0FDBDBCFFDFDFFFDFDBFDEBDFB9DFFFFFFFF9F09AD0ADA9E9A0A0C009E000E00909C0A00B009E00F00F0F0FEFFFFFFFFFFFFFFFFFFFFFBF909DBD0990FBFFFFFFFFFFF9F9FDB9FDB9F9F9BD9BD9E9B9DB9A9CB9B9CB9F9F9F9BDFDFDFDBDAD9DB9F0FDBDFBFFDFBFFFDAF9E9FFBCFFFFFFBFFAF09A90090009C90A90090B00900A00909AC0F090F09ECA9E9FFFFFFFFFFFFFFFFFFFFFFF999FB909009CBDFFFFFFFFF9FF9FBDF9BDF9F9BD9BD9A99C9A9D99B9C9DB9DBDB9DBDB9FFFBFFFDBEBDEDBDBDBDFDFFFDFDFFFDF9FF999FFFFFFFF0F9CB00F9A9A90A0090A000009A00D0BCAC00900CA00E0B9E9EFEFFFFFFFFFFFFFFFFFFFFB9ADBD9B9090FFFFFFFFF9F9FF9FF9F9BDB9B9BD9F90B9DB9B9DA9E9DBDB9FBDB9FB99DFDFDFDFDBF9DF9BDBDFDFFFFBDFFFBD9F0FFFB0FDFFFFFFBFB0B0E9000C90A90B0009009A0000A00090B0ACB0DAD0D0E0F0FFFFFFFFFFFFFFFFFFFFFF9D99FBD9A9090FFFFFFFFFFFDBDB9F9BD9BDDBDBB0DBDB9D0DA99990B9BD9C99FD9DBFBDFFFFFFFFDFF9FCBDE9FBDBDFFDFDFFEBFDFFD99FFFFFFFFF0F0900B0B0A9C0000000A009C9090DA000C9090A09A9ADAD0FCFFFFFFFFFFFFFFFFFFFFFB90F9FB9D09009BFFFFFFFFBFFFDB9FDBDB9BD9D9B999CB9B9DBCBD9D9CBB9FB9BF9D9F9FDBDFFF9FDADBF9F9F0DFFF9FFFBDBD9EFFFBDADFFFFFFF9F0F0B9C00900B0F00B0900C00A00A00D0B00CAC9CAC0DA9ADAFFFFFFFFFFFFFFFFFFFFFF9999DBDB9B909CFFFFFFFFFFDB9BDF9BE9DBDB9B9DBDB99D09B0990B0B9D9F9DBD9BBD9FDFFFF9FFFBDBD9F9BDBF9F9FF9FFEFFFDFFFF99FFFFFFF0FBA90C0B0F0A900009000090A9000D090AC0B0090A90FA9EDADEFFFFFFFFFFFFFFFFFFFF99E9FBFBD9909A9FFFFFFFFFDBFFDB9BD99B999F0DB099BCB9F99F9F9D9F9BD9BDBF9D9F9FFFDFFDDADBDFAD0F0F9FDFFDFFDFDADAFFF99EDFFFFFFFF09CB0B09009C90B000000A000000A0E9090CB0AD0CB00D0ADEFFFFFFFFFFFFFFFFFFFFFB9999DBDBF9F99C9FFFFFFFFFFF9BDBDBDBDBDB9B99DAD99D90F09090B99F0BBDBD9F9BDFFDFBDBFBDBF09DBD9F9CBE9FFFFFBFFFF9FFF09FFFFFFFBDFA90900CB0A0A0000A09000909090900CA900D0A9ACBCAF0B0FFFFFFFFFFFFFFFFFFFFFF99E99FBDBF9BF9FFFFFFFFFBDFF9F9BDB99DAD9DB999A9B0B99F9BDBDAD9D9DA9F9BD9BDFFDFFDEDBD9F0B0A90FBDFF9FDFDFDBDFFF9F909FFFFFFFADAB0E9A00D09090900000900CA00C0B0B00A9A9C0C9CBC0FCFEFFFFFFFFFFFFFFFFFFFFB9099F9DB9DBD9B9DFFFFFFFFFBFDB9DB9F0B99B09CB99D09D0B09C99099B9BD9F9F99FDFFFFF9F9BDA909C9DAD9CFBDFFBFFFFFF0FCF9F90FFFFFFBFBD0C9090B0A0E0A00900A00A0000B00C09C90C0A0B0B0DB0FAFFFFFFFFFFFFFFFFFFFFF9D90F9FBFDBB9F0FBFFFFFFFDBFDBBDB0D9BD9F9D9B9CB0B9A9C909B0F9BCBD9B99F9F9BFF9F0FA9D00D0B09A90ADBCF0DFDBCF9009FB090FEFFFFFFE90B9A0E0090909C00000000909000B090A0E9AD0D0E9EA0F0FDFFFFFFFFFFFFFFFFFFFFBB99099F9B9DF9F09000FFFFFFFBFDBD9BD0B090B0DA9090C9090B0C99C999B9F9F9B9DFDFFBD990099AD09C9E9DADF9FBFFFFFFB0EDFF909FFFFFF9F9E00090B0AC000090090090000090C0AD09000A9A90C9EDADAEFFFFFFFFFFFFFFFFFFFFF909AD9BDB9B9B9F09000FFFBDFDBF9BD0B99DB9090909C909A09C9000B09E909F9FDB9FFFDFBCBDA09009A9090BDADEDE9FDF9FC90FFFF90DFFFFBFEB9E9E09C909A9A00A0000000E000F09000A0D0D0C0FA090F0DFFFFFFFFFFFFFFFFFFFFFFF99D9ADBDBDFBD90009DFFFFFBBF9FB9B9C9A0DB0909A909009090B0909C99F99F9BDBDFFF9990090009090BCBDADBDBDF0FFFFB0E9FFFFFFFFFFFF9BC9090A000A00090000000A90900000ACBC9A0A09A90DAE9EBEFFFFFFFFFFFFFFFFFFFFFB90909090F9B99A0BCBEFFFFFDFDBD9C9CB999909AD090A9090000090090B09BF9BDBDFF9B0F009090909E9C9CADBCBEDADFBCF0D90BFFFFFFFFFFBFCA9A0A90BC90D0000900090000090B09000090DAC9ECBC9ACFFFFFFFFFFFFFFFFFFFFFFFF99090BDB9F9F00D000DFFFFFFBB9BB9B90BC9A909A90990D09090909A090F9C9BDBDBDFFD90090000000009A9D0FBC9ADAFDFFB0EFDBDFFFFFFFFDA9B0C900C0A000A0900000000A00AC0CA90B0E009A09A0F0F9A9EFFFFFFFFFFFFFFFFFFFFFFB9BDDBDB990900F0F0FF9FBFDBD9C90099B0DBD09DA0D0900B0000090090B9F9F9F9FFBFB9ADB90D0BD9BC90A90DBD90DEBEDF9EFFFFFFFFFFFBFF0F0B0DA9090B090A0009000009009A90C0C090BC0DAC9AD0EDEFFFFFFFFFFFFFFFFFFFFFFFFFDBB9B9F00009000FFFFFFFBDBA9B9000C9B09B0099A0B0D090909C900909BDBDBDFDF90D9000909000000F0DE9CA0CBDFDFB0CFFFFFFFFFFFFEBF0900A00AC000C0090000A0D0000000B0B09AC009AC9AD0AF0FFFFFFFFFFFFFFFFFFFFFFFFFFB9DBD900000A00F9FFFF9FDB9D9D09009B0D900900090909090009A99E9FDB9F9FFFBC9A09090A00000000DA9A090BCFBEFC0FFFFFFFFFFFFF9F090E90D090B09A90000009000A09A9C000A09ADAC90BC0F90F0FFFFFFFFFFFFFFFFFFFFFFFFFFFB90009000C90CFFFFFFFBD0B0B90900090A9000000CA90000900900909B9FBDBDF9B0DB000009000000CAD09000CBCDFFB9EFFFFFFFFFFFFFADA900A0A000C000C0A9000000900C0B0D09C0009ADACB00E09E9EFFFFFFFFFFFFFFFFFFFFFFFFFF090000090000FFFFFFFFBD9F0BD090000900000000DEB900090090BD0FD9DBDFFF9B00900900000000ACB00C09CFBE9FF0CFFFFFFFFFFFFBDBCA9090DA90B00A9000000000000B000A0E0BCBC00090F090E9EFFFFFFFFFFFFFFFFFFFFFFFFFF90000000000F0FFFF9F9FB9A99C9B0900000000000DA9000900090AC9B9BBF9FBDF9C900090000000000900090CBD0DEF00FFFFFFFFFFFFBDEB090E00000A009000900900B000000AD09090009ACB0E090E90FADFFFFFFFFFFFFFFFFFFFFFFFFB000900000000FFFFFFFF9F9D0BB0D000900000000000009009000999ADFD9FFDFB09A0900009000000000090E9FCBF0F90CFFFFFFFFFFFFFB09CA90B09C90CA00A00000000E09C9000A00E9AC90C909E09AD0FFEFFFFFFFFFFFFFFFFFFFFFFF90000000009C9EFFFFBDFF90B90D9B0900900000000009000000909E9F9BBF9FBFDBD90090000000900009ACBDF0F0CFDA9EFFFFFFFFFFFBCBDA9000C00A009000900A000009000A90D0900090A90AC09AC0AD0FFFFFFFFFFFFFFFFFFFFFFFFF900000000000A9FFFFDFBFBD0F0BCBDBD000090000090000B009009090F9D9F9F9F9A9D0A990090000D0C099EFBF9F0EF90CFFFFFFFFFFFFFDADA9CB0B0900A0D0009000090000B0CA0ACA90E0D0BC9AC090DADADFFFFFFFFFFFFFFFFFFFFFFFB000900900000CFFFFBFDFFB909090909B99DA0909A0C9A9009A09A0BD9FBFDBFF9E90A90CA9D00909A9BF0FDBC9E0DF9A0CFFFFFFFFFFFFBE900000000CA9000A0000090A0000C0090909C009A0000009ACA0FFFFFFFFFFFFFFFFFFFFFFFFFF9000000000900FFFFBDFFBD9CB0CB0F9E9CB099B0D09B0900900909D09AD9DBDF9B990909090A09ADADAD0DA9E9E0DEBE900FFFFFFFFFFFBE90E9A90D0A90C00090000000C0B090B00AC00A9A00F09ADA0909FADFFFFFFFFFFFFFFFFFFFFFFFF90000000000000FFFFFFFFFB900909009A90BCBCB0B0090900900900F9FBAF9B9F0F0F000009090909090B0D00009ADF900CEFFFFFFFFFFFBDB00CA0A900000B00009000000000009090BC90D000AC0090E00CFAFFFFFFFFFFFFFFFFFFFFFFFFB000000000CA90CFFFFBFFFFF9B00A9009009090909090009009009A090D99F9E9F99099A90000000000000090DAC9EEF000FFFFFFFFFFFDFADB009000CB0B0000A00000090000B0CA00000A0AD090B00C90FADFFFFFFFFFFFFFFFFFFFFFFFFF90000090009000FFFFFFDFF9F9C90900900900000000000000000BC9DBDA9E9E9FDBDB0D0090000000000000A0009EDF9000EFFFFFFFFFFFADA0DA0C0B000000D0900090A000000009C90B09C900AC0CA9ADADBFFFFFFFFFFFFFFFFFFFFFFFFFB000000000000E9FFFFFFFFFFB90D0000000000000000000909C9090B099F9BDBDB0BC9A99C0900000000C90C9ADEDFB0000FFFFFFFFFFFBFDA9090A90090C90A000000000A909CB00A00C0A00AD09A900CBEFEFFFFFFFFFFFFFFFFFFFFFFFFF900000000000900FFFFFBFFFF9FB09909A90090900000090C0B0BCBD9FAD0FDBDADBD9F9F0B909C90900900F0FCFFFFF9000EFFFFFFFFFFF9A9CA0D000E0A0A00000000090000000909CB09CB00A0C0A9E9C9FFFFFFFFFFFFFFFFFFFFFFFFFFF9000000000000900FFFFFFFFFF9F9AD9C90900A009090009BC9F990BC9DB9BCBF9BCBF9E9F0F9EBE9CBCADADFFFFFFFFA900DFFFFFFFFFFFEDA900A09090090090B0900000000090E00A00000C9C9A9C00EAFEFFFFFFFFFFFFFFFFFFFFFFFFFF900000000009AC0BDFFFFFFFFFBDBDB0B0F0BD909CB09F9E9FBCBDBDBFBCFDBFDFDBDBF9BF9FF9F9FBDBDFFFFFFFFFF90000EFFFFFFFFFFFBF9CB090CA00C00A00000000000900A0909009E90B0A00C0BCBDFFFFFFFFFFFFFFFFFFFFFFFFFFFB00000000000C090D0FFFFBFFFFDBDFBDBD9BC90F9BCBDBDBDB9F9BDADBDBDBF9B9BDBDFDFDFF9FFFDFFFFFDFFFFFFFDE900CFFFFFFFFFFFBC9A0000A9000B00D0000000009A000900A0DA00E00D0F0B0CBCEBFFFFFFFFFFFFFFFFFFFFFFFFFFF900000000009A90A9FFFFFFFFFBFBBDBDBF9BF9BCB9BF9E9F9F9EDBDBDBF9F9FFDFFBFBFBF9FFF9FFFFFDFFFFFFFFBFB0000FFFFFFFFFFFFFAD0F0D000B009A00C9000900000000C9C0090909A000009ACBDEFFFFFFFFFFFFFFFFFFFFFFFFFFB000000000000C09CA9BFFDBFFFFDF9F9BDBDDBFDBDFDADBDBCB9BDBF9FDBF9F9FB9FDFDBDFFDBDF9FFFFFFFFFFFFFDFFB000EFFFFFFFFFFBDAB000A90C00C00090A09000000000B000B0C0A00C9A9E9E09EAFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000000000909A090FDFFFFFFFFBFBDBF9FBBDBBDB9BDB9BDBDBDBDF9FBDFFFF9FF9F9FFFBDBFFBFFFDFFFFFFFFFFFF9C90CFFFFFFFFFFFFF9CB0900A90B0090A000000000090000B000A90DA9AC00009E9DFEFFFFFFFFFFFFFFFFFFFFFFFFFB000000000000AC90FCBF9FFDFF9F9DBF9F9FDB9DB9FDB9FDBDBDF9F9FBDFBDBDBF9FBFF9FDFF9FDFDFFFFFFFFFFFDFBCB000FFFFFFFFFFBEBCB00CAC900000E0009000000B00009009C90C000C909E9E00EADFFFFFFFFFFFFFFFFFFFFFFFFFFF90090000000009E09ADFFFBFFFF9FBF9F9F9BDBF9F9BDF9B9ADB9FBDF9FBDBDBDBF9F9FFBF9FFFBFBFFFFFFFFFFFFEDBD00CFFFFFFFFFFFDB9CB00900A0CB09090000090000000AC00A00B0B000E0009E9DAEFFFFFFFFFFFFFFFFFFFFFFFFFFF900000000000D09C09ADBDFFFFFBD9F9F9BFDBD9F9F9BDF9FDBDBDBF9F9F9FBDBD9F9FDBDFFDBFDFDFFFFFFFFFFFFFFFAD000FFFFFFFFFFFEB0C9A0009000000A00A900000000009A909000C0B090F0000E9DAFFFFFFFFFFFFFFFFFFFFFFFFFF000000000900000B0F9FFFBDBF9FBB9F9F9BBDBF9B9FDB9DB9F9F9F9FBCBF9FBDBFBDBBDFBDBF9FBFFFFFFFFFFFFDFFFB000EFFFFFFFFFFBF9CB0C90BC0B00AC09000000000900900000E90A9C0E000ADADAEFFFFFFFFFFFFFFFFFFFFFFFFFFF90000000000B00F0D9E9FBDFFFFB9DDBF9FDF9BDBDF9B9FB9F9F9F9FF9DBDF9F9F9DBFDFBDBFDFFFDFFFFFFFFFFFFFFD0900FFFFFFFFFFFDBCB009A00000C90900000000090000CA0CB000D00A909AD00DADBFFFFFFFFFFFFFFFFFFFFFFFFFFF90000000000C0090ADBF9FFBF9F9FBBD9F9B9FDB9B9F9F99F9F9BDBF9FBF9BF9F9FBD9FBDBF9FBDBFFDFFFFFFFFFFFFFF000CFFFFFFFFFBF0F09A0C90B09A00A000009000A000A90900090A9C00AC0A9E0AEDEFFFFFFFFFFFFFFFFFFFFFFFFFFB900E00000009E0BCBC9F9FDFFBF9DBBDBFDB9BDFDBDB9FF9F9FDBDDBD9FF9DBDBDBFBDBFDFBDFFDBFFFFFFFFFFFFFFF0F000EBFFFFFFFFFF0BC90A00C0009000B000000000000000090A000A9C909C009C9EFFFFFFFFFFFFFFFFFFFFFFFFFFF900090000009009C9FBFFBFBF9F9FBD9F9B9FF9B9BDBDBF9F9F9BDBBDBF9FFBDBDBDBDBDBBFDBBDBFDFFFFFFFFFFFFFF9000ADEFFFFFFFFF9BCA0090E09AC0C9000900009009000B00E0C90D00A0E0A0F0FAFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0000000900E09E9EBDF9FDFDFBF9DBB9BDF99BDBDB9B9DF9F9BDBBD9F9F9B9F9F9F9BDBDFDBFDFF9FFFFFFFFFFFFFFE0000DFFFFFFFFFBCBC909E00900090A0000000000000000C909000A0B0D090D0009DEFFFFFFFFFFFFFFFFFFFFFFFFFFFB000C0000009E90BDFBFFBFBFBD9FB9DFDB9FF9FDB9F9F9B9BDBF9FBF9FBFDFBDBF9FDBDB9BDBF9FFFFFFFFFFFFFFFF99000EF9EFFFFFFFBF0B000B00B00A090E900000000000B00A000B000C0A0E0ADACA0FEFFFFFFFFFFFFFFFFFFFFFFFFFFFB000D0000000F0DADF9FDFDFFBF9FB9B9FB9F9B9F9FB9F9F9F99FBDBDBDBF9FBDBF9FBDFF9FDBF9FDFFFFFFFFFFFFFF00000EFDFFFFFFF9F0DA90C00C09000000A90009009000090C0900D09090909009F0FFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000A00000CB0F0FBFFBFBFBDF9F9FBDF9F9BDBFDB99F9F9F9FBDBF9F9DBDBDBFD9FBDBF9F9FFDFFFFFFFFFFFFFFFF0B000CFBEFFFFFFBEFB00CA90B00AC9A900000000000A000009A000A0A0E00E0E9E0EFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000090000000DBFDF9FDFDFFBF9F9DB9BDBDBF99BDFB9B9F9BDBD9FBDBFBDFBDBFF9FF9FFFF9F9FFFFFFFFFFFFFFB9000DBF9EFFFFFFFF90CB090000090000000000000000090B0000F090D090909000FDEFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0000000000D0BCFBFFBFBFFBD9F9FBDBDBFBD9FBDB9DF9F9BDBDBBDBFF9DF9FBDBDF9BF9F9FFFFDFFFFFFFFFFFFFC00000EF0DFFFFFBF0FB00A00AD0A00A00A9009000000900C000D0000A00A0E0E9E0AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000000090A9CBDF9FFDFBDFBF9F9FBDBD9FBF9F9FBB9F9FDBDBDF9D9BFB9F9FBDBFFDFFFFDBDBFFFFFFFFFFFFFDB900000CBEFFFFFFFF0F9D090000D0900D00000000900000009A000B0D0D0D09000DCEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF009C0000000000FFFFBFFFFBDBF9F9DBDBF9F9F9F9F9F9F9BDBD9BFBFDBDFBFDBFDBDBF9F9FFFFDFFFFFFFFFFFFB000000FF0FFFFFFFF9F0A00CA90A00AC90000000000000009A00090C000A00A00E9EBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB900000000009EDBDBFDFFFBDBF9F9FBFBF9BDB9F9F9F9BDBF9E9FF9F9BDBDBDBFDBFFDBFDBF9FDFFFFFFFFFFFFD000000FE9FCFFFFFF9EF0DA900C090090A00B0000000090000009C0B00A90B09E900EDEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A0000000009A9FDBFDBDFFF9F9FBD9F9FDBDBDB9F9BDBF9F9FA9F9FF9FBDFBDBFDBFDFBDFFBFFFFFFFFFFFFFB00000C0FEBFFFFFFFFB9B00A09A00AC0009000900000000000D00A0009C0C0C000F09AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9090000000000CBADBFFBDBDFBF9FBF9FB9FBDBF9BDFBD9F9F9DBDB9FF9FBDFBDBBDBF9FBDFFDFFFFFFFFFFFF0900000F0DFEFFFFFFFEDAD09C090009A00000000000000000B009000000B09A0BC00EDFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00C000000000909DBDBDFBFBDF9F9F9F9FFBDBDBFDB9FBF9DBFBDBDF9FF9DBBDFFDFF9FFDFBDBFFFFFFFFFFF900000000BEFFFFFFFFFB9E9A00A0CB0009CA90000900900000000009ADA000A09C00BCBEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB00000000000000DA9F0FBDBDB9FBF9FBFDB9F9F9DB9F9F9FBF99F9FBFB9FBFDFB9FB9FF9FBDFFFDFFFFFFFBD00000000FEDBFFFFFFFBDE900090900000000C0000000009000009E0000909C9C00B0CBCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9000000000009ADA9DBDBDBFFBDBF9F9BFF9FBFB9F9F9BDBDBF9F9F9DF9F9FBDFBDFF9FFDBF9FFFFFFFFCF00000000D0FFFFFFFFFFFA9AC9AC0A009E9A900A900000000000900900000E0A09A00CBCBEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB00900B000000009C9ADB9E99E9FDBF9FDB9F9F9FDBF9FDBDBDBDBDBFBDFBF9FBDFBF9F9BFDFF9FDADFFFFB90000000ACFBEFFFFFFBFDF9A00900D000000A900000000000000A000B0009000C0D0B0ACFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB00E000000000000BC90F9FF9DBBD9FBBDFBF9F9B9FB9BDB9F9FBF9DBFBD9F9FBDF9FBFDBF9FFFFFFF0F9000000000CFBCFFFFFFFFFBAC9C9A00A0A900C00000000090090009000C0BC00D0B00A0CDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90000C900000090900BC90B99FBDFBF9DB9F9BDBDF9DBDBDF9FBD9FBF9DBFBFBDB9F9FDBF9FFE9F9F0F9090000000ADADEFFFFFFFBF0DB0A0C09090C00B000090000000000000A90000B0A00A9C9EAFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0090000000000000909AD9CB9CB9D9FBDBDBDBDB9FBDBDB9BFDBFBDDBF9FDBDBFEBFDBD9E9F9F0F090000000000C9EF9FFFFFFFFFFFAC90090AC000B0009A00000000000000900A900090D0C0A09CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90C000000000000000090A9CB9FBAD9FBDADBADBF0F9F9FF99BC9FBBDBF9ADF9BD9DBEBF9E9F0F0F090000000000CBEEFFFFFFFFF9F9BCB0A090A900C0A000000000090009AC000C00C00A090D0EBEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0009009000000000900090B0D09DB099B9BD9BD9F9F9F99FFDBF9CFBD9FDBBFDBFB9D90F09CB090000000000000FC9FFFFFFFFFFFBE900C900000A90900090000000000000009000B0A090CA00FCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000CA0000000000009009090BDB0BCBC9D0BD0BDB9F0BFB09B90B990B09AD09AD0DA0F09CA0000000000000CCEFBFEFFFFFFFFF9E90E900CA0D0000A000000009000000009000B0009C0A90DA9EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0000C0000000000000000000000909090AD00BDA9E9F009DBC0F9CBC9CBDB0F00B0900000909000000000000BFBCFFFFFFFFFFFFBE90A0B0900BC090C9A0000000000000000A0000C009C0A0C0FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000900900000000000000900900900A909A90090909F0B00B90B009A000900900009009000000000000000CEFDEFFFFFFFFFFFEDBE9C00000000A000000900000090009A009C009A0A0090CBEFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFF90000000000000000900000900000900009090000000900900000900900000009000000000000000000000FB0FFFFFFFFFFFFBFBC9A090ACB009000A000000000000000000009AC090DA0E9EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB000C00000000000000009000009090009000000900090090009090090090090000000000000000000009CEBCFFFFFFFFFFFFFFDE9A90AC9000A0C090900000000000000009A000000000900FEFFFFFFFFFFBFBFFFFFFFFFFFFFFFFFFFFFFFFFFF009A00000000000000000000000009009009000090000000000000000000000000000000000000000E0FFFFEFFFFFFFFFFFFFBBF0C9000009009A00000000000000000900000900F0ACADA9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A0C0000000000000000009009000000000000000000000000000000000000000000000000000000C0F09CFFFFFFFFFFFFFFFFD0B0A09A0AC0A000A090090000090000A00C9A009009000CEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF900900000000000000000000009000000000000000000000000000000000000000000000000000000CEFEFFFFFFFFFFFFFFF9EBC9C0C09009000D0000000000000000009000C0A00C0BCBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000ACFFFFFFFFFFFFFFFFFFFFFEBD0A9000000009A0000000000000000090A000090C0B000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC00000000000000000000000000000000000000000000000000000000000000000000000000CFFFFFFFFFFFFFFFFFFFFFBDBA90A9A00B0AC0000A90000000900000000009A009000CB0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDAC9000000000000000000000000000000000000000000000000000000000000000000000CFFFFFFFFFFFFFFFFFFFFFFFFDAD0C009C00900009000000000000000009000000AC90B0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000CFFFFFFFFFFFFFFFFFFFFFFFFBAF0B009A00000B0000000000000000090000000C900A0C0FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFBCD9000000090E000C00900000000000000000E09A00900BCBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9A000000000000000000000000000000000000000000000000000000000000000000EFFFFFFFFFFFFFFFFFFFFFFFFFFBAE9A9E000A009009A0000000000900000B0090000A0C000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBCD00000000000000000000000000000000000000000000000000000000000000000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB0C0090C09000A0000A000009000000000000009C0A90E9EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA0B00000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFF9AD0B00A0900E000090090000000000009000009000900C9EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D9C000000000000000000000000000000000000000000000000000000000000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBEDA900000000090000000000000000000000900A000A90A0BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A9E090000000000000000000000000000000000000000000000000000000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDB9CA09C900B000AD0A00000000009000000A0000C9000C9C0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF0090C00000000000000000000000000000000000000000000000000000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFB90CA00AC0000000000900000000000090000900A0C0A00FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E90A9A09000000000000000000000000000000000000000000000000CFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFBDADA9000000090000900000000000000000000009009000FCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9CAD00D00A90000000000000000000000000000000000000000000CFFFFFFFFFFFFFFFFFFFFFFFBEFFFFFFFFFFBFDAD000090090AC0900000000000000000000090A0000A09E0BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDA9009A0A900D0E000000000000C00000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFB0B9000E0000000A0000000000000000009000000D00000D0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF9E9E0D0C0F0A9009C90000C90CA000C0000000000000000C00DEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FC0E9A00000900000900000000090000900000900A00C0A0EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA900B0B9A0D0E0F0A0CBCBADA90C90000CBC9000000000CBFDAFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFB9B0000900000900000000000000000000000000000909C9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9CAD0C0C0F0B090B0DBE9FDA9BCBEFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFCFFFFFFFFFFBFFB0DAC09000A000A0000000000000000000000090009000A00E0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDA09A9A9A900C9E0C9A0DAB0BC0F9FFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFBFFB09000000900000900000000000000000090000000000009E9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9DBC00C0DADA9A9A90DA0DEBDB9A9CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBEFFFFFFFFFFDBDE9E9A090000090000000000000000000000000000090000C0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA09ADA9A009C0C90E09EB9E9E9DEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFFFFBFEB9A900000000000000000000000000000000000000000090BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC09E0090C0F0A0B0E90F0DEDBF9EBFFFFFFFFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFFFF0FFBFFFFFBFFEFB9E900000000000000000000000000000000000090000000C0CBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F0090E9A909C9C900F0FFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFBEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFF9FBDBC9E09000090000900000000000000000000000000000000A9ACBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9E0E900DACA9A0ADA9E0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBCFFFFF9FFFFBFB09A009000000000000000000000000000000000000090000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD09090E9A909C9C900CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFF9FFFFFFFFFFFFFFFFFFFFFFFFFBFFFFBDFFEBF9E9E9E9000000000000000000000000000000000000000000000FAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDADA0E090C00E0A0ADAF0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FEFFBEFFFFFFFFFFFFFFFFFFFBFCFBFFFBFBDFFBF9A900009900000000000000000000000000000000000000DADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB90909ACB0B0909C909CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E90DADFBFFFFFFFFFFFFFFFFFFFDFBCF0FBEDFBE9E9AD00000000000000000000000000000000000000000000000ADFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9E0C900C0DACA9E0A9EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBEB0DADF0FBFFFFFFFFFFFFFFFFEBFBFFFDBBEDBF9F9A9090000000000000000000000000000000000000000000ADAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0B09A0ADA9A090009C0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9CA9A0F0DEBEFFFFFFFFFFFFBFDFCBFBFFDBBDBE9AD0A00000000000000000000000000000000000000000000D0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD00E90D0000D0E0BCAF0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDA0A90CBCBEBDFFFFFFFFFFFFFFFBEBFDFADBFCBE9BDA0900000000000000000000000000000000000000000000ADADBFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA9E900A0A9E9A90D090DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F9CB009009E9ADBFFFFFFFFF9EDBDAF9FADBBF9E90900000000000000000000000000000000000000000000000BCBDBDEBFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFF09A9C09C000CA0ACA0ACDAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9E90DA0BC9ACBEDBEBFBFFDFFBF0F9AFDBFCBCB9E9A90900000000000000000000000000000000000000000090C0BCBFBDBF9FFFBFFDFFFFFFFFFFFFFFFFFFFFFFBFFFFFBDB0F0C0A9A0BCB09C909C9ABFFFFFFFFFFFFFFFFFFFFFFFFFFFEF9FDA9E09C000C909ADBDEFEBFBCF0F0FD9AD0BDB9E9AD000000000000000000000000000000000000000000000009ADAF0DAD0BCB0F9EBF9FBEDFBFFFFFBFFBFFFFFFFFBFBFEBC90B09C0D000C0A00E9ACDEDBFFFBFAFEBFFFEFFFBFFFFFBFDBEDA9E99A90B09ADADADE9FBDFCB0B90F0AF0BF0BCB090A0000000000000000000000000000000000000000000000000909A09ADA9E9E9E0FADBDADAD0F0F0F0F9CBC9E9CBC9090A00E0B0A0F0B0DA90009A9AC9E9CBD99E9ADB0F0F0F0B0F0BCB0F09A0C000C000000A9A0DAB09C0E90BD9AD0FCBC9E90900000000000000000000000000000000000000000000000000090000000000900000A009A09A09AD0A09A00B00BCB0C9C90C09C90C0900CBCA0D09A09A00ACB0F0ACB0F0F0F0F0F0BCB0F0D0B0D0B0D0BC90C9E9C0F0B090E0AD0B0B09B090000000000000000000000000000000000000000000000000000000009009000900900900900000900000900900000000B0A0A9A0A0A9A0ADA0090A0009009000000090000000000000000000A000A000A000A09A00A900000A9090BCBCBC0F0A9000000000000000000000000000000000000000000000000000000000000000000000000009000000900000000900BC009C90C90C90C090009C0090000000900090009009009009009009009009009009009000009009009000E0C09A90B0090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009E0ACB0E9ACA9E0E9E0A90000000000000000000000000000000000000000000000000009000000000090B0BC9E9CB000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090009009000900090000000000000000000000000000000000000000000000000000000000000000000090B09A9090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDFADFBCFBDEBDFADFBCFBDFBEDBF00000000000000000000010500000000000076AD05FE")}, + {empKey(6),ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E506963747572650001050000020000000700000050427275736800000000000000000020540000424D16540000000000007600000028000000C0000000DF0000000100040000000000A0530000CE0E0000D80E0000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF9A9FBCBFFD0000000000000C0BCF9BDF0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FCF0EFDE9A00000000000000BCB0FCA0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A9A9F9EBFC0000000000000AD0BCF0BDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0DACBE9F0A000000000000090BCB0BCBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0F0DA0FD000000000000000ADADE90FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC909A9F0A000000000000000000B0EBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A0C0000000000000000000BCBC9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0DA9E9000000000000000000009BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000B00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF900B0000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE00000000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC00000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000BEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000009AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000A0009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000A0BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000BF9ED0BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000900A0A9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF090FFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000B0E909000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000009A0A00000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90090F0DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000A0BC0900B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBC009090000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000090A9A0A00000009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE909A0DA9B0900DBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000009A090000009000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF090B09909009A90BCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A0000000000000BE9A000000009A9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD09A09090A9090909009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F0090000000000A009E9E000000000AA90FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00909D09A9909A090090009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA0F0A000B000000090A09A9A9000000009AF0BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09A9A09A900090900B00B0009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE000F009A0000000000000ACB0A00A00009AD0A00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0B0D09900909A00009009090900BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000900F00A000000000000090B0E9E900009A0BA90B0ACFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9090909B0090B00900B0090900A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0A9A0A0F0000000000000000A009A9A0A000009F0BC090900FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90B090B000B0009009009000A09909009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB0B000000F09A000000000000000000090CB000A0ADABA0A00009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0909A90990090000000009A09000909009BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0B0A0FA9A90F0A0900000000A90000A00A00B0000090A0C9ADA9A000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0909F0909A0090A909A090000900B000A9000FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A090F0B00E00AF090E0000000000A00000000B0000000A909A0000000A9C9FFFFFFFFFFFFFFFFFFFFFFFFFFFF0909A090B0D0B0090900900B0900900B9900B09FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A90BCAA9E9A09A0F00B0B0A900000000090000000A00000000A0B0B000BCBAA90FFFFFFFFFFFFFFFFFFFFFFFFF009F099A909A90BC00A09A90C9A9AC90000090900BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA090AC0B0DA0ADA000F0A0DAF9EBCB00000000000000900000000090E09A0090DA00009FFFFFFFFFFFFFFFFFFFFF09B09B099E909AC09A900000B000909A9C90B09CB090FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE90DA009A00A00F0A9AF0F000A90F0B00000000A00000000A0000000B0A9A0000ABA900900FBFFFFFFFFFFFFFFFFFF09B09B09AD09E09000000B00900B0A9A90B0909A909090FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A9A0A000A0DADAB0ADAC0AF0009AFAF0A00000000000000A09000009A00F00A00B0D00A00A9B0FFFFFFFFFFFFFFFFFF0B0DB0DA9B0B0900B009000900900D09CA9A900090B0DA9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A90CB090090A00000F0A0B0F00000090F0A00000000A00009A00000000BB0B00900A0BC00000F0B00BFFFFFFFFFFFFF0999A99A9DAD00A90090A090A00A09A0B090DA9B9A90B90909FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF009A9EBBF00000ADABADA00B00AF00A09A00009A0000A900900000B00000A00DACB00009A0B0A09A0F0B0000FFFFFFFFFFE9BE99AF9A909A90090A09000900900900B0B09E099090B0B90BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A000B0F0FFA00A000C0A9A0CAD0F0900000B00000000000000000BCA9A009AFA900000000BA90900BA000000BFFFFFFFFF9BC90BD9ADA9A900B009000B000900900909C9A9BCA9F09909B09FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE00A9000A9FBFFF0009CA9ABC0CBA9A0F0A0000000B0090A000A00A00B0A90090A9090A00A000B9E9A00E900000B0F0FFFFFFFA9F9BBF0B09090DA900900090009A09A9ADA90B090B9B099A9F09BDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC00000000ADFFFFF00000A90E00AB0000AF00000000000A00090009000000BCA0A00A0A000900FADA9E0A090A9000090BFFFFFFFDB0BC90BCB9E9A909B90B0B0909009009090B00B0F09E9AD909B0909FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000A90000009AFFFFFF00000A000B00A9EB0F0000000000000000A00000000B0B9000B09000A0A90BADA909A0BCA00000B0BFFFFFFBF9FBAD9B0B909A9C0B0909A09A9CB9A90B09B0D90B09B9A9B09B9B90FFFFFFFFFFFFFFFFFFFFFFFFFD0000A000000A0009BFFFFF000A9CA9A0F0E00CAF00B0000000000000000A000000BCA00900A000909EBDBA9A0009A09000A000FFFFFF9F9A90DB0F09A9F9AB99A9B0D9A90B09D0B09AD9A0BC9ADADBC9AD09A9BDFFFFFFFFFFFFFFFFFFFFF000A0090000A090000AFFFFFF00090A90CA009ABA9F0000000A000000A0000000000BDA9000AB0B00A0A90A0000000A09A00000B0BFFFFFBCBF9FB0B09F9A9AD99E9F090B0DB09A0B0FA99A9B90B9099A9B99BE999AFFFFFFFFFFFFFFFFFFF0000090000900000000BDFFFFF0000AC0A0A90A0E0C0F00A9A0000900000009A0900000A00A0B000000000A9000000A90F0000000009FFFF9FB90B09F99B09DB9BEB99ADBB09B09BDB9999DA90DA99EB0F9F0B099B0F90FFFFFFFFFFFFFFFFF0000A000A000A00000000BFFFFFE0000B0CB00E9A90BAF09009A090A00A09A000A0A000B0B0000BFA9A0B090009A00000A0BCA090A000FFFFE9EBD0B09AD0BA9AD99EB9B90DB09F09A9AFA9A9B9A9E090B0B9F0F099B9B9BFFFFFFFFFFFFFFFE0009000000A0090A00A000BFFFFFA0000B00F0000A000F000A0A9AE90B00009A0900000000000B0A9A9A00A0A000009009FA900000009FFFF9B99ABDB09A99DB9A9A99E9AB0BDA9BBDBD99BBD0B909B9F090A9B9B9E9ADA99FFFFFFFFFFFFFF000A000A00900000000090FFFFFF9009A00A00AB0E09E0F0A09090A90A009A000000009000000B0F9B0F00F0900000000FAA90A9A09000FFFCB0FAD900F99B0A909099A99D9990B9D0BB0BBC90BDA9B090B9F9BDADA90B9B9EBFFFFFFFFFFFF0A900000900000A00000000BFFFFFE0000CAD0BC0090A00F0000A0AD0B0B0A0FA00A0000A00A000B0BEB0BB0CA000000A9AD9A00000A000BFFBDB990B9B0A9C9909A9B0D0B0ADA9DA9BC9BC9B0B00900F0BC9A90B9B9B9DBDBBDBFFFFFFFFFFF000A09A0000A0000090A00ADFFFFF9000A90A0A0FA0A0A0F09A09A9AB0A09A90B0900A000B009000B0B0BCBB0000000000BAF0F0B0000A00F0B0FB0BC0999A9A9E090D0B099099A99A9B90B09F9B9B99099B09F90F0BCB09AD9B9BFFFFFFFFFFC0000000A00000000A00009ABFFFFF00000B009000F0C90F0A0000000F0BADABCBAF000F9ADA00A009A9AB0FA00000A00BDF090000B0900B9F9F90F9B9A9E9B0999A909090A9009099090B99A909E9A9B0B0B09AF09F9F9B9BBCBDBFFFFFFFF00A90A00900090000000090ADFFFFFE0000E0A0E0B00A0A0FA9CB00B0B09A90BCB9E909B9ADA9A090BAB0F0F090000090BEBA00A09A00A0B0F9A9AB9090DA9909B0A90B009090099A9E99B09E9A9B90DB09DB9DB999BB0B0BCBDB9B0FFFFFFFC9A00E90A000A00A0B000A000BFFFFF0000B00C09A0CA900AF9AB0B00B0BADAF0B0F0FAACAFB0A90A0090B0B9A090A000ADBC9A09A009A9BDB0F9F9CB0A9A9B0BD09990909A009A00909B0DB99B9C9A9B09A900B0F0BC9DBD9B9B9FBDBFFFFFF0A9A90A0000000000000900A9FFFFFFF0000A0B0A00A00E90F09ADADA0C90B0BB00A9090B9ADBDABCB0AB0B0A9E0009A00A9A00000B0000EBDB9A9B9990909090B9ADA909090009090909B09A90B9B0909A9B99B99B0B0B0BE9E9F0BB9BFFFFFAC0CA09E9A000000000A000000FFFFDA0000D00ACB09A00A0F00A9A009A0ADBC00000A0000F0A000B0A90B0ADA9ABDA0090009000000A0F9FBCBDB0BE9BCB0F9B9099090A00090000A90909BD9BDA9DB0F90DA90FA9F9BDB99BB9BBDF0FFFFF00B0B0F000000000B000000000BFFFFFF000A0A0090E009A00F009009A0009A00B0A9000A0B0A90BB09B0A0090B0BDA0D0A00A0A00B0090B0B0B9A9F99009099AD0B9A9A909090009090B09B09A9A99A099A9A9DB999B0B09EBC9DBDBB9BFFFE0F0A0A0A00009000000000000BFFFFFF0009009CB0A000A09EF000A00000000B00000A09000000A0CBA00900A00BFADBA00009E9B00A0A9FBDBDBDB0B0B9A9A999B9C9090909A909A0090B0DB99B9B099A90999A90BE9DBDB99BAB0B9FADFFF000AD0D090A000A0000B0009A00FFFFFFA000E0A00E00B0C0A0F0000000900B00A0B0F9A00000000B00DBA0B000009BE9F0B0B09EBF0900B9CB9A9A9BD90909DAB0DBB9BDB9F9DB99D9B090B09E9CBDB0F0BDA9A9B999BA9A9E9D9F9FB99BFFDB0B0A0A000000000000000A00009FFFFDF0000B0A90B0CA9A9AF0000000000000090FBADA00A00000BBA09A00000A0F9A0A000BFBDFA00BDBB9F9F9F0B0F0B0B9DB909ADB9E9B09F0B999B99B99B99A9B990909D90F0B09BDB9A9A9A9BEBFFFE00CBCB00A0900000A0000000009AFFFFFA00000C0AACA0000C0F0009A00A0000090BBADB0B000009AD009A0000B0DB0000900BFFFFADA9F0F0DA9B9A99909C990B9ADBDB9F9BDBF9B9E9F9CB90B09A9909A9A99A9B9BD9F9A909BDBDBDB9FFF00AB0A0000000A00900A0000000AFFFFFE0000B0A9E90090A0BAF000000000B000A0ADBEFBC0900A00BAFA900000A00A00000BDBFFFDA9A9B9BB9BCBDABA909A9F0B9B99BDB9F9F9F9F9B0B9C9BDB9DA9F09090B9ADA9A9A99BDB09A9A9FBFF09AD0E90F0BCA900000009A000009BFFFFFD0000D0A00A0AC9AC0F000000900000000B0BB9ABA0000000900A0B0009A09000000ADBFFFFDBDA9CBDB9B99D00B09099C90F9F0F9B999BDBDBDBB9B0B0BB9A9B0B0F099B9F9B9F0B09F09F9B9BFFAC0A9AE00A0BCACA0A0B00000A00ADFFFFFA0000A9CB09A00A0B0F0000000000000900B0DAF09A0090B0A0900000B0000000000BFFFFBFA9B9B9B0BDABB0B909A0B0B9F9B09990090099BDB9D9F99F909D909C909B0B9B9E90BD9B09B09E9FF090B0E909ADBCA9090000CB0A09000BFFFFFD00A00AA00E09A0000F000A900A0000000B0BAB9FA000000000A000000000000000BDFFFFEF9F0F0F0BDA9D090000090909090990BC9A090009BDBB09B0B9B0A9A9A9B0F9E90B9B90B09B0DB9BE0ACA0E9A0E0A0A90A00000A0000000BEFFFFA00009E90CB00E00FABF0000000009000A90A90FB0FB000000900000B000000A00000BFFFFBDA9B9B9B9B99A9A900900000090C0A90B990F0B9090D9F9D90D0990909A9B9B9FB99C9B9BC9B0BCB90009A9E0B09AD0A0CA09A0900B00A09FFFFFF0000A00BA00B09A000F000000000A00000B0A90FBF0FA000A0000000A9A0000900000DBFFDBDA9E9ADB0FB90DA9A000000009B90F90E9A9BCB909099A9A9B0A90B099CB9A99BEB9A9C9B0999BCB0B00CA9E0AC0A9C9000000A00000000BFFFFFF0009A0CA9E0AE0BE0F0090A0900000090A9A0BBCBF00900000000B0000090000000BFFFFFB9BDB9DB0B909A909090000000000B00B909099BE90B00909009000090A99F9BAD99F9B9B09F0B0B9A0CB0BCB0D0B00A0A0000000000000ADFFFFF0000ACB09A0B09A00BF000000009000A0A9090BCBF0B00000000B000900000000009FFFFF09E9A9AB0BD0B0909000090B09A909099000090BC90B09000009009090B99B09F99BBA90B0DB090B9C09A0BCBE9AA0DA0909A000000000009FFFFFE0000090EACB0CA0DA0F000000A00009009A0A90B0B00A0A090000090A000A000000A9FFFFFB9BDBD990B9DA9A9A9A9090BC90909009099A90B009000000000000A909ADBB0BB0D99F9B099B9C9B0A9ADADAF9CBADA0E09A0000A00000AFFFFFF000B0AB09AC0A90A0AF000009000A0009A9B0A000DA900900A0000A0000000000009EBFFF9E9A90B0F90B0909C9090B099B9A9A990B909009090A909090000000909A990F90FBA9A909B0BCB9A9B0CBAFBCAFBCBADA9AC009009000009BFFFFFF0000C0A09A90A0090F000B0000900A000A09A9A9A9ACB0AD00B0F0900090000A0009FFFDB9BDBB9B90B9B09A9A90B09F0B9D990BC90F09909099900B00A0009009E90B99BB999BDBBD09B99A9C9A90DADBDACBFCB0FA9A00A00000000FFFFFF00000B0CA000E0BCA0F00000A90A0090A9B9E9C9E09090C90BC0900A00000090000BFFFBA9F0B0D090BD090B0999A90B09E9BB0B9B9B99B09A9F9A9B909000B00B090F9ABC99AF0B090B9F9AD9BADAEBFEBEBF9EB0F0C0000000A00B0FBFFFF00009A00B0F0A900A00F0A0090A009A009A0A90A90B0F0A9A009A0BC090A0A00090A9BFFFDB9BD9B9F90B0BC9B0F09E90B99AD9F9E9F9FBDBBDB0B9D009009009090B90B99B0F99B99BBDB0B9B099ADBCBFDBCFEDFE9AB000A9A090000BFFFFFF00000F00A000A0B09AF009A00900000A0909A90A900090090B0900B00090900A0900AFFFBDA9B09A9A99B9B0990B09BD0A99A9B99B0B9A9DB9BDBA9B0A900900B090B99E99B0B09AD09A9BDB0B0F9ABFCBEFBFBFADAD0F000000A00000FFFFFFF000A0BC0ADA000E00F0000000A909A90ABA0009009A0000000A0B00A0000A090A0BDFFF9A9F0FB999F0909B9A99B090BD0BDB0DA9D00DBA9E909D0099CB0A9A9DA90DA9BA99F9BDBBD9BCB0F9B09FDEBFFBCFDEDADAA0000000000A09BFFFFF000A000A90A09EB00AF00009A090A090A9009A000A000009A0B09009090A0090A909FBF0B9F9B90DAB09B9A9E9CBC9AB90B0909A90A99B0D99B9B0B9B0B099909A909A990D9A9A9A990B0B9B9BC9BFEBFF0FFBEBAF0F09A00A0000900FFFFFF000090FA90A90A000B0F00B00CB0B0B0ADA0B0900900900A099C000000A0090A0000BEFFBDB0B0B9B999BC999B9B9BBD0F90B0B90909A009B0B0BCB90C9090F0B909B99A9B0B9B9F9A9BDB9BDA99B0BFFFFFFEFFDF0B0E000009A00000BFFFFFF0000A000E00E0B0E00FB000B0BCF9ADB0B00A0A009A00900A9B0B00B009000090009BDF9A9F9F9F0BDA9B0F090BC99B9A9F99CB0B0090B09B0D090B9B0B0B0B0F90DA99A990F090BDA9A9F0BDABCB0DBEFFF9FAF0F0B9A09000000A000BFFFFFA0009A0B0B0900B0B0F09A909FBBCB00B09B09090A90B00900000000000A000A000A9FABDB9A9A9B9A9F9B9B9F9BBEBDB9A9A909C0B0C90F09B0B9CB0BDBD99F9A9A9BC99A99BBB90B99B9B9B99B9BAFDBEBFEDBE9ACAC00A00000000BDFFFFF000A0AD0A0A0AC000AF9F09EBE9DA90B00A000B0A90B00B0A90B00B00B0009000A99FB9DB0BDBDB0DB990B09A90BD99ABC90D00B09090B090B0BD0B9F99A9BA909B99099E99A9C90B9DABCBCBCB0DADFBEDFE9BE0BCB09A00000A9000FFFFFF00000D00AC09A9AADA9FA9A0B9BFA90B0F09A90009AD090009000000000000A09000FBCBB9F9B0B9BBDAFB99F99B0BFBD9B9A9A900B00900B0D909F9F9EBDA9DB9B0DA9B09AD99B9F90B99B9B9B99B0B0FFBCBFE9F0ADA0000900000B0BFFFFFF0000A0B0B0E0090A00FBE9B0F9ADA00B0B00A09009A9A9000B09A90B00B0000A00B9FBDA9ADB9F0DB999BFA9A9C990BBD0900909009A90909A9B0BB0B9B9FA90B09A909B99A9A9A9BB0F0BDBA9F0BD0F0FFBCBDADA9ADA00A0A00A0000FFFFFFB0009AC00B0A0E09A0F0900F0BCB09E0000B00A00909009A900000A000009090090BF9B9F9B9F9BB9FBAD99B99B9A9900B0090A090900A09A90DB9DBDBDB99BBD9B99BD0B09BD9B90DB9B9B0F9A9B0BAFBCFBFAFA9E9A090000900000BFFFFFE000A00B0A009A90AC0F0B0B0B0909A090B000000B0DA090090B0B0909A9A0A0A00BF9B9FB9BE9BF9FAD9BABD0B009D0B900000900A009090090B9FAB9ABA9BC90B09A90BDB9A9AD0BB0B9E9F99F9E9ADBCFAF0F09E9AD0A00000A09A0BFFFFF900000F0AD00E00A0BAF00000000A090A00B009000B09A0B0A900000A000000909ADBBCBB0F99BF0BB9BF9D9BD99B9A9909000009909000090090099DBD9D099BB9BD99B99AD9B99BD9BDB9B9AB0B9B9AFFBDAF9EB0E9A000A9A00000BDFFFFFE0009A00A0AB00AD000F0090009000A0900000A0009AD9909DA909A09A9A0B00A09BDB9F9FBFBDBDBDBDBBB0B9A90990BDB09000A0909A900000900B09B0B9A909D0B0BDA99B0DBA9A9A9BCB9DBDA9E9DAFCBDAF0F9ADADA900009A000ABFFFFF900A0F09000A90A0B0F00A090A09090A000000090A90A0B0A90A0090000900B00BEFBF9B9B9FBFBFBFB0F9F9A9F9A0B9A99A9F999A990000A99BDB09A09099B9B0B9B9A9BE9BB9DB9F9B0B9AB9B9F9B0B9BFAF0F0ADA9A00A00A0000ADFFFFFE000000A0EB0DA09AC0F0900000000000000A09A0A90B09009A009A0A09A00B009F9B09BFBCFBDB9F9F9F9B9BDB90999F9FA9B90BC90B09A990B09099099B0F9E9B9F9F9FB99BD0BB0B9E9BDBDA9F0B0BD0F0BDA9ADABCA0B09A90A9A9BFFFFF9000BCA9A900A00A00AF0009009000A09009000909A09A00B009A0909A09B009A9BBDBF09FB9FBFFBFBFBF9EB90B9B0BFB99D0B9B9B99F99BDBD9B90A9BC9B99B0F9B90B9DBE9BF9DBDB9B9A9B9B9B9F9BB9BCBADA9E9AD00A00000000FFFFFFE000090E00EA90F00B0F00A00000B09000A00B0A9ADA09A00B009A00A9A00B000B0DB9BFB9FBBDB9F9F9F9F9FB9F0D909FFAB9BC9B0FA9B0B0BBF0BD909B09A9B9B9EBDBAB9BF9BB99A9BDB9F0F9E9A9BDBF9BC9ACB0F9ABC9A00A00000FFFFFF0000A90B090E00BC0AF0900900A0000A09000090B09A09A90B0A09B009A90B0BDB9AF9B9FBDFBFFBFBFBFBB9FB9B9A9FB9F9FDBBDB99F9F9FBDBF9A9909BDBDBDBF9BB9F9FB9E9FABDBCB9FB9B0B9F9FADBBDBE9A0F0AD00A09009A09BFFFFFB000A00A0EA090A0A90F00090A9090A9090A90B0B09A09AD0B0B09A00BA9A0009B0BF9B9FBDB9BF9F9F9F9BDF09F0B99BDF9FFBBDBFBFBFBFBDFB9909A9B9B9B0BB9FBDF9FB9F9BBD9BB9B0B09F9BDA9B9B9F9A9ADB0ADA0A000A0000AFFFFFFC0009CBCB009AACB00AF0B0A9909A900000000090A09A09AB0B09A09B0D00B0BF9F9BDAF9FBBFDBFBFBFBFFBBFBBDBCB9BBFBBDFBF9F9F9F9FB9FF0B99BDBCBF9F9F9FBBBDBFBDBDBBC9F9F9FB0B0B9F9F9F9FBDA0E9FA90090000A090FFFFFFB000A0A00B0E09A0AD0F9099EBB09A9B9A90090A9DA09A090B0B09A00A0B00909B9FBBDBB9F9BF9FBDBDB9BFDBDBB9B9E9FFDFBF9FBFFBFFB9FB9090BDA9BBDBBBFBFBDFBF9FBFBBDBB9AB9B9B9F9A9B90B9A9B0F9A09EAD0A0A0900A09FFFFFF00009A9E0B0A09E0AAF0B9A9B0BF9000000A0A90A90A90A9CB0B09A90909A0BF0F9F9BDBEBFB9FBDBFBFFF9BFBDBF9F9B9BFBDBFF9FBFBDFF9DB9B99B9F9FBDBDBDBDBBF9FBDBDFADBF9DBE9F9B9DBCBBBDBDBF0ADAF09A00090A0900BFFFFFC000A0CA0BCADA00B00F90B9B9BB0BA9B0909090A9B090A9AB09A9A900A0A09F99BB9FFBF99F9FBDBBDBDB9FFD9FADBBFBFDFFBFDBFFDFDBB9AB9E9DABFBFBDBFBFBFFBDFBF9FBFB9B9B9BB9B0BCBAB9BD09B9B9E90B0DA0090A00000BFFFFFFA0009CB0F0A9A9EB0CBFA9AFBBCBB09000A000A0900A0909099A9A90B09090A9B9F0F9BDBBFBF9BF9FBFBFF9BBFB9BBDBDBB9FDBFFBFBFBF9FBDB9BBDF9B9FBF9F9FBDBF9FFBF9BDBDBDBE9F9F9B990F9BBBDAF9BCADAA900A00090A09FFFFFFD000A0A00BCACA0CBA0F9090B9B90B0B0900B090A9090A0B0AA9A9A90B0B090BDA9FBFBFDBF9FBDBF9A9B9BFDB9FF9FBCBDFBFBFFBDFFBF9FBDBFBDBB9FFBF9FBFF9FBFBFB9FBFBBDBA9B9B9A9B0BF9B09DA99BCBB0BD0E900000A0000AFFFFFE00009CBCA9A90B009AF0A9A90B0B0000A09000090B0A900B999A9E9B0B00A9F09FB9F9BBF9BBDBB0F9F9E9BB9FB9FF9BFBBDBFDBFFBDBDBF9BF9FBDBFB9F9BF9FBFBDBDBDFBDBDFBDBF9F9F9F9F90BDB0B9FBDB9CACAF00000B00000B9FFFFFB0000A0A09E0AC0AF00F09000A00000000000000A000900B90AA9B0BA9B0B9B9B9B9FBF9F9BFDBB9DB9B099B0FB9FB9BF9BDBFBFFBFFFFBF9BFDBBDBFBDBFBF9FBBDBFFFBF9BF9BB9BF9B0BB9B9A9BDB099FB0B99AB9B00A0000000900FFFFFF0009AD00DA0090A900FFA0900090000000900009009A00B00B99A9BB9EB9A0F90FBFBDBBFBDBBBDAB9A9B9AD9A9EBDAF9FBFBDFBFDBF9BDBBDBBDFB0BDBDB9FFBDFBDB9BDBBDBBFDBF9BFBD9BCB9A9B9F0B9BD90F90E0F0090000000A9FFFFFFC00000B0A09E0B0E0A0F9A0A90000000900090A09B09A90B00A90B0BA90B9B0B909FBBFDBFBFCBBDB9F0DB9A9999B9F9B9D0FBBDBFFFFFBDBFDBB9FBDBBBFB9FBBDBFFFBBDFBDBBBDBF99BBE9B9F9E9A9F9FBBB90BDB0A00A000000000BFFFFFBA000AC09A00AC09A90FA9900000B00A000A0090A00B00B0B09AB9BB9BA00BDB9BFBDF9BF9F9BF9B9A99B0990F0B0B9BDBBB9F9FFBF9B9FBF9BF9FB9B9F9BDA9F9B9B9BDFBBDBDF9BF9EBD9BBDA99B9FB9A99C99B9A0F0F000000B0009FFFFFFC000A9A9E09A90B00CAF90A00B0B009000090B0B09B00B0009ABDBE9A099B099CBF9FBBF9FBBD9F0F99B099A9090D9BE9A99FBFFBDFBFFBF9AF9EB9F9F0BCB9B9ADBDAFB9BDBBB9BF9BB9BFBD9B9BE9B909009BE9A9F0B00000B000A9AFFFFFF00090C0A09AC0A0E0B0FA09A00009A000B0A00B09A00B0A9A009A9BADBA00B09B9BFBFDBE9BDBBB9B0B09E909A90B099F9FB9F9BFBF9B9F9FB9B9F9BA9B9B9BCB9A9B9BDBFBDBDBF9BF9FB99AB9E99F9FB09B099F9A0F00A9A000A90A9FFFFFFA000A9E90E09AD0B000F9A0900B009A00000B00B0BBA09000B0A9A9B00B0BD909F9F9BF9BFBBDADBDBDB90B0909090B0B90BDBFF9FBFFB9B9DBF9BD9B0D909B9BDB9F9FB9BDBFBDBFDBB9FBF9F9BB0BB09F099A99F9B0F0000B0B0EB0A9FFFFDF0000A0A90AA0A00BE0F0900A90B0009009000B0B0090A0B0009ADA0B0090B09ABFBFF9F9909A9B0B0000909A90B09090B99BB9BFBDB9FADBB0B9A9AD9B0BD090B9AB9BDBFBBDBBF9BF9F9B9B9BDBDBC9B090009A90F0B0B0000ADB0DBFFFFFFA00009E9E0D09CBC00BF00A090A90B00A0000B0009A0A90000B0A9A90A9AB0909F9BF9AB9A090909009A900000000B09B9FAD9FF9FFBF99BB9DB0DB90B0900B9F09BDAFBDBFDBBFDBF9BBE9BFBCB0B99B090A999F9B0B0E00A0A9A0FAFFFFFFF000B0EB0B0A0AB0A0B0F9009A99E009090A00009A09000000000900A9000D9009BFDBFBDB9F000000000A00000090000D099BA9BFB9F9BF9DAB0B9009090B9909BDB9B9BBDBBFDBBBDBF9BF909B9B0F00B00900B9BCBCA9000900DBE90FFFFFFC00009EBCB0BC0A9AC0FA9000A909A00000000A09A00000000000B00A9A9AB09A0BF9BDB090B00000009000000000009A9A99F9F9FFBF9BA99D90B9A900000A909B0BDBCBBDBBBDFBBF9F9BFBFB9F9B9009009009B9A9E0A9000A00BEBBFFFFFB000AD9EB0E0B0CA0B0F000A9CA9A09A900090000000000000000000000090909DB9FFF0FB9090000000000000000000990DA9BBFB9B9BC9B0B0000009A90909A90F9BB9BDBFDFBBDBDBBFDB9B0F09F090009099F9A9E9A00A0000B000FFFFFFC0009AE0BE9A0A90F0AF00090A90090000000000000000000000000B000000000BBFFB9B9909A900000000000000000B009B9BBDB9F0F09B0B9000090000000000B9B0DBFBF9BBBDBFBBF9B9F9F9BB000009090B09F0BC00000000009BFFFFFB000A0E9BC000F00A000F09A0000000A000000000000000000000A90000B0000009EB9F0FADB90B0909A00000000000909B090D9ADB9B9B09D0090000B0000000900009A9909BF9FB9F9F9BE9BB0B0DB90090B0999A9E0B0B00000000A0FFFFFF00009A0A0A9A0AC9ADAF0000A90A9009A00000000000000000000000B0000000099DBFB9BB0F90B0B090D99A90900900009A9A9B9AD09DB0B90A000000000000000000000B090B9EBBFBF9BF9BDB9B09B9A90B0F990BDAC000009A0009BFFFFFF0000D0CB000900A000F0090000000000090000000000000000000A00000000000FB9BDBD0B9A999090B000900A09A090909990D099B0B09009090000000000000000009A99B9FB9F9BDBBDBBCB9A9FBC99F999B09B0A90A0000000000FFFFFF000A0A0A00E0A0A00A0F000090A00A00000000000000000000000090000000000BB9E9FA9BF09DA09A909A90090900909A9AD0B9B9A0909009090090000000000000090099ADBB9BFBDBBDBBDB9ADB099B090FB099E0BCB0000000000BFFFFFE000900B00B00CA9CB09F000A000090000000000000000000000000000000000000DBBF99F09B0B9B0DA90900B00009000909A9900099B09A9A9A9009090909A9A9A9909B0B90BCBDB9BDBBF9A9E90B9A90B9B9090B9E9AC90A0000000BFFFFFDA000E00AC00B000A0A0F0A90009000090000000000000000000000000000000009AD9FA90B0DB0D0B090B099090B00B0909090B9BDA9090909090B00A9A9A9D09090CB09D0B9B9BBFFBBCB9BDB9B90BDB090FB0090B0E9A00000000000BFFFFFD0000BC0B0A0B0B0C9AF00000A00000000000000000000000000000000000000009BB090009A9B0B90B090A000909009A9B09B090909A90090009090909C90B9A9A9B09A9B9E9BC9B9ADBDBF9AD0AD0909AD900999E9A9CB0000A0000BDFFFFFA00B00A90AD0E00A9A0F00009000A0000000000000000000000000000000000000BE9F0000000000000000090B009A900909BC90B9B9C9A90A90B00909A9A900909909B9B0B9BDBBFBDBBBB09B0B990B9099B0909A9A9EA000B000A90FFFFFF000000F0ACB0A09ADA0FF090A0000900000000000000000000000000A000000000099B9F00000000000000000009A090B9CB09B9F909A909009090090B0909A9B0F0B9AD0BD9F0BF9BDB9DBDBE9900A90A9B00000B9ADA09A000009000BFFFFFF0000A09A9A0B0A0A0B0F00000090000A09000000000000000000000900000000000F9B0B000000000000000000090B90B99B09A9BD09B0B090A090A909A909A99990B9B99AB9F9BE9BFBA9B99A9A9009009900099E90FBC09A00A00B00BFFFFFF0009ACB0F9CBCB0F0BF000900A00009000000000000000000000000000000000009ADBD00000000000000000000BD0B90BCB9B90B9B090D0B0900900909E990B0BF9E9AD99A9BF9FB9BDBDA00000000000A099DA00A00B0000900000BFFFFFE0009E0B0E0A0A00E0ACF000009009000000000000000000000000900000000000009B9A9A000000000000000009090B9EB9B9D09F9BCB09A90900B00B0A909CB9F0909A909ADBD9BBDADB9A90000000000009B0B00BCBF0A00000B00BDFFFFFB00000B0F9A9E90B0B0BF090A000000000000000000009000000000A00000000000000F9F9C000000000000900A0F9B0B99F9AB9A9A999F90B00B00900090B0B0B0B0B090B09B9ABF9B9B9B900000000000000090000B00D009A000000BFFFFFF000A9E00ACA00AC0AC0F0A090A90A9A90A900000009000000000000000000000000009A9AB90900000000A009909AD9EB9AD9BDB9DB0B0B90900000000000090090000000009F9F9E9BCBCB00000000000000000090AFA0B0000000000FFFFFCB00009E9A90AD0A90B0F009000000000000000900000000000000000000000000000099F99ADA9A9F0F0B99B0F9B9BB99F9BBDA9B9A9F99E9A9000000000000000000000009A9B9BB9F9B90000000000000000009A0D09E000000B000BFFFFFF0000A0A00A0CA00E00AF00A0090090009000000000000000000000000000000000009A9A9E9B9F9F0B99BDAFDB9ADA9DBA9BC9B9E9BDB0B909000000000000000000000009FBDBCBDBB090000000000000000000AC9ABE0000000000BCFFFFFA0000090CBC0B09A00BCF0900000A009A090090A00000A900A009009000000000000009F9B9BDA9E9BDAF9BF9A9FB9DBAD9F9BBDB9F9B9F0B9F0990000000000000000909A9A9B9B9B0D9A000000000000000000009AD00B0000000000BFFFFFF000B0E9A0A00A0CB00AF00090A900A000A00000900000000090000A0000000000000009ADA9BDBBF9BDBE9BF9F9FBBDBB9BF9BADB9F0B9BDA9BA9A900900900000000009DBDBCBF9F9A9000000000000000000000A0ABC0B0000000000FFFFFDB00000A009AC0A00E90F00A000000909A900A00000900000900A00000000000000000A99B9B0B9DABFB9FBF9BFB0F9F9F0F9BDFBBCBBDBDB9F0DB9F0B00000090000B9FAB9B9B99A9A900000000000000000000009E9CB000000A0000BFFFFFE0000F00DA009A00A00AF0000A90A00A0000009000A0090B000090090000000000000090BC9E9F0BBDB9F9BDAFB9F9FA9BF9BCBB9DBBDB0B9FB9BBCB9D09ADA90009B9EB99E9F9BE99000000000000000000000009A00B0E0000000009FFFFFFB000A000A0C0A0DA90E0F090900909000909000A090000009A00000A00000000000000090B9909BD9BDABF9BF9DBFB9FBF9FBB9FBBF9BF9FB09FADBDABBF9B90FBDB0F99FB9B9E990000000000000000000000000ADA9E09A000000000BFFFFFC00090E90A9A0A00A09AF00A00A0000BCA0A000000090A00009A0900000000000000009A90B0B09A90B9DAF9BBBDBDB9B9F0F9B9CB9E9BBDBBF9BB9BDBD9FBFB9B0F9BFB0BDAB9B0B000000000000000000000000000A9AC00000000000FFFFFFB000A00E00C90AC0A00F0009000A000090909000A000900000000000000000000000000000090B09F9FB9BF9F0B9ABDBDBB9BDBB9F9BDBA9F9BD9FBF9AFBCBDAF9BBDB0F9B9F9E900000000000000000000000000BAD0F0B000000000BFFFFFE000009A09A0A090B0CAF090000900B0B0A00A0B0900A00A90000000000000000000000000000009B0B09BDB0B9FB9DB0BB99E9ADBBBDBB9F9BDBFA99BFBDBDBF9FBDA9F9BCB9A900000000000000000000000000F0C0B0A00000000A9FFFFFF9000BCA00A0000CA00B0F0A0A0A000000909000000B090000A0000A0000000000000000000000000099BF9ADBDB9C9A9B09CB99B99C9A9DB9FBA9BDBFB9DBBBB9BA9BF9B9B99F90900000000000000000000000000B0B0E9000A000000BFFFFFE0000900BCACA9A0E00AF00909009A9ADA000900000000900000000000000000000000000000000000090B9B9A9A99909F9B0B09A9B99BADB0DBF9B9BFBBDBD9F9DB9BDBCBB0B9A00000000000000000000000000A0F0B0AC0000000000FFFFFF900A0AC000900090BC0F00000A90000009A0A09A9A9A0A0090009000000000000000000000000000009FBCB09090A0B090099F99B0BC99B9BB9B9FBC9DAB9AB9BBBCBB9B9DBDA90000000000000000000000000B0F00AD0B000000000FFFFFFE000009A0B0A0E0A000AF00B0A90000B0B009090000C0909A00A00000000000000000000009A0000009A909000000090909BDA90B0D9B9BADBDB0FB9BBBB9FBDAF9DBBDADBA9A9000000000000000000000000000F00BDAA000000000B9FFFFF9000BCA00CA009AC00A9F0009000A900009A000A00A9A0A0000000000000000000000000000090900009B000000000000B09099BD9AB9AD9B0BDBBCBF9F9FB9BB9ABF9B9B9DB9000000000000000000000000000B0B00A9C0000000000FFFFFFE0000000B00DA00B0BCAF900A9E000A9AC090B09090090DA900000A00000000000000000000000A90000000000000009009A9A90B99BDBB9FB9BDB9B9F9B9F9F9F9B9E9F9AB0F9B0000000000000000000000000CA0F0CA9A000000000AFFFFFF000A9AC0A000A000000F0090009000009A0000A000A0A000A0900900000000000000000000009009A900000009000000090990B0BCB09CB99E9B9FBDABDA9A9B9BDB9B0BD990000000000000000000000000000B0F09A9E00000000009FFFFFB0000C09A09A0D0E0A9AF0A00B00A9009A00B0B09A90909A09000000000000000000000000090A090000000000000000900B9CB999909BB90B9B0F90B9DBB9FBF0FB9BDB9A9A900000000000000000000000000BCB0AA9E09A0000000BFFFFFFC00B0B0A0E00A0A090E0F0090000009A0000000000000A09C0A0000B000000000000000000000090B000000000000000A009A990F0B9F909BDBDB9BBDBBBDB9B9BB0F9AD0909A000000000000000000000000000BCB0DA9AC90000A000FFFFFFA00000C009E00090A000F90A00A90A00B0BC9A9E909A900A90009A0000000000000000000000000900000000A0009A00900090A99B0B0BDB0B0BDADBBDB0BFBCBDB9BB9A9A0090000000000000000000000000000BCA09E9A00B0009A9BFFFFFF000A0B0A00BCAA0CB0AF00909000090C000000000A00E900A9000000000000000000000000000900090909A9000090000090B99A9D9B9A9F9B9B9B9CB9F999B9B9F9CB9090000000000000000000000000000000E9BCA9E9EB0E9AC00FFFFFFF0009CAC9AF0A9CB0AC9F0A00A0B09A00B00B0B0B009090A0900009A00000000000000000000090A00A0000000000000000A90DA9A9A9E9B9BCB9FBDBBF9BBEB9E9B0B90B0000000000000000000000000000000B9ACA9E9A9CA90F0BFFFFFFF00000A9BEF0AFCBE9E9AF09AC00C000B00900000090A0A090000B000000000000000000000000009009000009090000000909B099DB99BD9E9BDBA9BF99AD9BDB9E9B9AD909000000000000000000000000000000E9A9E9E9EBDAF0BCBFFFFFFF00ADAECB0FDABE9E9AFF000B09A09009A0B09E90009090A90A00000B00000000000000000000000090B090B09E900900000099A9A9E90BB9B9AD9F0BF9FBF9BF9BE9D9A9A000000000000000000000000000000B0F00A9ADACBCBCA9CBFFFFDA000099EFFAFDADAFEDAF09000009A0909000A00A9000000009000B0000000000000000000000000000000090B09A00000009A090909B990BCB9BB9BDBB9B9AD9B99B0B9090000000000000000000000000000000F0BCBCBE9FADA9FAFFFFFFFF0000AE9F0F9EFBF0BFFF0A0A9A9009A0E0A900900A00A9CA0009000C000000000000000000000000090009A000900000000009A09A9ADAF99BDBCBDBE9F0BF9BADB090000000000000000000000000000000000BCBCB0AF9FADAFFE9FFFFFFF000B0F9FAFFAF0FCBFE0F909C000B0009090C0A0A00090A09C00A00B00000000000000000000000000000009090A90000000000900909999ADB0B99BF9B9F99A990090A9000000000000000000000000000000000B0A0BDAF0FBF9E9F0FFFFFFE0000EBCF0FDFFBFEDBFF0B0B0F009A00B00B00C09000000A090000000000000000000000000000000000000009000000000000000000A099A9DB0B0BDA9ABD9E9B0A900000000000000000000000000000000000CBDACB9EBCF0FBEBFFFFFFFB000B0FBFFBEBCF0FADFF0000000B009000000B000A0009000A00900B000000000000000000000000000000000000000000000000000090A090B0F9F9B9D990B90909000000000000000000000000000000000000BA09A0CBCBAFBCF0FBFFFFFC0000BCFEBCFFFBFFFFAF00B00B00000000B0000B00900A0090000A0000000000000000000000000000000000000000000000000000000009A0909A900B09A900A000000000000000000000000000000000000009E09E0FBE9ADE9E9ADFFFFFF000A9EBF9FFBCBEF0F0FF00090000090A9000B0000000000A0000000000000000000000000000000000000000000000000000000000000000090B090B90B09A909000000000000000000000000000000000000000BCA0B009E9A9EBCBAFFFFFFA000CBCFAF0FFFDBFFFFF900000090A00000000000000909000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0B00CBE0ACBE9E00DBFFFFFD009A0BCDAF0FAFCBE9EF0CB0B09AC900009090BC900A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0CBA09ADADEBCB0AFFFFFFA0000BEBAE9EBDEBE9EBFAB9EDAB09A9B0B0FAF0B0BF90B09A9F0ADAF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F09A00DAF0BFADA00FFFFFFF0000F0CBC9E9FEBF0FADFDFFFBDFFBFCB09FBDBFDFF0FFCB0DADF9FF9F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0A09ABDAFE9FA0A0BFFFFFF00000ABCAEBEF9E0F0FAFBFFFFFBDFBFFFADFFFFBFFFBDBFBFBFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ADADA0C0ADBFEDE90D0FFFFFFF00A9CA90BCBEF9EAF0FFFFFFFFFFFFFFBDBFFFFFFFFFFFDBCFFFFFFFFE000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0B00F0BFAFCBFA0EABFFFFFF00000A9EADAFDAADADAFFFDFFFFFEBFDBDAFFFFFFFFFFFFAFFBFFFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000BCAC0B00BCFFFFE9FADFFFFFFF0009CBCA9E9FAEDA0FAFFBEBFFBDBDFAF09FBFFFDFFF9F9F9BDFFFFBFBF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B9BC0A9EBDEBE9EADADFFFFFF000A0A0F0BEDE9A9FADFF9BC9F0F0B9F0B00DADAF0FBE9E9A9A9FADFCFDA00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000E0A090E9EBFCBE9EBEBFFFFFFF0009CA0FCBEBE0E0FAFF00B00A90000900B09A90B0090B0D00B09A9A9A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0F0BACB0BCBADAE9E9FFFFFFF0000A0BCABFCF0F0FADFF00000000A9000000000000000000A9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0B0BC090ADABCEBDA9EFFFFFFCA0090BCAFDEBABCBADFAF0900090000A000900900009A0090909A0900A090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0E09A0BDACE9A0ACABFFFFFFF000ACAFDBEBDEDACFAFFF000B00090000900A00A09A0090A00A00000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F00B0BC9E0A9B0E9E9AD0BFFFFFFF00090AAFDEBAFFAFDAFF0A0000A000900A009000000000000909000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009A9ACBCAA0BCACB0E0ACAFEFFFFFF0000A0FDAFBCF0ADBEFFF090909009000009000000000000900A00A0000A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009E0E0B0A90D0B0F0F0BCBF9FFFFFFB000ADBFADFCFBEFFE9E0F0000A00000A0000000900900B000A0909090090900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A09AD0F00A0ACB0A9ACB0EBFFFFFFF00000E0FEAFACBDAFFBFF09A900B0B00900000A0000A00000900A000A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FA09A00BC0B09ADACBACBCA9FFFFFFE009A9F9FDBFBEFDADEBF0000090000000A900000A00000000090909000A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B09E00F00A900ACA9AC0B09EFFFFFFE90000EAEFAE0E9EBFEBCF000B000000000000900000000900B0A00A009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F0CA09A0A9000E9A9E00B00E9BFFFFF9000ACB9F9ADB9FAFCB9EF00000B0090A00000000900900A000090909A0009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000AB09A0C90ACA900CA0B0CA00ADFFFFFF0009A0E0ACACACA9ACA0F0090009A00000090A000000A00000000A9090900A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0DAE0CB0A09000A9090CA009A9AFFFFFEF0009A9E9A9A9ADA9ADAF00A0900000090A00090A0000000900B090000A00000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009A90B00090A00A00ACA090A000FFFFFFF000A0CA0ACACACA0E0A0F0009A0009000000000000A009000A0000A0B090A900A0BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0EACA0A00E000900B00000AC00ADBFFFF90000CB09E90A9A9E9E0FF0900009A00A00900900909000A00090909000090000009FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009AD00A90000000C00B0000A09FFFFFFE000B0ACA0ADACACA0B00F00B09A000900000A00A000000000000A0090B0000B09A0BFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0F00A0D00A9A00A0A0000A0090A0FFFFFF9A0090BCB0A9A0BCA0FAF000000909009A00009000A09000900090A0000B0000009FFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000E9090AF00A00000009009000090000FFFFFFF000CACA0AC0E0F0A9E00F9009A90A0A00009000009000C000A900009A9000900A9ADFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000009A00A0AF00B00BC0A000000A0000A0A9FBFFFF0000A0B09E9A09A0BCA0FFADBE9AD909F9B0F9ADB0E9AB0BBC9ADA9AD0F9B0CBC90FBFFFFFA00000000000000000000000000000000000000000000000000000000000000000000000000A000A009ADAD0BACB00A9090A0A0000A00000FFFFFFF000900EA00B0E9E0ADA0FB0F9FFAFBCBCFDAFDADB9FDFF0FBE9FFF9ABF0F9A9BFADFFFFFFDB00000000000000000000000000000000000000000000000000000000000000000000A0000000ADACA00BAFC9A0E90A0E0090A9A0D0A90A9FFFFFF90A0E90DA0CB0A9E9ADAFFFFFF9FDAFBFBFFDBFBEFFBFDFFDFFF9FEDF0FBE9FE9FBFFFFFF0E0B000000000A000000000000000000000000000000000000000000000000000000A00000009E9A0B0FACB0BE9E9AE9A09A0E90CB0A9CA9FFFFFFE00009A0A0B00E9E0BCADFFFFFFFFBFDFFFFFFFFDFFFFFFBFFFFFFFBFBFFDFBDBFFFFFFFF0FBDE0000000000000000000000000000000000000000000000000000000000A9000000000A0BE0A9CBCBCB0FE9E9AF90F0ADA90EBACBCA9EFFFFFFB0000ACB0F0EA9A0BCA9AF9FBFFFFFBFFFFDBFFFBF9FFFFFFFBFFFFDFFFBF9EFFFDBFFFFFF00A9FAC00000000000000000000000000000000000000000000000000000A9CA0A0090A9C9E90F0FAFADA9EB9EB0F0AE9F0A9EFBCFBEBFEBFFFFFFC000B0BCA0A90E9E0A9E0FFFDFBFFDFFDBFFFFFFFFFFFFFFFFFFFFFBF9FFFF9FBBEFFFFFFFFBCACBA000000000000000000000000000000000000000000000000000000AA900BCA09AA0AEB0BCBCFAFE9EF0FE0F9A0A9E90AFBCF9E9F0FFFFFFFB000CA9E9EA9A0BC0A9AFFBFFFFFBFBFFFFFFFFDFBFFFFFFFFFFFBFFFFFFBFBCF9BFFFFFF0CB0BCF00000000000000000000000000000000000000000000000000000F0DACBCA9EAC9E900F0FBFADA9E9AFA9B0E9E9E0AF9EFBEFFAFFFFFFFFE0000B0CBA9CBCBCBADACFBFFFFFFFFFFFFBFFFFBFFFFFFFFBFFFFFFDFBFFFDBFBFFFFFFF0FB0ACB0AC00000000000000000000000000000000000000000000000000FA0A0B0A9E909AB0EFBEBCE9E9EBEDA9CACB0B0A9F9EDBCBDAFCBFFFFFF9009E0FBEDEBEAE9ADA0BFFDBFFFFF9FBFFFFFFFFFFFFFFFFFFFFFFFBFFFFFBCBDBDFFFFFF00F0B0F0BA000000000000000000000000000000000000000000000000B0BCBE0F9E0BEAD0FB0F0FBBFBEBDFADAA9A0E09CA0ABAFBFADBBFBFFFFFE0000B0E9A9E9BDAE9AF0FFFF9FFFFFFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFBFBFFFFFBF00F0F0E9CB0000000000000000000000000000000000000000000000BCBCB09E0A9E09ABE0EBCBEDE9E9EA0FADAC9A9A0B0F0CBCBCBEDEFFFFFFFF00A0CB0FAF0ECBDBEDAFFFBFFFBFFBFBFFFFFFFBFFFFFFFFFFFFFFFFBDFBFBDBCFFFFFFFFCBF0F0FBEBCB0C000000000000000000000000000000000000000000BCBE0EBE9E9E9BE9E9FBCAF9AFEBE9FFADA9A00000000BADBEBE9AB0FFFFFFB0000B0F0F0FBBEACBADAFFFFFFFFBDFFFFDFBFFFFFFFFFFFFFFFBFBFFFFDFDABFBFFFFFFF0B0FBEBCF0F0FA900000000000000000000000000000000000000ACBCBCA9BC9A9EB0E0F0FACBFDAFF9F0FAE9EBCBC0B0CA9A9CBE0F0FADFFFFFFFC000B0E0B0E9ACA9FADADFFF9FBDFFFBFBFBFFDFFFFFFFFFDBFFFFFDFFFBFBFFDBDFFFFF9E9EF0FDFBFBFF9EDAD0000000000000000000000000000000000A90B0A9ADACBEDABCBDBEBCBF0FAFDAFAFBDBFADA0B00A900EAFADBCBADABFFFFFFB00009ABCA9ACB9EA9EB0FFFFFFFFBFFDFFFFBFBFFFFFFFFFFFFFFFFFBFFFFFBFBFBFFFFFFA90FAFBCFCBEFBEBEFBE90000A000000000000000000A9009AC0AF0F0EDA9A9A9EDAEADAFBCAF0FAFFCBCAE9EDADA0F00AB09ADFAFBCB0F0FFFFFFF00A0C0A9E0B00E9CB0FAFFFFBFBFDF9FBFFFFFFFFBFFFFBFFFFDBFBFFDFFBFDFF9FFFFFFEDAF9F9EBFBFDADBDF0E9EBE9E9000A000A009A00090AC0AA00B0D0A0B0A9EFCAF0BDBDADADBF9EBDA9FEFBDEBAFADA0AF0CBE9EBDACBEA9FBFFFFFE009CBADA9ACAF0AB0E00FFFFDFFFFAFFFF9FF9FFFFFFFFFFFFBFFDFFFFBDFFBF0FFFFFFF9BADAFEFFCFCBFFEFAFBFDADAF0E9E09009CAC09A0A090A9C9AC00A09E0F0E9ABDAFAEAFADAF0EBDAFFE9BCAF0FC9ADAF09BE9EBCBEB9E9FEFFFFFF9000A0CBCAD0B00F0E90BFFFBFF9FBFBF9FFBFFFFFFFFBDFFFFFFFBFFFFFFFFFFFBBFFFFFE0DAF9F0FBFBEDADBDF0BEFBCBDBE9FADA0A99A09000A900A009A09E00B0B9ADFEBCDBDADADAF9FADBCBFEBDAF0BACB09EAE0BCBFE9FEBEBFFFFFFFE0000FBCB0AACAF0EBCA0FFFFFFFFDFDFFBFFFBFFFFFFFFBFFFFFFFFBFFFFBFDBFDFFFFFFBF09EBFF0FDEBFFAFAFFCBEDBEBCBE9EBCBCAE9EACBC0E0B09A00000B00E0E0B00BAA0F0FAF0AF0FAFBE9E0F9EBCB0FEADB0F0BCBFEADFBCBFFFFFFB00B00EBCF09ADAF90BCAFFFFFFBFBFBFFDF9FFFFFFFFFFFFFFFFDBFFFFFFFFBFFBFFFFFDF0BFBCFAFBEBDADFDF0FBF9EF9EBDBADA9A9F9E9F9A9B0BC0A000A0000F0B0BCAF0DADAA9E9F9EAF9EF9EBDAE9E9AF0BDA0DA9EBE9BDBEDFFFFFFFFC000CB0EB0FADADAEBCA9FFFFFFFDFFFFBFBFFFFFFFFFFFFFFFBFFFFFFFFFFDFF9FBFFFFFA0F0CFBDFEDFEFBEBEBFADAF9EBCBEDADACF0EBF0EDAE0F0BC0B090A9A000A00900A90A9CB0AE0BDAF0FADAADB0EF0BCAADA0E9ADFEFEBFAFFFFFFFB00A9ADB0FADAFADBCA9EFFFFFFFBF9FFDFFFFFFFFFFFFFFFFFFFBFDFFFFFBFFBEDFFFFFF0F0FB0FADBE9FBCFDBCFDADFADBFCBBCBDBAF9FCFB0F9F0F0BC00A00C90A9C9A0A90A9C0A0E90F0E9FACB0E9AEF9AFCBC9A9A9EFEBFBFCFF0FFFFFFC000ACA0F0FADADAE9AF0FFFFFFFFFFFBFBDBFFFFFFFFFBFFFFFFFFBFFFFFFFBDBFBFFFFFF0B0FBEDADBE9FBEBEBEBFADBEDABCE9EAD9EBABCFBCB0F0F0B090B0ACBCA0A9C0AC0A0B090A00B0ADB0E90F9ACF0B0BAC0ACB0B9EDEBFAFFFFFFFFB0090BCBEBCBEBE9EBCAFFFFFFFFFFBFFFFBFFBFFFFFFFFFFFFFFDFFFFFFFDFFFFDFFFFFF9E9EDADBFEBDACDBCBDBCBDADABDE9BE9BAF0FDEBF0FADAF0BCACAC9A9A90BCA0B0B09000E090BC09A0F0A0ACBA9E0E0CB0DA0FFEFBFDEF0FBFFFFFE000A0BCBCBE9E9E9CBDAFFFFFFFBDFDBF9FDBFFFFFFFFFFFFFFBFBFFBFFFFBFBFBFFFFF9EB0FBFFFEBCBEBBAF9EAF9EBEBDEBBEDACF0F9EBF0FADAF0BCB09A9ADADAFC9ADAC0F0E0B09A00ABE0F0A0DA9AD0A9A0B0A0BDE0FBEDAF9FFFFFFFF00A0C9EBCBE9E9EBEABEBFFFFFFFFBFBDEBFBDFFFFFFBFFFFFFFFFFFFFFBFFFFFDFFFFFFE90FEDFEBDFBE9FCF0E9F0EB0FDABDE9ADB0FBE9F0F0F0BCBCBCA0DACADAF0BEDA9AB0A9ACA00E90090A9CA00E00F00D0009E0ABF9CBEB0FAFFFFFFF9009AB0FADAFAE9E9FCFCFFFFFFFFDBFBFDBFBFDBFFFDFFFFFFFFBF9FFFFFFF9FBFBFFFFFFA9FBFDFFFDFFAF9FBE9F0DA0ADABCFAADB0CBEBFAF0F0BCB0BCB09B0F09E9ABCBC9E9E90BCB0A9E0F0A90A90B00A0A0F0A09C0AAF00FACB0FFFFFFE0000CBCBE9CB0F0EBABFFFFFFFFFBE9FBFF9FBFFFFBFFBFFFFFFFDFFBDFFBFFFE9FFFFFF0DADEFBEFAFBDFFEFFDEFBADBDAD0B0D9ACBBDBCBDBCBE9BF0F00F0E9AFA9EDABCBADA9EBCB0E9E9AB0F0A90A00B09000009A0BC90AD00B0FFFFFFFF000A9EBCBEBCFAFBCFCAFFFFFBDBFDBFDF9EBDFFBDFFFFFFFBF9FBFBFFBFFFFF9FFFFFFE9A9FBFFFFFFEFEFBDEBF0FCB0EA9EBCBAE9BCBEBCBEBCBCE0F00B0A9ADADE9A9CBCF0BE9CA9E9A9AF0F0BC0BC9E00A0A000000000A000B0CBFFFFFF000BCAB0F0ADABCBCFAB9FFFFFFFFBFFFBFBDBFBFFFFBFFFFFFFFFFFDFFFFFFDBFBFFFFF9E0FEFCFFDFFFFFFFFFFFFBFFF9FFADAC09ACB0BDADA9E9BBDA9AC9EBCBCB0F0FADA9AD0BA9E9ADAD0FAD0BCA0A9AD0D00B0A0900090A000A9FFFFFF00009C9E9ADAFCBEB0FCAFFFFFFFDF9FFDFFFBDFDBFFFFFFFFFFFDBFBFFFFFFFFFDBFFFFF9A9BFBFAFADFF9EFBDEFDEBCBF0FDBFBF0F9EDAF9ADA9ECBE9E9A09A9EBCB0F0F0F0FAF0F0BCB0FAF0FAF0BCBDA0A0A900C0000A00090090AFFFFFFF000A0A0ACA9ABCBCB0BCFFFFFFBFBFFBFBDBDBFBFFFFFFFFFFFBFFFFFFFFFFBF9FFFFFFCBC0F0FFFFFFAFFFFEFBFFFFFFEFFAFCBCB0A9A9E0F0BCBBCBCBC0F0F0F0BCBE9AF0BCB0F0BCB0F0BCBDADACBCA9E9E9EB0B0B0A90A00A0009FFFFFFA00A9CB0DA9CBCBCBCBCBFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFB00BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF0F0BCBCB00B0B0F0BCBCB0F0BCB0F0BCB0F0BCBFADA9A9A9E9A9A00000000000000000BFFFFFF00000A00A00A00A0A0A0A000000000000000000000105000000000000D4AD05FE")}, + {empKey(7),ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E506963747572650001050000020000000700000050427275736800000000000000000020540000424D16540000000000007600000028000000C0000000DF0000000100040000000000A0530000CE0E0000D80E0000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00FEDECDACFEFCEADE0ECECBCE0CEFEFECFFEFCFEEEDEFEFEFCDEBCEFFEFCFEDEFCFEFECFDEFFC000C0A0C0CE0EDFE0FCE9FDFCBF9F9FCFCEFCF0FC000000EFCE0CE0CE0FCBE0F0EDECACBCAECACEF0FCE0FCCACE9E0FEEDE0E0E0EEDACEC0CEDEFCACAECE0CAEDEECFCACFCEDEFE0EDEFECFCFEDFFEFFFCFEEECEEFCE0EFEDFEDEFFEDFEFEFE900000C9E9CADEFE0FCBDFCBDBD0FCF9F9FFDEFFCAF0000C0CADCAC0E9ECACCFCECACADECEDEFCF0EECACECAEDE0ECECEDACFCFCEDEACF0FEF0EEFACEDCADEFED0ECBCADEEEFAEEDEFEEFCFEEFEFEEFEFEFEDACBCF0EFEDE0FEFEFECFEEFEFFF00C0CE0EC0EDEFACFCBDBCBDFE9FDB9E9E90EFFCAC0CA00C0EFCACCECEC0ECACE0FCEDECBCAC0E0ED0CEDEBCDE0FCF0E9EEFEE0E9E0DE0ECACEFDFCCCAFCEECFEEFCECECE9ECFCFEFCE9EEFEDEDEFDEFFFEDEDECEEFEDFEFEFEFFCFFEFDEFDEFF00000DE9ECEFEDE9EDADFDAF9F09EDF99CF9EFFFCAC0900CC0EC0E9CACE9CF0EC0F0E0ECFCFECFCEEBCACCEACECACECECFCBCFCECEECFCEDEDEEFEBCECBC0FCFCAF0F0ECEDEEFCFEFEFEDEFEFEFEFFEEFFEACEFCFCEEFFCFEFCFEEFEEFFEFFEFB0C0E0F00DEBCE9E9FDADBDCF0F99ADEB90D0FEFFDECA000ACDAC0ECDACE0EC9ECECFCFE0ECBECADCECEBCCE9ECECADEACECFACBEC9EE0FCEADEF0CEFECEFEEBEFCECEDACAEDEAFCEDECFEDACFEFEEDFEDFCE9EE0EDFEEFEDEFEFDEDFEFFEFFF00000C0CFEFCDAD9E9EDBCFBDF9CBD99C9F0FCFDEFBCC000CCACFC0EACCACCACCACAC0E0FCECCBCEADACCE9ECE9E9ECEDEFEDEFCCBECDEEBEDEFFCAC0CEFEFDECCCAC0EECEDECFCFFEFFEFEFFEDEFFFEFEAEDEECFEFEEFFFFEBEDEEFEFFCFEFCF000CAEEDEBCBC9E9CBDADBDE9A9C9CE9E09D0FEFCFE9A00CACF00E0CCADCADEADEDEEDECE0FEECFCECFE0ECE0ECECF0ECADEECFEECFAEDEDEFEFFCBCAC0CEEFEBEDEF0CBCAEFEFEEF0EDEDECFEFEFEFDEDCAEDACFEFFDEFEFCFEFFFEEFFEDEFFBC00CDFAFCBDAD9CBD0FDCBFDF90A99C9FCBCBCEFCFED000CACECCEDACE0E0CCACAC0EDAFCAC9E0E9E0DECACFCACACEDEFEE9E0F9EECFEEFEEDFFFEFCBC0C0CECEACEFECECCECEFDEFEEFEFEFEFDEFEFEEFCCECEFFCFEFEDEEDEECFFDEFFFEFEFBC0EEDCBC9C9E9D0FDAF9CBDAD9C0BD090D0D90EE9EFE900CBCBC0EC0CECE0ECECFE0ECECFECEFCECEACEDE0ECFCE0ECE9EEDEEEDEF0FCFFFAFFEDFEFEFBCAD0CCE0EDF0FACFFEEFCFCFCEDEDEFEFFEF0EEF0EFDEFEFEDEFFEFFFEFEFEFEDEFFC00CB0BC9E9E9E9F09D9EDDADBC90C0FCF0F0E0DCEDEF000CCEC0E0CE0C9CECBCACCFCBCBC0F0CACADCE9CACC9E0FCFADECFECFCEBCEEFEB00FEFEEDFCFCEDEECA9CCEEECCFECEDEFEFAEFEFEFEFFEDFEDACEFFEFFFEFFEFCEFCEFEFEFCFEFFEFFCE0FC9E9E9CDBC9E9E9FADF09A90B9090D099CAFEEDA900EF0E0CE0CACE0DECFCBEECECEECEECDECE0CECFAEECE0ECEEFACBCEFCEDFFB000FFCFFEEFEEFEE9FCEF0C0CFE0EFFEFEDEDE0FEFEFFEFE0ECEDEFEFEFEDFEDEFFEFFEFCFFEFFFCFEFF0D09E9D0F9ACBD0F9E9DE9F99C9C0DAD0BC09C0CFEF00CECFCE0C0ECACEACACEED0EDAC0F0DAE0E9EACACC0CBCECFEDEDEEFACFFEF0C0E0FEFECFFCFDEDEEEFCEEBCE0EDEEDEEFEFEFECFCFEFFFFECBCEFEFFFEFEFEFECFFEFCFFEDEEFEFEFCAF0F0DADBCF9DADBC9F0F9E9E0909A0DAD09E00E0CFEB00CFAD0E0C0CC0DEDEF0EEFCACECCECCECECCCFCACECCACBCF0EEDECFCEFFEB000EFFEDFECBEEBEFDECBEDEE9C0ECFEFDEFEDEDEEFEFEDECFECEFCFEFCFCFEDEFFEDFEFFEFFFFFCFEDAD0D0F0DAD90F0D0DBC0F09F09DA00D0090F0909CEFED000FEDEC0CACACECACECED0CACDACACAC0F0E0E0ECACACFCEEEFE9E0FEFFBC90E0F0FEFEEFFEEDEDAEEFEDEEDEFE0CE0FEEDEFEEFCFEFFEFFACEFFFEFEFFEFEFEFEFEEFFEFEFEFEFBCADAD0F0DE9DADF9E9F0DBD9FC9F09C909F0D0900CEFF0CACFEF0E0E0C0CE9ECF0F0ECACEACEDACCE0ECE0EC0CCE0E0FCBCFCEFEFFACA0E0E00FCFCFCEDFEFEDEDECE9EFEDEF0ECEFFEFCADEFEFEFFECF0FEFEFDEFEFCFEFCFEFFFEDEFCFEDECFC0D0F0DBC9E9E0D9F0DBC9E99F0F0BC0000B0C9000CEFFCFE9EF0C0C0E0CEDACECF0EDACCCACCE0EC00CC9EDEBCFCFEFCE0EFDE9E9000000A0FFEFAFEEDECFEFAFEFEDEFACFE9E0CEFEFDECEDEDEFFECEFDEFEFEFCFEFCEFEDEFEFFEFEFFEFCA9CBC9F0F9F9F9F0F0F0DBC9E0D90D09B0D0C90A900FCEFFEFECCFE0E0CE0ECFCACAC0ECA0FCAC0C0ECEBEECACCE0ECCACFFDEFFFA0A00A0000FEFCFCF0EF0ECEDEDEEFECFFECFEFCACFEEEBEFEFFEF0FEFEFDFEDEFEDEFFEFFFEFDEFCFCEFCB0C9CF0DF9EF0FCBDBD9F0D0F9F9E9A9C0D009A90C000EDEFCFCBE0C0CCACEF0E0DECCE0CCEC0C0EFE0C0CC0BC0E0CCBEFFEFEFEA00000000000FFFEEEFEFCEFE9EEFEDECBCECFEDE9C0EFFDEDEFFEFCEFCFEFEEFEFCFEFFEDFEEFFEEFEFEFCF0F9EB9DEBCF9DF9F0F0F0F0F9C9E9C9CA90A900C0900CEBCFEBEEDE0CA0CE9CE0EE0E00CCAC0E0EC0C0E0E0ECEECFFEFEDEFCFF0900000000000FCFFFDEDEEFCEEDE0FEFFEEFEFEFEFFFCCEEEFEFEFFEFEFCFEFFEDEFEFCFEFEFDFEFFCFEDE0909C9DEBDDB0FADE9FDBDBD99E9E9F0F99C900D0900000CEEFDEDECAC0CE0CEACC0CC0ECA0C0ECE0CACECEDEDEDAFE0FCFEFEF00A00A000000000FACFEFEFE9EF9ECEFCEEDEDEDACFCEEFAF0DEEDEFEDEFFEEDFCFEFFCFEFEFEDEFEFCFEDEFBCBCADADDEBCDFDDBDE9E9E9EDAD0D09D0F0B0D00A0090000DCEFECBEDAC0CFC0CACE0EC0ECEEC0C0CACBCBCE0E0EDEDEFECFCFEB00A000000000A0FCF0FFEFCFECEFE9CEDAEFEFEFFEFFDEFCECADEFFEFEFCFFEEFFFEFEFFEDFEFFEFEFEFFE009C99FDBE9DBF0FADF9FDF9F9BDF9ADADB0D0DA90D090000CEAFCADECEC0CACACC0C0E00E0C0C0EADEDECCECADECEEFEFCFFEBA0A0A0000000000C0FE900EFFFECFFEDEFAEDEDEDEDECFEEFFEF0CEEFEFFCFEFEFFFEEDEFCEFEEFFEFDEFCFE09FCBCFCBD9FFEDFFDFBEDF9EF9ED09CBC90D0F09C000000000EDCEFE9E90EEDAC0AC0ECDECC0E9EFCCACADACBCECFADEDEFEAE0000000A000000000AF0F0000FEFFECFAECFCEEFEEFEFFCFFEFFEFF0CCFEFFEDEFCFEDFEFEFFCFFFCFFEFEF0C9C09CBDBDEFE9FBDFBDFDBEDBCFDBDE9D9ADBD0F09090C00000CEBCFCECEC0C0C0CCECF0E0CADCE0CBEDECECECEDAEDEBEDAFC9A0A0A0000A00B00A90BE0A000CFEFFFCFEFCFDECF0EFEFFEDEFCFEDEACCFFEFEFFFEFEFEDEFEFEFEFEFEDEEF9A9CBDEDEDBDFFDFEDFE9FDBFDBDADB9E9CDADAD0F0CA90900000CE0EF0F0FCACACA0CA0C0E0EE0FECCAE0F0E9E0ECFECEEFCAA0C9000A0000000A00A0F09000000DFEFEFCAFEAFEEFEDEFEFEFEFEFEFD0EFEFEDEFEFEFCEFFCFEFEFEFCFEFD00D0FCBDBFBDFBCFFBFFFFFFDEBDF9FCF9FA9D0DAD0990000000000CFE0FCECAC0C0CCACCACECD0EC0FEDCFCEDECFCBC0FFCEA9C0A0A00000000A009A0AF0A000000FEFFEDEFFCFCF0FCFEFDEFFCFCFEFFEFFFCFEFEFCFEFFEEFEFCFFCFEFA90E9CBCBDEFDEFFFFFFDFBDFBCFBDE9E9F9F0DDADBD09F0009000000CE0CFCEAD0CACACACCADE9E0EDACE0CEACE0E0E0ECFECA9000BC0000000000000A090FF0C0090009EFFFECEFEFEFEEFEFEFCFEFEDFEEFFEEFFFEDFEDEFEDFEFFEDEFEFCFC09E9FDFF9FFDFEDFFFFFFFDFBFDBDBDF0F0DB09E9CBC0AD0C090000ACFEE0FCE0C0CCCCADE0CECE0ECBCFACCBCCFCECFEF0B0A0A00A900000000000000A0FEB09A00A0000AFFFECFCFCFFEDEFEFEFEFEFDEFFDEFEFFEEFEFCFEFDEFFEFEDEF0BC90F0F0FFDFBFFFFFFFFFFFFDFADFCBDF9FBCFD9E9DBD9090000000C0C9EF0E9E0CAF0FCACE0F0EDACE0CE9ECAE0CFE9EACA0900AC0A000000000000A000FEDA000090000ECFEDEFAEFECFEEDEFEDFEFEEFEFEFEFDEFDEFFFEFEFFEFEDEFE09C9EFCFDFFEBFFDFFFDFFDFFBCBFDBDBDADAD0B9AC9AD00F0E90000000E0ECECFCC0ECCACACDACCEC0CE0EC0ECADCEFE9E90000A0900A00B0A0A00A0A00000F9A000900000000EFFFEDEDEFFEFFEDEFEDEFFFFCFFFEFEFEFEFEFEFFEDEFEF090D0F9DBFFFFDFDEFFFFFFFFFDFFFDBDEDADBD9FC0D9FDADF9090090000CC0E0FCACACFCACEDCAC0E00CE0CC0ECADEEBCB0A0A9000000A09A000000000000000BE90900A00000000EEFFEFEFEFFCEFEF0EEFCFEFFEDEFEFFEFFEFDEFEFEFCFE9CBCBCFEFDF0FFFFFFFFFFFFBFFBF9FEB9BDBC9E99F9E90F09AD0D000000CACFCADEFCAC0DE0CACEC0ECE0EF0E9ECECFDA000000AD00E000A0A0A000A0E0F0B00F0A00A090000000000CFEDFEFCFFFCFEFDEFEFFEFFEFFFCFFEFFEFFEFFFEFEDA0D0F9FDFAFFFFFFFFDFFFFFDFFDFF9FDEDADBF9E90F9ED9FC90B0B000000C0CFCAC0FCACACFEC00EC0E0D0CECECFFBE0F0A000E0AF009A000000A00000A00000F90009000000000000ACFEEDEFEFEFEDEEFCFFEDFEFFEEFFEFFEFEFFCFEFED00DADCFCBFDFFDFFFFFFFFFDFFFFFFDEF9F9F9C9E9E9D09AD0BDE9C09000000EE0EDEFCADECE0C0EC0AC0ECEC0FEB00C0FA0000ADFC9EBE0A0B0A000000000A0A000090000000000000000EFFFEFFEDFEFEF0FEFFFEFFEFFFEFFEDFFFEFEFDE90DADBFFFFFFFFFFFDFFFFFFFFFFF9FBF9F9E9E9E999DA9ED9BC09909000000EC0CFCACADE0C0C0EC0ECCF0E0EFE90000A00000AC0A0A00009A0A000A0000000009F000000000000000000000EFFFEFEEFFEDEEFEFEFEFFEDEFEFFEEFEFEDEE00E9DEDEBDFFFFFFFFFFFFFFFFFFDFFDFDF0F9F9F9ECF09C9BCDBDBCBC900000C0FE0ECFCE0CE0E0CAC0DACECFE900000000000000A000E0F0A0000A0000000000A0AB000000000000000000000ACFFFFFCEFEDECFFFFFEFFEFFFEFFFEDFFEDF9C9E9FBDFFFDFFFDFFFFFFFFFFBFFBEFBFFDBDAD09990F0BC0B0DAD909A900000C0ECBCAFCAC0CCE0CEECECFFCB0000000000000000000000A0BA0000AC000000000D00000000000000000000000ACFEFEFFFFEFEFEFEFFFEFFEFDEFEFEFEFE00DADFCFFFFFFFFFFFFFFFFFFFFFFDFDFFCBFEF9FF9EDB9CD9F0F90BCBC9000000EC9CECFC0C0E0E0ED0CEFB00000000A0A000000000000A0E90000A0AC0A00000000A0000000000000000000000000EFFFFEFEDEFFFEFFFEFFEFFEFFFFFEDA90DADEFFFFFFFFFFFFFFFFFFFFFDFFFFBFDBFDF9F99E900DA9A0D90FD9C9000000CC0EEDACACAC0F0CCAEFB000000000C0000000000000000000A00A0009A0000A00A09A000000000000000000000000000EFFFFFEFEFFFEFFEFFEFFEFEFEFE90DADFDBFDFFDFFFFFFFFFFFFFFFFFF9FDFFF9F9EDEF9F9F09C9DACF9A0BCB900000ADC0ECF0CCACCCAFCF0000000000A0A000A000000000A00A00A090ACA0000000000E9000000000000C0B0A00000000000EFFEFFFFFEFFFEFEFDEFFEFFDEF00D0FAFDFFFFFFFFFFFFFFFFFFFFFFFFFFF9FFFF9F9F0F09F0BC99B0D9C99C09000C0E0DADECACCACECFA90000000000C090A0000000000000000900AC90CBCA000A900BE000000000E009A0000000000000000EFFEFEFFFEFFFFFEFFEFEFEF00DAFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFF09FF9F9F99E99C9E9C9ADBCA99000000C0ECEBC0CACFAF00000000000000AC000000A00000000A0A00000A000090A90000F09000A0A0E9CB00000000000000000C9EFFFFFEFFFFEFEFFEFFFEF09E9EDFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FDF9FFF0F0FC9E99E9A9C9AD909D9E00000E0EC0FC0DEEDAD0000000000000A000A0E0000000000000000A00000A00A0000000FCA00000000A00A0000000000000000A0000EFFFEFEFFFFEFFEFEDF000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFF9F9FBDB9F9F090D0BC9BCBDA099000C0C0CEACEE9E90000000000000000A0AC000A000000000000A00A0A0000000000000AF0000000A00A000000000000000000000000CFFFFFFEFEFEFFFFEA9CBCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDAFF9E9DADA909F9E9C9BC9909DBCB0000C0CBCDE9E9000000000000000000000A0A000000A000A000000000A0E0000900000FE9A000000000000000000A00000000C0A0000FEFEFEFFFFFEFEFF900DFFFFFFFFFFFFFFDFFFFFFFFFFFFFFFFFBFF9FE9FB99F9F09090B0C9ACBCB0909000ACACEA0A0000000000000000000A0AA00000A0A000A0000000A00AC090A00000000FA00000A0A0A00000000A000A0000A0A0C0000CFFFFFFFEFEFFFFC0CFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FDF9F99F9C9E0D09ADAD0D9AD9D90DAD0000C0CCAD0000000000000000000000000000A000000000000000000000A0900000000FF00000000000A0A0000000000000000A000A00CFFEFEFFFFFEFCB9ADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFBFBDB0F90BC990BC9090B00900BCBD9B000C0E0FCA000A000000A0000000000A0A0A00000A0A0A00A000000000A00A00A000000FEB0000000000000000000A00000000C0A0C0000CFFFFEFEFFFEF00CFFFFFFFFFFFFDFFFFFFFFFFFFFFFFDFFDFDAF9F9E9DBCBC9BCBC9DBC9BC090AD09000C0CE0E000009A00000A0A000000000000000000000000000A000000000000000000FBC00000000000A0000000000A00000A00CA0009EFEFFFFFFEFFE90FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFBF99EDADBE99DB9C9DBDAD9A090C9D0F000C00E0DA9A00FAC9A0000000A000000A00A000A000A0A00000000000000000000A000FEB0000000000000000A000000000000C0B00E00CFFFFFEFEFFE90FFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFBDBC9EFDBDF9DBDADE9FBC9F9FC99E900A990C0ACCFA000CFAC9AC90A00A00000000000000000000000A00000000000000000A0C0AF000000000000000A000000A00000000AC0E00A00EFFEFFFFEFCE9CFFFFFFFFFFFFFFFFDFFFFFFFFFFDBDBDF9FF9FADF0FF0F9B9F0DFE9E99F090BC9C0000CAC0CA00A0CBE90A0CA000000000000A00000000000000A00000000000000000A00FFB000000000000000000A00000000000A00E0CA09CFFFEFFFFF90FFFFFFFFFFFFFFFFFBFFFFFFFF9FFFF0BCF0FFDFBFF9FFFCFDBFBDBDFF9F9E9090B090C0CEB00A00A000A009ACB0A0A0000000000A0000000000000A00A0000A00000000A0B00000000000000000A000C0A000000000CA9CA9CA0EFFFFEFEB0EFFFFFFFFFFFFFFFDFFFFFDFBDFFF90FFDBFFFDBFDFDFF9FBFBCDFFFFBCF9F9DBC90D0C00F9000000000009A000000000000A00000000000000000000000000000A00000A00F0000000000000000000A0A0000000A000A0EA0CADADFFEFFFFC9CFFFFFFFFFFFFFFFFFFFDFFFFFAF0FFFDBFFDBFFFFBFBDFFDFDFBE9FDFF9EBDA90BCB0B0EC0000000000000000A000B000A00000000000000000000000A00A00A0000A00000AD000000A00000000000000A000000C0A00C00DA0000CFFFFEFB0FFFFFFFFFFFFFFFFFFFFFFBFF9FDFBDBFFDFFFFDFFDFFFFFFFBFDFFBF9FFDDB9F9D09C9CCB00000A0000E0ACB000A000E9000A0000000A000000A000A00E90A000000000000FADA00A000A00000000000000A0A0000DA00ACACB0000CFEFFFE9CFFFFFFFFFFFFFFFFBFFFFFDBFFBDFFFFFFFFFFFFFFFDFFFFFDFBDFDFF9FBFDE9A9F09A9A9000000000A0E9ACB0000A00A000000000A0000A00000000000E00000A000A0000FFA00000000000000000000A9C0000A0A00A000000A000FFFFED0EFFFFFFFFFFFFFFFFFDFBFBFFDBDFFDFFFFFFFFFFFFFFFFDFBFFFFBFF9E9F0F9FDB0DAD0D000A00000A0CBCE90A00A00A00A00A00A00000000000CA000CA00A0A0000000000FA00000A0000000000000A0C00AC0A00000000A0A00000EFFEFB0DFFFFFFFFFFFFFFFFFFFDFDFFFFFFBFFFFFFFFFFFFFFFFFBFDFFDFDF9FFFDBDBF9ADB99CB00000A00000ACA9A00000000000CA00A00000A00000A00000A000000000A000000FF90A00000000000000000A0A00A00C000000000000009CFFFFC00FFFFFFFFFFFFFFFFFFFFFFF9FFFFFFFFFFFFFFFFFFFFFFFFFDFBFBFFDBDBDBC9FDB0DA90D00A000000000AC0A00A00A000A00F0000A000000A00000A0000A0000000000000FCA000000000000A000000000A00A0A0A000000000A000A0DEFFBCFFFFFFFFFFFFFFFFFBDBFBFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFDFDBFDBCF9FF0F9F0DB09A9000000A0000A000000000A00E00A0A000A00A000A00C0000000000000000000AB0000A000000000000A00000000000C000000000000A000EFFED0FFFFFFFFFFFFFFFFDFFFDFFFFFFFFFFFFFFFFFFFFFFFFFDFFFDFFFBFDBFF9F99F9F99BC9F000A0A0A00A0000A00000AC00A00A0000E000A000000A000A0000000000000000F00A00000000A0E0A00000000000A0A0A0000000CB00000CDEFF0EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF9FDBE9DBDADBCBCBCB909DA000000E000A0000A0ACA9A0A000A0A0A0A000A00000A0000000000000000000E0CA0000000C0AC9A000000000000000000A90A0C0F009A0FEFE9EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF9FDFF9F9DBF0F9F9BDB99DADA09000A0A00A0000000000EA000A0000000000A00000A0000000A00000A000000B9A0CB00000A0E9A000A0A00000000A000A000090B000A00ACFFFCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFBF9F9FBD9F9FBCFDADAF099DAD0A00000ACB0A0A00A0000A0000A0A0A00A000000000A00A000000000000000E000AC0A000000A000A0000000000A0000000000A00A0000CBCFEBCFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFDFFFBDF9F9F0DAF9F9CB9BDBD9BDA990B0000A000000000000A0A0A0A0000000A000A00000000000000E0A00000000B00AC0B00000A000A000000000000000A00900A000000000ACAFFCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF9FDF9E9F99F999C9B9FCBF9ADA9C9E900A000A0A0A00A00000000000A0A0A00000000A000000000000A000000000E0000AC0B0A000A00C0E0A000000000000000000000000A00ADCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFF9FBDBD9E99F0F9FDCB9D0BDBD9B09D09C00000000A0000A00A0A0A000000A00000A00A000000000A000A00000009A0000AC000000000A900000000000A0000000000A00000000A0EFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F9FBD9F09D0B0B9DEBDDADAD0F0B0A0B0A000000A0A000000000A00A000000000000000000A0000A000000000E0000000A000000A00CACA0000000C00000A000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFDBF9F999F9DB99D9D0B9DABDBDB999C9D000000A000000000000A0000000000000A0A000000000000000A000000090000A0000A0000000A009000000E0B00000CA00000A0000000A0EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBD9D90DF0B909DA90BD0F9D9E9F09E9F0A00000000A0A0000000000000000000000C0A0000000000A0A000000000E0A0000A000000A0AC0F0ECB0A0000EB000A0C00A0000000000009CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFBDBF9FDB9D9F9F999D09F09EB9F9F090099000000000000000000000000000000000A90000000A0000000A00A0000B0000A00A000A00A0A000ACE9000A0C0E9C09A0C0A0A0A90A00000AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFDFFDFF9FDBDF9D9FCB99C9F99DADB9F9F90F0A0A00A00000A00A000000000000000000E0A000000000000000000000CA00000000A00000000EADA9E00000A000A0AC0B0C00C00000000ACFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFDBD9F9FDB9F0B999CB909CB9DBCF9090F090000000A0A000000000A0000000000A0000000A0000A00A0A0A000000B0000000000000A0A0A000E00A000000A00D00AC00B0A0A000A9E0DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFFFFFFFDB9BDBF9F9B0D99D090990BDB9EBDF90F9F090A0A00A00000A0A000000000000000000E0ADA0000A0000000000A0000E0000000000000000A0A0A0A00A0000000A0AD00E0C00900A0000AEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFBFDBFDFD9D9FFFDFBE9B90DB0D09C99CB9FF9099E90000000A0A00000A00000A00000000000000000000000000E0A0000009A0000A0000000000000A000A0000000000000A09A0F00A000A009CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFDFFDB99FFFFFFFBDFFDEFB0D90909E9BDADBF9E99E90A0A000000A0A0000000000000000A00ACA0000000A000A00D0A00A0E9000A00000A00000A0A00A000A0A00000000A0DAC000E00E900A0AFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F9FDFFDFFFBDBDFFFFFFFFFFFFFBDF9FB0009090DBDBF9E9F090000000A00A00000000000A00000000000000A00A00000A0CA0A00000B0000000000C90A000000A0A0A000000000A000A00B0E90E00CAD0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD9DEFFFFFFFFFFF9FFF9EB9E9900009DBC9F9F990DADA0000000A000A00000A00000000A00ACA0A000000000000A0E00CA00E00A0000A0A0A000000A000000A0A0A0000000B0B0000CB09EB0ACBFFFFFFFFFFFFFFFFFFFFFFFFFFBF9F9FBDF9DAFBFFFBFFFBDFFFBDB9B9C99CB00000099E9E9F0F90900000A0000A000000000000000000000C0000000A0A00000CADA90A0DB00000000CADA0A0000A0A0A00000000A000A0A0A000ACAC9CB0ACFFFFFFFFFFFFFFFFFFFFFFFFFFFFBEFDFFBF9DFDBFFFDBFFBFB990D0D0900909000000B9F9FDB0F9F0A0A000A000A0000A00A00ACA000000A0CAC0A0000000000ACACA000EACA00000000000000A0900000A0A0A0A090A00000000000A0A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB9FF9009EFBDFF9BD90D9CBD9B9B909F0909090090D0F9B9D090900000000A000A0000000000000A0A000A00B0000000000A000AC9A0F00000A0000A0A000000A00A0A000A00000A00000000A00B09000F0FFFFFFFFFFFFFFFFFFFFFFFFFFF9BDF909090F99FF9F9DBDBDBDB9C9D0BD090D09A99000B90FDA9ADA90A00A0000A000000A000A0A0A0C00000CAC09A00A0000000A00A00EBE0E09A0A0000A000000000000A0000A0A0000A000000000A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFBFDB0F900009F9F9F9FBDBDBDBDFBF9FDB9DB9BD9D0D99CBD9BD9C9CA000000A000A000A000A00C0000A0000A000E0CA000A0CA00000000FCB00E009000000000A00A00E009E9A0000A9A090A0000B000B00ACFFFFFFFFFFFFFFFFFFFFFFFFFFFB9DB99BD9DBDF9FDF9FFFFFF9F9DBF99E9E9C9ADA9A0909AD9B0B9900A0000000000000000000A0A000A0A000A00EADA0C0A00F0000000FBCEB0F0A0A00000A000900F0BE00A00A0B0000A000B0000900000FFFFFFFFFFFFFFFFFFFFFFFFFFFF9DB9CBDBFFFFFFFFFFFFFDBDF0FBD0FBDB9FB9DB9D9990F99E9D9E9B0000A000000000000000A0000A000000000A0CA0F0AC0E00A00000FCB00C0A9C9000009000A0F0AC00B000900E0A000B000A00A00A0FEFFFFFFFFFFFFFFFFFFFFFFFFFFB9A9CBDFFFFFFFFFFFFF9FBFFBDBC9F9C99E9DCB0DBCBCB90F9F9A9C000A00000000000000000000A0000000000000A9E0E09A0A0000000AB0DA9AD0ACA00A00A90000E90B0000A0A09E900000A00900B0900FFFFFFFFFFFFFFFFFFFFFFFFFFFF9D9BDFFFFFFFFFFFFFFFFDF9DBD9B099E99A9B9DA9909C990F0D9CB900000A0000000A0000000000A0A00A000000000000A0C90A000000DEA0000A90B00000000A0000A000A00000E00EB0A000F0A000ACBCEFFFFFFFFFFFFFFFFFFFFFFFFFBDB0BD0BDFFFFFFFFFFFFFBFBFBDAD99A0909D09CBDADB9BCBD9B0B90DA000000A090000000000A0000000000A0A00A00A09CA0E090A0000A90000000ACADA00A0AC0B00000000000A0AD00009AE0E90A00000FFFFFFFFFFFFFFFFFFFFFFFFFFFFF990FFFFFFFFFFFFFFFFDFDBDBDB9C990F009E990990D0990FDBD0F0900000000A00CADA000000000000000C000000000A00F0A0000000F000A000A909A00000DAF0000000000A09C0A00A0AC9EBCA00A0AFEFFFFFFFFFFFFFFFFFFFFFFFFBDF990FF9FFFFFFFFFFFFBDBFBDBDBD0B909009909E9F0F9B90F9090F99000A00A0A00E9A00000AC0000A0A00A00A000000AC0E00000000009A00900900A0000000AC0A09A0000000A0B00A00000E0EA900000CFFFFFFFFFFFFFFFFFFFFFFFFFFFBFF990F9FFFDFFFDFBDFBD9DBDBFBD9E9099E090909909CBD9F9F990F090000000000AC9A0A000ADA0900000A0009000000B0A00A0000E0E9A00B000090A900000AC9A000A00000000E009A00A0EDEEB000A0FFFFFFFFFFFFFFFFFFFFFFFFFFFDBDF999F9FFBFFBFFFBD9B9BDFFDBF9DB9C990DA99E9F990F90F0F9D0B0F0A00A0A000AA0000A0000ACA0A00CACA00900A00000000B0E9EB000000B00000CA000090A00A000A0A0A0A90E00000CA0E9E9A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF9F09FBDFDFFF9FDBFFDFFBDFF9FBDA9AD9BD9C9909E99EDBD9E9BDD0000A0000A0000A0A00A0A0090000A09000A0000000A000C0CACA09A0F00000ADA90000A00CF0E9A00000900E9A0A00A0E9ECA0E9FFFFFFFFFFFFFFFFFFFFFFFFFFFFBDFDF9F90DFFBFDBFFFFFFFFFFFF9FBDBD9D9BCB09A09E99F9B9DB9F0D9A9A00000000A0A0000A00000E0A0A0CA0A900C0EABC90A0A0ACFF0000090A000A000000000A0F0E00A0A0A0B0AC9090000A0BCB0CAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFBF9F9A9FDBFDFFFFFFFFFFFFFFDFF9E9AD9BF99DB9DF0FDFADE9DABD0000000A0A00000A00000009000C0BC9C000B09C0A00000EDAE9A000ACB0F0C90A900000BCEFF0F000000A009A0A0A000000ADB9DBFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFF9F9F99AFDBFFFFFFFFFFFFFFFBDFBDBDBCD9E90DB9F9B9D9BDB9DCBDB000A0000A000000A0000A000A000A0ACB00A0BC9A00000ACFE9000DACF09A0AC0090A9CAF0FA00A00B00A0A000000A000FDBDFFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F9F9DBDFFFFFFFFFFFFFFFFFBDBDADBBF99FBCF9FDEBFDBDE9BDA00A0000A0000A0A000A00000A09A00C090E90000A0000A0CB0B00A0ACB00A000900AC9CAD0F09CB0CA00A90000A0B090FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FFBDBDBFBDFFFFFFFFFFFFFFBFDF9F9DBD90F0D9BCB9BDDB0F9BDF0B00000A000A090000000A0A900000A0BEE90F0009000000A0ECA0D0000A000000A900A0B0A0E0E0EB0E900A000000CADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFF9FBDFFFFFFFFFFFFFFFFDFBDBC9E9E9F9FBFDBDFDBBDF9FDA9F00000000A000E9E9A00000000A00000C0FFE9A000000000000BDA0A0A000A00A0000900009E9ADADE9A0AC90A09A00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFDFBDFF9FFFFFFFFFFFFFFFF9F9B99E9F9F9FDB9FDE9FF0FDADA90000000A000000000A00A00900000AC0EBE9A00000000000CA0000000A000D0B0A0A90000000A0A0000B0AD0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFDFBFFFFFFFFFFFFFFFF9FBDBC9F9F0F9E9BDF0F9FDBF9BDB0000000A000A0A0A0A000000A00A00009A9C9000900000A00AB000A00AC090A0B0C9C900A0A0A000000CACAD0A0CFFFFFFFFFFDBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFDFFFFFFFFFFFFFFBDFBDAD9F0F9BDBDBDF9F9F9BC9FC9AD0A0000000A00000000000000000000A0000A090A000A000ACE9000000A0A090009A0E000900000A00A0F0FAF0F0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFBFFFFFFFFFFFFFFFFFBDBDB09F9FDBDBDF0F9F9FDBF99F9AD0A0A00A00A00000900A00900900A9000B09A000A00000000FAB0A0A0E0CA0B0A0090A000E90A000000EEFCB00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFF9FF9BDF9F0BDADFB9F9F9EBD9CFA909A9090A00A000A00A0A000A00A0A00E9E9CAC9A0000000A0A0BD0000009EB000090B0090A000A0000A0ACBCB0CBFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFBFFFDFFFFFFFFFFFFFFFFFFF9FDA9E9F9FDB9CF9F9F9DBFB99F909E0E000A00A000000C0B09009009E000A09A000A0000A0000F0A9A0A0E0009000000A0A00A0000A000CBEB0E9EFF9FFFFFB9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFFFFFFFFF9F0B9F9F9F9B9FB9BD0F9FAD9CF09CB0B000A00A0A0000A0B00ACBC0A0000A00A000000A00000A0A90000000A90090000000000000A0000A000000ADFFFFFFFF9FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFF9FFFFDBFFFFFFFFFFFF9FF9F9D09E9E9C90D9CB9E9D9E9B9DA90000A00E00000A9090000909A90A0A000000A00000000A00AFE9A9A0A000A00009000000000000A000A0A0000FFFFF9FFBD9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF9FFDBFF9FFBFFDFFFFFFFFFFFFF9F9A9F99F9B9F90B9099A999E9E99F9A000A00ADA090A0A0B0A0E0E000000000000000000000A00B909000000A0DA9A000000000000000000000A09FFFB9FDFDBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FF9FFF9FFFFFFFFFFFFFFFFFFBC9D0F990909090D9BC9DADBD9BC90000009E0A0A0AD000C0900909A000000000000000A00000AF0A00000A00DAC0D0000000000000A0A000A000CFF9D9FBFBF9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDFB9F9BDBF9FFFFBFFFFFFFFFFFBDBD9B0B90C90D0BC90BC99AD9D0BC9A9A0CA0E000000DAB0A9ACADACA00A0000000000000000A0A0A909A00000A0A9A0B000A000000000000A00000FFF9BDDFFDFFFFFFF9FFFFFFFFFFFFFFFFFFFFFFFFFFFFBDBCBDBC9F9F9FDFFFFFFFFFFFFBDB0D9D0B909BD99BD99AD90B0BDB0D00B0CA00A000A00CBCADADA9000000000000A0000A0000000DA0000000000000000A0000A000000000000A00CFFF9AF9FFFFFFBFFF0FFFFFFFFFFFFFFFFFFFFFFFBDB9DBDB909B90F9FBFFBDBFFFFFFFFDBDB009009F099E9CBD09A99C909DB0F00A0CB000A00A0000CACACA0A00000000000000000A000A0A90090000000A00A00000A000000A0A0000A000FFFB9D9F9BDFFFBDB09DFFFFFFFFFFFFFFFFFFFFFFFFFFBF9F9F9C9B909C9FDFFFFFFFFFBFF9099A99099F099B90BD9CB09F090D0F0CFACA0000000A0A0A0A00000A0000000A0000000000000F00A000000000000000A00000000000CA9A00CFFFF9D9B9DFFBDB9E90FEFFFFFFFFFFFFFFFFFFBFFF9FF9F9FDB9F9BD0999B9FBFDFFFFFFDF909F0C9ED9F0F9E9CBD90B0DB09E90B0A0A000A00000000000000A0000000000A0000000000000A0F090A0000000A0A0A000A000000A0A000C9CBFFFFBB9CB9F9FFDF9B09FFFFFFFFFFFFFFFFFFFFF9FFBFFFFFBFF9F99B9F09CBDBFFFFFFFFBF90099990E990909900090900D09E9C00000A000000A00A00A00000A0A0000A00000A0A0A00A000B0A000000000000000A0000000A0000A0A0A0CF9FFFDB990F09FBF0900DFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF9E9CBC9099DBDBDFFFFFFF9F9F9000A990000000090000090B00909A0A00000A00000000000A000000A0000000A0000000000A0E900000000A0A0A0A000A00A0000A00000000FBFFFFF9F9999F9F9909CFFFFFFFFFFFFFFFFFFF9FFFFFDFFFFF9F9F9B9F9A99E9F9FFFFFFFFA90090D00000000000000C90909F9000000000000A0A00000000000000000000A00000A0A0A0000B000A000000000000A0A00A0000000A00A0DFBDDFFFFFF990FFF9CA90ADFFFFFFFFFFFFFFFBF9FFFF9FFBDB9909090D0909CB9F9FFFFFFFF9D9090000000900000C090B00F0D0000000A000A0A000000000A00A0000A00A000000A0000000A00A0000000000000A0A000A000A0A0000000A0EDBFFFFFFFFB9090B9900DBFFFFFFFFFFFFFBF9FFB999B0090000000009009090D0FFFFFFFF9FB090090900000000909E909090B9B0000000A00000A0A000000E90E0A00000000A000A0A0A00000DA0000000000A0000A0E0F00000000000000FFDFFFFFFBF9E990000900DFFFFFFFFFFFFFDFF9FDBE9DBD0900000000090B090B99FDFFFFFF9F9E900009E909ADBCB9090090D0C090000000A0A000000000000A000CA0000000000000000A0000A00000000000000A000000A0A00000000000E9F9FFFFB090900909000D0FFFFFFFFFFFFFBFFFFBD9FBDBFBD0900B00BC909099CFBFFFFFFFF99909090009CBC90900909BCB09B900000000000A0A00000A0A0C0A0A00A0A0000A00A0A0000A0000000000000000000A0A0A0000000000000ADF9FDBFB0D09009000009AFFFFFFFFFFFFFFFF9F9FFB9CFFDFFFB9D0D9090909E9FBDFBFFFDB0F0F9A90090009090909CAC00909C00000000A00A0000000000C00A0000000000A00000000A0000AF00000000000000000000000000000000000FBDFFFD99000900000000CDFFFFFFFFFFFFFFFFFFFF9F999F99FDFB9A909090F9DBDBFDF9FBFDB90D90090909009ACB0999990DA0900000000A000A0000A0000A000000A0000000000A0A00A0A00A000000000000000000A00000000000000000FB9DBF099900009000009AFFFFFFFFFFFFFFFFFF9FFBF9A9F0B09C990090F99BF9E9FBFF9F99FF9ADB90009090990DBCBCADB0900900000000A0000A00000A00A0A0A000A0A000A0000000000A0F00000000000000000000000000000000000F9DBF999E009A90000909ADFFFFFFFFFFFFFFFFFFFF9FFFDF9F9F9B09DBCB9AD09F9F9F9FF0FF09F990DA90000000909909900900000000000000A000000A0000C000000000000000A0A0A000000000000000000000A00000000000000000000E9BD9C9E99F9900000000CDFFFFFFFFFFFFFFFFDFFFFFDBFBF9F9FDBDA9990D09F0B0F9FF9FF9FF9F0F990900000000000000900009000000000000A00A000ACA0A0A00A000000000000000A00A0A00000000000000000000000000000000000DF9E9BDBDF9E9900000009AFFFFFFFFFFFFFFFFBFBDFFFFDFDFBDA909000090F90D9DBC9FFF9FF9E9F9F0DAD0000000000000000CB0000000000000000000000000000000A0A0A00A00A0A00A0CAB00000A0000000000000000000000A0000000E9990DFBF990009000000D9FFFFFFFFFFFFFFFFDFADBDFBB090090009090DA9E90F0FBF9FDFBDF9FDADB09099A90000000000090990000000000000000A0A0A0A0A000000000000000000000000CA00000000000000000000000000000000000FBDEFFBD90090000000000FFFFFFFFFFFFFFFFFF9DBDA9D090090090000009D9FF9FDFDFFBFDFBFFBDBFDBDAD09C900009000C09C0000000000000000A00000000000A0000A0A000A0A00A0A0A0A0000000000000000A00000000000000000000999990909000000000090FCFFFFFFFFFFFFFFFFFB909009E09C0F0BC9E9FFFFF9FFFFBFDFDBFDFBDBD0BF0F9FDB0D0F9E0909B0A900000000000000000000000B00000A0A0000000000A0000009E00A000000000A000000000000000000000000000090000000000000009FFFFFFFFFFFFFFFFFFFFDF9F99DBBD9FDBDBDF9F9FFDFFFDBFBFFBFDFFFFFFDBDF0BC9A9009D0F00C90000000000000000000A00A000A000000A0A0A000A000A0A00A90000A0A000A000000000000000A00000000000000000000000000000009FFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFBFF9FFFFFFDFFBFDF9F9FDADF9BD99F90B009090900000000000000000000000A000A9A00000000A00A0000000EA0A0000000A0000A0A000000000000009000000000000000000090000000DBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFDFDBFFDFFBFFFFFBDB9C9A9E90C9090000000000000000000000000A00000A0000A0A0A0000000A0A000B0000A0000A000A000000000000A000000000000000000000000000000909FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFDFF9FDBDBD09BD0900B090009000000000000000000000000A0A0000A00000000000A00000A0E00A00A0A000000000000000A0000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFF9FFDFFFFFFFFBDB09F09090909000B000900000000000000000000A0009A0A000A0A0A0000000A000009A00000000A0A00A0A00000000B00000000900000000000000000000000009CBDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFBFF9FFF9DB09909A0C90900090000000000000000000000000A00000A00000000A00000000000E000A00A0A00000000000000A00000000000000000000000000000000000909FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFBFFF9FFDFDFFFF99B0909E909DB9000900900000000000000000000000A00A0A000A0A0A00000A00000000B00000000000A000000A0A0000000000000009000000000000000000000000BCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFBFFDFB9F0D9F990DA900090C0900900000000000000000000000000000A000000A0000000000000A0000A0A0A000A0A000009A0B000000000000000000000000000000000090D9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFDFFFBF9D099B09A909D099009A900000000000000000000000000A00A0A00A00A0000A00000A00A0F000A0000000000000ACA000000000000000000000000000000000000090C90AD9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFBFFDFFBB900090D09A99E90009009000000000000000000000000000000A00A000A00000A00000000A000000000A0000000090A0A00000000D000900000000000000000000009A9DBEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBFFBFDFFBF9D90DB9DB90BC9A90090909000000000000000000000000A0A00A000A000A0000A0000000E0F0000A0A0A00000A000A0A0900000009000900000000900000000000900909DA9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFDFFFBFDF9A0DB9CB900D9909090000000000000000000000000000000000000A00A0000A000000A0A90AB00000000000A000A00000A09009000090000009000000000000000009E9E9DEBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FDFFB99DBC9BDA9990A90000909000000000000000000000000000A00A0A0A0000000000A000000ACA0A000000000000000A0A00A00000000000909000000000000000000C0909A99DADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDBF00BDBF09900F99090900009000000000000000000000000A000000C000A00A00A0000000A00B0000A0A0A00A00A00000000000000000090000000000000000000900900F0D9EFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFF9FFFFFF99D9F09F9CB909AD0909090000000000000000000000000000A0000A0AE9000000CA00A00000A000000000000000000B0A0A00009000000000000000009000000000DAD9090FBDBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFBDBF99E9E99F9A990F09090000000000000000000000000000000A00000E0CF000A09E0B00000A00AF0000000000000000A000900090000000000000900000000090000000900B0F0DEDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFF9F099BDA9C90909009A09090000000000000000000000000090A00A00BE0FA0C0E0E09A00000000A00000000000A0A00A00A00A00000000090090000000000000000000B09C90F9FBDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFBDFF9B0D0BDB9BDA9099090000000000000000000000000000000C0F000000E0DA9ACBCA00A000000F0000A00A00000000A00A00A00900000090000000000000000900000D09A90D0FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFBDF9C9F909C909C9AC909090000000000000000000000000000ACA0A0A0F0EA000A0A9A000A0A00A00000000000A00A000A09A09000900000009000900000000000000000C000FBCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBFFDBDFB0B9ADBDB9B909090000000000000000000000000000000CACBE090C00A9000A0000000A0000A900A000000000000A0000ACA00000090009000000000000000000090090909C9F0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFBD90D909090DA90900909000000000000000000000000000A9E0FCE0B0ACA0A00000A0A0000A00EA000A0A0A000A0000000090A90000090C00000000000000000090000000CB0DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFDBDFBF9ADBCB9909CB09000000000000000000000000000000000A0A9AECB000C00A000000A000000B0000000000000000000A0A000000000090900000000000000000000000900DAFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFBFBDF9F90999CA9099090900000000000000000000000000000A090CEE9A00A0A0A000A0A090A090A000A0A0000000000000000000000000000000000000900000000000009000DAFDAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBFDFDF0F90DBC909DA90000000900000000000000000000000000C0ACAA9A0A00AC00000000C0A90A00E000000A000A000000000000A00000000000000900000000009009090000009DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFBF9FBDB909A900900900000000000000000000000000000CA0A000000000A00A9A09A00A9E9A00A0B00000000000000000000A0000000000000900000000000000000000009090CADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFBDBDFBFDBDF0F909B00900090000000000000000000000000000CE9A00BCA0A00A0C0CA00000A0E9E900CA0000000A0000000A00000000000000090000000000000000009000900000BDAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFDFFFFBD9BDB09909C090009000000090000000000000000000009A0AC0A000000A00A0ACACA900EDA9A0AA000A00E090A0000000A000A00000000000000000000000900000090000090D0FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FBDFAF0F9F0B0090090000000000000000000000000000000A0C09A0A0A90A00A0AD0B000ACB0A0090B0000000AA000A000000000000000000009000000000000000000000090000ADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDFFF9FFFDB9D99909D90909009000000000000900000000000000000CA0E000000A00A00000AC9A090E9C9A0ACA0000A000E9000000000000A0090000000900000000000000900000000900909EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FFBDBFFFBFF9F090B00000000000090090000009000000000000900B0A90A000A90A0A000AC0A000A00090B0000000AC0A000000000A00000A0000090C090909000000000000000000090DEBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FF9E9F09A99AD09009000090090000000000000000000000900E0C00A00E0CA000000A00B00E0E9A0A000A0000AC0A00A000A00900A000090000000BC90C000909000000000090A90ADBDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFBDBDFF9F9F9DBD9090909000900000000000000000000000000000AC90A9AC0B00A000A00A09AC0ADADA0000E000A0000A9A000A900A0A900000000000090900909000000000000000909C90DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFDFFFFBDBF9F9A909CB0900009000000000900900000000000000009C9A0E0000ACAE09A00009CA00ACACA0E9A0B00000A00C00A0C0A00000A0A0000900000000000000000000000000000C0090BC9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF9FBDBDBE9F9F9B090090900000000090000000900000000000000000009A000009E00E09A00CAC0E0BCB0000E0A0A00CA0A000A000A0A0000000000000C09000000900000000000090090B0C9FFFDBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBCBD99090909009000009090009C0900000000000000000090B0A0A000B0E0E0B000A0CA90B0A9CA00ACBA00C000A00000000A0000000000000000090CA90000009000000000000000C0B9E9FFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFF9FDB0BDB909A9090090900090000000000B009090000000000000000000000A0000B0000FAD0B0CAC00CA0A0CA09E0ADA000A9A0A00000000A00A90000900009000000000000000000000009090C9F9FFDFFFFFFFFFFFFFFFFFFFFFFFFFFF9FFFBF9F9090909000090000900909000909000DA9CB0000000000009C900A0A0C0A0E00B0E0CA00E00ADA0000A90AA900A00AC000000A000A0000000909000000090909000000000000000090000090DBDBFFFFFFFFFFFFFFFFFFFFFFFFFDBFFBDBD9F9F90B00090900090000000009000090009009009000000000000A00C0A9AC00E0000FADAE0ED0ADA9A90A00E0A000A00A0A00A00B00000000CA00000000000000090000000000000000000900BCBDF9FBFDFFFFFFFFFFFFFFBFBDFBFDADBCFA9909D09900000900000090909000000000000009000000000090B00E0FCACB0F0F0BE00E09A0AECAAC0CACA0B000A00000000000E00000000A099E90090000000000000000000000000D0090A9C9BCBFFFFFFFFFFFFFFFFFFFDFFFBFDBF9F999DAD9A900090000000909000000000900900900009000000090E90CE0FA0A90E0E0E00CE90E00EDADC0EA0900E0000000A00000A000A00000090C09E9000000090C900900000000000000900C90BC9F0D9F9F9FBFFFFBFFBDFBF9F9F9BD9F0BCB09B090090000000000000900000000000000000009000090090A0A9EEDEDEA9E0F0E9A0E00AC0ACAAE90E0A09A0000A000000000A000000000A9EC90F00090000000000000000000000000000090099AD0FBFDF9F0FDB9FB0DBF9E9F9B099990900909009000000000090000000090090900000000900000009C00E0AEBA9CA9E0F0E0B0A9CACACCDAEC0AC0E0000000000000000000000A000009E900D000900900000000000000090090909C0BD0AD0BD09A9E9F9BCB9CB9099990009000000900000000000000000000000000000000000000000090090B09AC0FCBCCEF0CA0E00E00CA00ACAAAC0ABC0A9A000000000000A000000A900A0A9E900F909000000900000000000000000000009000909009AD9909009009009000000900900900090090000090000000000000900009000000000900000A9C0E09A0ACEAF0AADEDAF000AC0ECCACC0E0C0ACA000000000000000000000009000000909E90D0090009000000000090000000090090900009090009009090090090009000000000000000000000000000000009000090000000000000909C9CBCBCA0ECBCF0E9CACAC0A0F00E0BAC0AE0E0E00F0A00000000000000A00000A0000A090009E9A9A090000000000000000000000000000090000090009000000000009000000000000000000000000000000009000090E9000900000009000B09CACB090ACAEFEADEBEBCA00E0ECCAE0C9A0A0E0A0000000000000000000A0B09A0000A09C090D0D9CA909000000000000000000009009000900900900009090090000000000000000000000000000000000000000900909A9CA00900CA90D0DADEBCA0E9EEDE9EACE00E9CE0E0A0C0EA0C0C000A000000000000000000000000000000000B0DA9A009C00090000000000000000000000000000000000900000000000000000000000000000000000000000000090009000009090009090B09A0E00F0D0E0FAFEFDEB0F00E0ADACE0AC0CA0A0A0F00000000000000000A0900A00A00A0E90DA0D0D9F09AD000900000000000000000000090090009000000000000000090000000000000000000000000009009009CB000090090D090B0D090C09EFAEAE0ECEEDAEBCE00E0ACACA0E00A00000000A00000000000000A000A00000009000A00DB0F0090C9A900C09000000000000090000000000900090090000000000000000000900000000000009000000000900909090000009AD090B0E9A000EDADAF0EFAFFCEBCACADE0CACACBC0ACA0000F000000000000000000A0000000A0A0A9CB00D0DB0F90C0DA900090900000000000090000000000000000000000000000000000000900000000000000000090C9A0000000000B090B0D0D0000EEDAFECAEBCFEEFFE9E0CA0ECAC0AC0AC000A000000000000000000000000000A0000000A00DBCBCD0009B090009A0000000000000000000000000900900000000000000900000000009009000000000900F0090909000000B090E9C9A9A90ADADAEE0F0ECEFEFFACAC0E0ECA0E0E0E0A0A0000A00000000000000A00000A000000A00A0000009CB0F9D0C909C90D9000000900009000000900900000000000000900900000900000000000909000000009009A90000000D09C099A90D00A00ACACE9EACFAFEFFEFFA9E0E0A0DA00000C0000009A0000000000000000000000000000000A9A0E9AD90E9B0F090DA0DA09000000000900000000000909C0090090C000009C00009000900900000000000D0090D0900090B00009BC0DADAD00000E0F0E0E0ECAFEFFEFCA0E0E9E0CACA0A0A00A00E00000000000000000A00000000000000000090DADB9E0D0DAFA9DA99000000009000900000000900090900000009C9000B0000090000000000090900A090B000090C0090B9009B090E0A0CA09E0E0E9E0EFEFFEFE9E00E0E00A0000C0000000000000000000000000000000000000000A0A0ACAD09C9DB0F9C9DA9D000900009009000000090C00C909A0090909000090009A09C000009090900000909AD0900B00090000CB9F09CF009E90CEACAF0E0FECFFEFE9E00E0000E00A00A00AC0A0B000000000000000A00000000000000A000000090BCB0F0D9E9B09CBCB00900000F090090000009A90A0D0900000A09C090D090009009000000000000D09000900090090DB9C909CA0F0E0E0B0CBC0E0E0FEEFFFFEBCA0A0E0000C0000000000E000A00000000A000A0A000A0000000000A0B0A0009C90F090C9E90090D000000009C90C0900900009C909E9090D090900000C9000000009000090090B0090009090900B0DBB0BC9CACADA9E0E0EACADACADEFEFFEDAC0C00A0A00A00A00A000B0A0000000000000000000000000000000000000E0B0F90F0F9090C9CB090900D09A9A90900000900C0B0090F0909E9E9D0B090A90900900090900F090DB090F000A09F0F0D0D090ACBCACE0F0F0CBCACAEEFEFFEDA0A0A000000000A00C00A0A000000A0000A00A00A00A0000000000A0ADA0B0900D00F9F9CBCB9A909AC009A0C9C900090090009090C9CF0D0AD0909A0090C90CA000009E9E0909F090009009C9DB099B09A9A09E0E0E9E0E0EAC0E0EDEFFEFFEAD0C00AC00E00C000A00C0F0000A000A00000000000000000000000000000A0EB0BD0C9A909D0D09C90900090B0F9B0090C090000909A99AD9090F099D0A9009090F0D0909F9F00B0090009A90BC9E9CB090CAC0ACBCACBCAD0EACBCAFEFFEFBC0A0A000A000A0AC00A0A00A0A0000000A0A0000A0A0B0A00A0A0000A0A0A000C9CB9BCDE9E0B0DA9CB09C9AD0D0C9D0A990090900F0D0E90F0F090E0090090090090BCBD090990090A9090DBD0B90F9CBCB09ACF0EACACACAC00E0ACAFEFFEFEF0C0CA000A0000A00C000F00000A0A0A00000A000000000000000A00000D0F0B0A0D0B99F99C9AD0A90CB0D09A9BCBBD00B0C9E9F09A909E909DAD99009000900B9C990B9E9A0D909D0FBDB0A9D0F9E0DA0CACA0E0CBCADACADA0ACEDEFFEFF00A0A000E00CA0000A000AA000A000900000A00000A00A00A00000000BCA0A0AC00F0BDADE9E9AD9A9D090D0B00DCB9C90F0D9E90090D09E9DBDA9900AD090F009C0B9AD9C9ADDBADB0F9C90D9F0BCBCBAC9EAD0ECBACA0E0A0A0C0CAEFFEFFE9E0C00A000A000CA000A009A00000A000A000000A00A00000000A00A000DA9000B00E9ADB9FBD9A0D00BCB090D0B0CF9AD99A09F9BC900090A0BDE9F990F090DB09BCBDA0BD99AD90DB90B0F0F0F90DA0DAE0CAF0ACCACA00D0CA0A0CFEFFFEBE0A0E0C0A0000A000A0C00E00000000A000A00A00090000000000000ACAC0E9A0CA900F0E9C9BC990F909DADA09C9B0F9AC9DF09C9ADB09C99C099A9E990DA90DBC99C9D90BCB9ADB0DA9DB9F9F0FA0FE0CAF0CAC0A000DA0A00000A0EFEFFFEDAC00A000CA0000AC000A09A0000000000000A00A0A0A000000A00A9009A90000000A00090B0CB0E900C0E909CA90C90D99E90F0BD9ADBC9A090009C9000B9CBBC9BCB0BCBDB0D9BCB99CB0E9E0F09E00FACCAAC0BCACA00000A00C0E9EFFEFFADA0E00A0A00E00000A000A0000A00A0A0A00000000000000A090A0000A0A0A0A0A0009A0A00B0D99CB909C9E99009ADA9E9E909D0AD9C9B0D09E900B0909009C9BCB9DF9BCBDBEDBDBCB0ADACBC9EB0EAC0A0D0AC09000ACA000A0ACEFEFFFACAC000AC000000CA0AC0A0E00000000C0000A000A000000000CA0900A0000090000A0A000000000CA90AD0B09A0D0C909C909C90AD9B0B9C90B00DBD09A9CB00B0F90FA9EDBDA99A9A9A9ADA0F0BAC0E9C0BC0AE0A0A0E00000AC00CBEFFFFEFF0ACA0000E0A0A000000009A0000A0A9A00000A000A0E9000000E0A000000A0A0000000A000A00A90C900D0D0D09A909A9CB09E900C09CA909090009AD9009D00D0FD9F9BCB9DB0DB0DA0F0F0ADCBAF0E0E0AC009C00000A00C00A00CEEFEFFE9E900E0A000C00A0A00A00E00A00C90000A00000000000A00A0B090000A000000A00A0A00A00000CA90C9A9A90BC90F0D0BD0F90F9B9090DAD0090DADB0990B9DBF9AF0F9BDA9ADA0FA9F0B0F9ABC0CADA00CB0E0A0A0A000A0A000ACAFFFFEFE0CA00000A0A000C0CA0CE90000A0A00A00000000000B000000C0A00A000A0A00000000000000A0A909A90DC9C00BC990AD0BD0F90C0F0DA90ADE9099CBFCBC9E9CBD9FDADA90F0BDB0FA9EDA0EC0EACA0F0A0000000C00E000000E0EFFFEFFEB0ACA0ACA0000A0A0A0FE0A9000090A0000A000000A00A000A9A000000000000A00A0A90A00000D0CE09C9A9A90900CADD9EDAD0F9BD0F0DE9D09E9CA9C9BDBF9FF9EF0BDB9EB0BCBEBCBE9ACF0AE09AC0AC0ACA0E0A0A000A0CA000CEEFFFFFCE00C0000C0AC0C0ECE0FEEA0A0A0000000000000000000000009A0000000000000090E00A00A0A0A9E0AD0E90F090900ADBCBFBDADAD9F0DA9F0F9FDB0F0F09E9BF99FDACB9E9EB0BCBCADA0EC90E00BC0B00000000000A00A00A0A0FEFFEFCBDA0A0A00A00ACAE9EFEEFB000900A00A0000A00000B0A00000A000A00A00A000000AC0BC90A0000F0AD0A090F0DADCFF90DBC9CAD9F9E9FBDF0BCF0BCF990DF9F090F0ADBE0BA0FADA09E0CB0AAC9AC0A0C0A0A00ACA0000C0000C0EEFFFFFBEACAC0C0A0CACACFEFEFFFC00A0A0000000A000A00A00000A0A0000000000000000AC0BE0A00000A00F0A900E0CBCBA909E9E90B99A9EBDBC9A9DBDBDF99ADB9A909E9ADB00B0DB090A9E0EB0E9C0A00A000A0000E0000CA0A00A00ACFEFEFFEF0900A0ACEBCFEFFEFFFFEBE000000000A0000000000000000090A009000000A0A009AC090A00A00A0000CA009A0B0D0F0909B90DADB9DADBF9CBCBCB9FFDBCBDADA9ADACEBCBACB0ADA0B0CB00A90E00DA00C0A00000A0000A0CA0CAEFFFFFF00E0E0ECBCEEFEFEFEFEFF0B0B0A00A0000A0A0DA0A000000000000A0A000000000000B0A000009000A0B009A0000A000A0E00C90900909E9CBF9BDBDA09EFBCB0FBCB09A9CBC9A0F00F0CB0CB00E00B000A0A00A00A00A000C0000ACFEFFFEFFE0E9E9EEFFEFFFFFFFFEFBC000000000A00900A0000A000000A00900000A00090A0A0C000A0BCA00B0000A000A0000B09090B0A0009E0B09B09E9E9A9FA9ADBCFBCBCBE9EFEBE9EBFFBFBFFBE9A90AC0A0C0000C0AC00C0A0A0A00CBCFFFFFFACBCEEEFFEFFFEFEFFEFFFE0A0A0A00A0000A0BC0B0000000A000CA0000000A0A00C00A0A9C0000CAC0CB0000000A0A000A0A0090A9A09C0E00F0B0BCB09BCBEFBFBFBF9EFADADBFFFFFFFFFFB9A0A00A000A0A0A000A0A000000CA0EEFEFFEFEFEEFFFFEFEFEFFFFEFFEFB00090000000A0000A000000000000B000A000000000B0A09000A0A0A09A9A0E9A00000090A090000A00000A0B0BCADADABCBACBFBFFFFFFBFBDFEFEFFFFFFFFFFFFF90CA90CA000C000A0000A0AC0A00E0FFFEFFFFEFFEFEFFFFFFFFEFFFFFFCB0A0A9A00A00000000A0A00A00A0000B009A0000000009A0A0000D0DAC0E900009A09A00090A00B00A0A090E0F0B0B0BCB009ADFFFFFFFFFFFAFCBFFFFFFFFFFFFFBFA90A000B0A0A0C00E00C00A000E0EEFFFFFFEFEFFFFEFFEFFEFFFEFEFFF0A000000B00A0A00A9000000000CACA00A000A00A0A0000000A0A0AA0B000E9A0000000A0A09000009090E09A0ADAF0FB0B9EBAFFFFFFFFFFFFFFEFFFFFFFFFFFFFF9A0AC0B0C00000A0A00A0A000E0BCEFEFFFFFFFFFEFFFFEFFEFFFEFFFEBA009A000A0000000000A000A0000A0B0CADA000000900A000B0090C9C90A9A9000A00A090090A0000A0E0A90AC9F0F0BBCBDABDFFBFFFFFFBFFFFAFFFFFFFFFFFFFFBA9C90A00A00A0A00C0A000C0E0ECEFFFFFFEFEFEFFFEFFFFFFFFEBFACBCF0A000A0000A0000A0000A0000A0D0CA900DA0000000000A000ACA0A0A0000A0A90090A0BCA00A0A000090A909A0B0BE9BEBDAFBFFFFFBFFFFBFEF0F9FBFFFFFFFFFF9A0A00E00A00C000A00CACAF0FEFFFEFFFFFFFFFFEFFFEFEFEFAFCCFACAA900A000A0000A0000B00000A000A0900A000B000A0A000000A0900000B00000C0A00000009E090000B0AD0A0A9CAD0BE9FBFF9E9FBFBFBFBFFFFFFAFBFBFFFFFFFFBE900E90A00E0A0E00E0ACBCEEFFEFFFFFFFFFFFEFFFEFFFFEFADABA0000F0A0000000A000000000000B000A00A0A90A000A0000000A0A9CA0B0B000A0A00A00A00B0A009A0A0A0CB0A9000A9ABDBFFFBFBFBFBFFFFBFBFFEFECBCBFFFFFFFFFB9A0B0A00DA000000E9ECFEFFFEFFFEFFFFFFFEFFFEFFFEAF0BCAC0C0E0AA0000A0A0000000A0A00A0000000000000009009000A0B00000A000000A00900A90909000900A000900B09000A900FDAFBFBFBFFFFFFBFBFFFFEFF0BFBFFFFFFFFFFBCB0C00CA0090E9EE9EEFEFEFEFFEFFFEFFFFFFFBEFFECAF00E000A0A00CF00A0000A000A00000000ACA0B00A000000A00A0000000A9A0A9A9A0A000ACA900A0A0A0A0A90A9A0A000A9A900B9ABFFFFFFFBFFBFBFBFBFBFFFEFDAFFBFFFFFFFFA900A0A90AE0E0E0FEFFFFFFFFFFFEFFFFFFFFEFEFACABC0EA0E0AC0CACA0B000A0000A0000009000090000000A00A0000009A000000000000000A90000E00000009000A0000090A0900E9E0E9FBFFBFBFFBFFFFFFFFFFFFFBEBF0FFFFFFFFFFBF0B0D0AC90E9EFFEFEFEFFEFFEFFFFFFFFFFFFEF0FAC0A00C00C0ACACACF0A0A0A0A00000A0A0A0A0A0A0A0A0000000A0A0A000A0A0A0A0A0A0B0A0B0B0B0B0B0B0A9A0B0A9A0A90A0F9BB9BFFFFFFFFFFFFBFBFBFBFBFFEFFCBFBFFFFFFFFFF0B0E0ADAEEFEFEFFFFFFFFFFEFFEFEFFFFFFFE9EE0E0E0E0A0E0E0E0ECAA00000000000000000000000000000000000000000000000000000000000000000000000000000000000A9FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBFBFFFFFFFFFFB9ACBCFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE0E0E0E0E0E0E0E0E0A000000000000000000000010500000000000070AD05FE")}, + {empKey(8),ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E506963747572650001050000020000000700000050427275736800000000000000000020540000424D16540000000000007600000028000000C0000000DF0000000100040000000000A0530000CE0E0000D80E0000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00F00900000000000009090FB0000900000090000000909BFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0009C90C0F9E9BE000090009000D09009009000000000000000B0900000000900000FF009CD00000009000090900000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B0D0900909E9FC9000900900FBFA90000000090000909000000D00B0000000000009000009B00000000090900000909BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0900B00009E9E09000900F9DFFFF090009000090000090090B09090000000000009F00000F0000009000009000000BFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AC90009009090C9000099E00D0FFFE900000000090009000F000000000000000009E90090F00000090090900009C9BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00900000000900900009F00090909ED009090900000090090000090000000000009E0009F0000000000000909E90FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFBFFFFFFFFFFFF90D00909090909000900BC90900009BDA90000000000BEFFE00000000000000000090F09A0009000090000900090BFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFCBBF00000000000090009FE00000000FFC000900909DFBD0B0000000000000000000099ED00009000090009F00009BFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFC9F000000909000000099E00000090B0090090000BFC00D00000000909A00000000009000000000000900BF009ADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFBFFFFFFFFFFF9E9FF90000009090090009BD0B0909C900000090900900B00000000000900000000000009000090000000900009BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFBFFFFFFFFF09F0009000900000000000FBC9000A00909000009F000F0000000090000900000000000000000000900009F009EFBFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBFFEFFFFF0F009000000900000000090DAFDBD0900009009FE000900000000009000900000000000000000000009090FC9BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFBDFBFFFFFBFFFFFFF0000000000000000009000090000000000000090000F000000000000000900000000000000000000000090009FFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFBFFFFFFFFFFFF090909090090000000000009000900090000000000B0090000000009000A0000000000000000F00009000900BFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFBFFBFDFFFDFBFFBFFFF00000000000000009090090000900000009BF00990F00000000090000909000000000000000900F9E0009009FDFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFCBDFFBDBFFFDFFFBFFC000900900000000000000000900090009CFC9FF0EB09090000000900000000000000000009009009A000009BFBFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFBFFFEBFFFFDFFFFFD0009009090DBDB00000000000000090FBFFADAD0F90009A00900009090900000000000000F00090D9000000FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFBFFFBFDFF9FFFDBFBFFFEF9EB00000000F0FADFBCBC90000090000BCFFCF0F00F0090090900909000000000000000000090000000009009BFFBFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDFFBFFFFFFFFFFFFBFBFFD000900099A9DF0FFFFBCF000000090BD0B09000B09A90900090A090900000000000000009E00009000009AFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDAFFFFFFFFFFF9FBDBFBDFBDFDFBCBA000099FEDFE09F0D0FFBC00000900D0A9C0000F0BDA09A9000900009000000000000000099E000B0090099FDFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9AFDBFFBDBFEFFFFCFBDFAFBCFFDFD00000FFF0F9C009E09CBC00009E9AFD000000F900D009C09009000090900900000000000090000000000BFBFBFBFFFFFFFFFFDFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFDBEF9FFEF9F9F9FBDFADF9FBDBEBFE909009BD009B0F9FE9F0900009C900000000B09F0090B900000900000000000000000000090990000009FFFFFFFFFFFFFFBFFBFFFFFFF9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFBFFFFBFF9F099EB9F9FBCBE9FABDBFEFFEFFDFBC00090FE9AFEF9E09F000000000000000000F0900B09000090000009090000009000000000000000009FF9FBFFFDFBFFBDFCBFFFBFFF0FFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFEFFFFBE90FDFADBF9F9FDFCBFDBF9FBFFF00000090DBDFFED00000000000000000000B0B909F090B000000000000000000A90000000000009000BFFFDBFFBFDBDFBFBFDBFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFBFFDFBDBE9F9F9B0BDFF0FBEFAFBDBFDFFFFFEFF0009000000909090090000000000000000F9CB00BFC90900900000909009009C0900000000000000099FAFF9FF0FAB0F9FBEF9FFFDBFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFBFFDFBFFFFFFFFEFCB0B9FBDF9FDBFFEBFAFFFBDBFF000900090000000000000000000009000F09099E90B00900000000000900909ADA900000000000090BFDB9E90B9D9F9ADBDBFFFBBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFBFFFFBFFBFF9FADBDBDBCBCFBAFBBE9F9FFDFBCFFFFFF90000000000000000000000000000000B0A9E099E9C090B090000090009000900000000000000000909BC99BCDBEBFFFFFFFFADFFFFFFFFFFFFFFFFFFBFFFBFFFFFFFFFFFFFFFBFFFBFFCFBFFFFDFBFFDBFFFAF90B9FFDFFDFFFFBFFFFFFFBDAC0000000000000000000000000000000F9090B0090B0009A000000009000C000900000000000000000009AE9BBF9FDBFBFBFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFBFFDFFFDFFBDFBDBFAFDEBFFBCBDEBD9E9FBCBFBFADFFBFFFBFEFF909F0000000000000000000000000090FC909C900909000909009090000B9090C0900000000000000000999E9CBFAFDFDFFFFDBFFFFFFFFFFFFFFFFFFFFBFF9FFFBFFFFFFFFFBFF0FFBFFBFFFEDFBFDFADFBDBDBEF9F0FFF0FDFFBFCFBDEDBDAF009F9F9000000000000000000000000FB09A90A90000900900900009090000A9000000000000000000000B9FBDBDBFBFBFFDBFFFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFBDFEFBFFBDFADF9FBFBDFBFDBEDAFBCBDAFBF9FFFBF9EDBFFFBFFFF9F000000090000000000000000000000B0F0900900B0900000B009090B00090900000090000000000900099CBCBDBFADFFFFBFFFFFFFFFFFFFFFFFFFFFFDFBFBF9FDFBFFFFFF9FFF9FFBDFBEFF9FEBE9BE9BF9CBFBF9F0FADBCBFFBFFF0FF0F9E0000000000000000000000000000090F09F0A90090000090909E0000000000000900000000000000000000B9BDAF9FFBFBFFFFFFFFFFFFFFFFFFFFFFFFBFFDFFFFBFFDBDFBFF9FBFF0FFBDBDBE9FDFFDBFF0FBF9E9EDBDBFFFDBDFE9FFDBFBC9FA00000000000000000000000000000F90BD9E9009000000000909090909000900000009000000000000090E9BDBF9FFFDF9FBFFFFFFFFFFFFFFFFFFFFFBFFBDFFFFBFFBFCBFFDF0FF9EFADBDBFBB9AF9ADBCBCBDBBEBF09BFAFBDBF0FAD0FBC9D09000000000000000000000009000B090B0909A009000909009A0000000090009000000000000000000099ADADAFBDBFABFFFFFFFFFFFFFFFFFFFFBFDFF9FFB9F9FFFFFBFDABFFBDFB9FBEBDADEFDBEDF0BDBFADF9F0F0F0FDEFF9FBDBF09B0A90000000000000000000000000000F00B0DA9090000000009000090090000000000000000009000000009ADB9BDBCBEDBDFFFFFFFFFFFFFFFFFFFFFFFFBFFE9FEFF9FF9FDBFFCBDAF9FE9F9E9FB9BADB0BDADADBDAFF9F9FFB9B0F9E9F0DBC0900000000000000000000000000000F09CB09AD0B00000000090900900000000009000000090009000000090F9E9FBDBF0BFBFFFFFFFFFFFFFFFFFFFFBFCF9FFDBDBF9FFBFE9FBFBF9EB9F0F9FADEFDBCBCB9F9FABDB0FAF0BDEFF0F9E0B00B9000000000000000000000000000090BE9009090909009000000009009009000000000000000E00009000009B0F9B0FBDBDBDFFBFFFFFFFFFFFFFFFFFFFFBFFBFFFBCBE9FE9FF0DBCBDBDE9FADB9B9BCB9DBCB0F9FDADBDB9F0A90F9ADBC9F000000000000000000000000000000000F90B00F0B0C00900900900900900B09000000900000909000000000000DBC9F9CBF0BFBFFFBFFFFFFFFFFFFFFFFDFFFBFCBDFFFDFB9FB9FBDBFADA9F0DBCFCFCBDAA99ADBE9ADBCBCF099FBBCB099A0900000000000000000000000000000000B0F09B0909B09000000009000009000090000000009000000000900009BA9FADBF09EBDFBFFFFFFFFFFFFFFFFFFFBF9FDBFEBF9FBDFF0FF9E9E9F9F0FB0F9B0BB0F9DADBC9BDBE9F9B9E009C90DA009000000000000000000000000009000000F09BC09A900900900900009A000009B0000000900000909000090F00090DA99ABCB9BEBDFFFFFFFFFFFFFFFFFBFFFFFEBF9BDFF0FBCBFDAF9F9F9ADF09F9ADBC9F09A9A9BADA9DA9E9E9900BCB00900000000000000000000000000000000900F009B09C0B00B0000009000090000009000000000000000090009000009BDAD9DBC9D9FADFBFFFFFFFFFFFFFFFFFDBFDFCFFADBFDFBDBF9FADB0F9A9F0BD9ADBE9F0D0D09DADA9F9B09ACB090090000000000000000000000000000000000000F09A0F099090D0900000090900090090B00000090000000000000900000009A0B0B0BF9FBFFFFBFFFFFFFFFFFFFBFFFBFBF9FBDBF0FBE9FBDBCBDADB0F0BE9F09B0B0B0BDA9BBCBC0F0D90009000000000000000000000000000000000900000B00D990B0009A000009090000900000000000000000000000000000000099B9F9F09ADBFBDBFFFFFFFFFFFFFFFFFFF0FDF0FBCBE9FBD9F0DADBDADB0F9BC9DA9E9E9C9BDA90D0990B909A0000000000000000000000000000000000000000000F09A0A090B00900000000909000000909000000090000000000000000000000909E9DAF9FEFBFFFFFFFFFFFFFFFDBFFFA9F0DBF9FBDBEBFBF0F0B0DB0D0B0B0F9A90B0009CB0F0A9C0B009000000000000000000000000000000000000090000FB09909E9C900900009000000000000009090000000000000000000000000900B090BDBE9BFFFFFFFFFFFFFFFFFFF9FFDFBFBCBDE9E9F9CF9F9F9FADB0F9E9F0D9F099F0B09909D0B00900000000000000000000000000000000000000000000F00009A90B0B000009009000900000000000000000000000000000000000000909A09AD9FF9FBFFFFFFFFFFFFFBEFFF0FADBCBDA9BFBCBB9E9A9E99B0F09B90BAB0BCA09C9ACB0A9090000000000000000000000000000000000000000000000F99E909DA9000900000B0000009009009090009000000000000000000000000009090BDBE9FFFFFFFFFFFFFFFFFF9E9F9DBCB9EBDF0DBFDE9F9F90F0DADBC0F0D0D00990A9090090000000000000000000000000000000900000000009090009BE09A9A0DAD900000900990000000000000900000900000000000000000000000000090B9FBFFFFFFFFFFFFFFFFFFFFBFADB0F9DA9FAD0B9F0DA9F0B90B09B09B0B090AD909A9009000000000000000000000000000000000900000000009000F9F0909B09AB000000900A900000009000000000000000000000000000000000000090BCBCBFBFFFFFFFFFFFFFFBDBDADFADF0B9F0DBF9E9FBADA9F0F9CB0DB0DAD9A9000B000900000000000000000000000000000000090000000000000000F0B0F0000F9C900000090900900000000090900000000000000000000000000000000009B9FDFFFFFFFFFFFFFFFEFEBDB0DB0FAD0BA90FDB0D9BF09B0B99E9ADA90AD09B00090000000000000000000000000000000000000000000900090000B00F90B0B90B0000099AD0000000090900000000090000000000000000000000000000090FFBFFFFFFFFFFFFFFDBDBDB0FB0F9D0F0D9F0B0DBAD0DBCBC0E909090F9000009000000000000000000000000000000000000090900000000000000F909A9090CBC90000000B0909009000000090900000000000000000000000000000000009A9FFFFFFFFFFFFFFFFFBF09F0DB0B0B9B0B0BDBAD0B9A9B09B9BCBCB0000B0900900000000000000000000000000000000000000000000000900000FA9ADA90B9A9A9000090000000000090900000000000000000000000000000000000000009FBFFFFFFFFFFFFFFBDE9FE9B0DBCB0F09C909C9BD0F9C9E9C90909C9B9900090000000000000000000000000000000000009090000000000090000F0009ADA009C900B00009A90090009000000090900900000000000000000000000000000009FFFFFFFFFFFFFFFFFBF09BC9A990D09E9ADA9A9A90B0B9A9ADA9A9AC0009000000000000000000000000000000000000000000000009009000000B09BBD0909A9AC909000090000000009000090000000000000000000000000000000000009BFFFFFFFFFFFFFBEBDBC9BC9AD0E9A9A909B09D0D0BCB9C9AD09C90909A90000000000000000000000000000000000000009000000900000000000F000CBFAD00C9B090A0900000090009A000000900000000000000000000000000000000000BFFFFFFFFFFFDFD9FADBF0B90B990D0D0BC0DA0B0F09CB0F9A9A90F090000000000000000000000000000000000000000000000000000009090000F9A9090B0B0B00F099000000000090090900000090000000000000000000000000000000009FFFFFFFFBD9A9E90D090D0BD0A9A90B099A99C909B090B0D09CB090A9090000000000000000000000000000000000000000000000000000000000F090B0BDBC90090BC009000000000090900000900090000000000000000000000000000009BFFFFFF9FCBFDB9BDB9B0B9C0BD090B0DA090A9A90C9AD90B0B09CB0D0000000000000000000000000000000000000000000000000000009000000B0AF0D0BCB9A90BCB090090000000900A90000000000000000000000000000000000000000BFFFFDFBDBFCB9EDB0FC99CB9900BC90909CB900DA9B090F09C9A90900090000000000000000000000000000000000000000000000009090000090F9090B0F9BC900090F00000000090000909000000000000000000000000000000000000009FFFFFBFFFDBFFFB0FDB9FA90BCB909A90B0900DB090C9E90F0BC90B00B000000000000000000000000000000000000000000000900000000000000FA90BCB0F0BE909A9A9F000000000909000B00090900000000000000000000000000000000BFFFFFDFFFF9FFDFBFE99F9F090CB0DA9CB09A09A9B090B09909A9C900900000000000000000000000000000000000000000000000000900000900F9E0909E9F99AD090DA9090000000000090009000000000000000000000000000000000009BFFFFFBFBDFFF9FBDF9FE9F09E9B090909090900D009A9C9ACB0D0B090000000000000000000000000000000000000000000000000000000090000F09B00A99A9E9B0CB09E900000000000900090000000000000000000000000000000000009FFFFFFFFFFBDBFFFFBFF9FA9F9BC9B0A9A90F09A90F090B90990B09000900000000000000000000000000000000000000000000000090000000009BAC0909ACBE9FCB90A90B09090000000009000909000000000000000000000000000000009FFFFFFFFFFFFFFF9FEDBFDFF0F0BC0D90D0909090090F90CB0E9090E90000000000000000000000000000000000000000000000000000000090900F9B0AD09099A9BCB9C009CA00000000000090000009000000000000000000000000000000BFFFFFFFFFFFFDFFFF9FDAF9FBDF9B9A90B0A9E90B09000B09909E09009000000000000000000000000000000000000000000000000000000000090F0AD90A0B0ADBCB0F9A90B90900000000090009000000000000000000000000000000000BFFFFFFFFFFFFFFBFFBFFBFDBFDA9E9E90F09D0900D00B09090A0909B09A000000000000000000000000000000000000000000000000000000900000B0990A9D009BCB9F00909000000000000009B00000000000000000000000000000000009FFFFFFFFFFFFFFFFDFDF9F9BFCBFDBDB0F909A09A9009090DA0D9A090C09000000000000000000000000000000000000000000000000000000009090F00AC90A9F00BCB09B09E90900090000000009000900000000000000000000000000009BFFFFFFFFFFFFFFFFFBFBFEFDFBDFADBCB00F09B9C90B009A099A09F00900000000000000000000000000000000000000000000000000000000000009AB099A900BD09ADAD0F090000000000000000000000000000000000000000000000000BFFFFFFFFFFFFFFFDBFDEDBDBE9FB9FBCBDBB0F0C0B0909E09000900090A90900000000000000000000000000000000000000000000090000000090900F0A0A09A90A909B9A909A090000000000000009000000000000000000000000000000BFFFFFFFFFFFFFFFBFFDBF9FBDB9EDFBCBDAD0D0B9B0DBC90909A9CB09A090000000000000000000000000000000000000000000000000000009000009BB00909E9E99F0F0E90F09000000000000000000000000000000000000000000000009BFFFFFFFFFFFBDBDFBDAF9FAD9EDB9A9F9E9ADBBDAC9A09A90A09000900D009000000000000000000000000000000000000000000000900009000909A9CF0A0B0009A00B099F00B0000009000000000000000000000000000000000000000009FFFFFFFFFFFFFFFADF9F9E9DA9B0ADBD0F9BDB0DA9BC9B0909090909E0909A000000000000000000000000000000000000000000000000000000000990BB0090B9A09A9CBE09B0D000000090000000000000000000000000000000000000009FFFFFFFFFFFFBDF9F9BE9F9EBDAD9B0DA9AD0BCBBDE9B0DADA9C000B090A0090900000000000000000000000000000000000000000000000000900000AD0F00A00ADA0D0B09BC9DA90000000000000000000009000000000000000000000009BFFFFFFFFFFFFFEBF9F0DB099099A9C9B0D9A9DB0DA9F0DA9090B09000090900000000000000000000000000000000000000000000000000000000000B09BA0009A99090A9CBCB0A90000009090000000000000000000000000000000000000BFFBFFFFFFFFFFDBD0DADB09F0F9AD09BCB9A9CBADB9F09A909E9090090900000900000000000000000000000000000000000000000000000000900090D0BCF0A0A9AA9A09A9A90F900000000000090000000000000000000000000000000009FFFFFFFFFFFFFBFDBFB990F0909C90F09090D0B9DADADFF90F09E9090000A90B000000000000000000000000000000000000000000000000000009000B0B09B009009AD0BC09CB90BD000000000900000000090000000000000000000000000BBFFFBFFFFFFFDFBC90DAD990F90B090BCBCB090E99B9A9DA909A90F09A090000909000000000000000000000000000000000000000000000000000009090F0B00A0A0B0BC9A9A9E9CB000000909000090000000000000000000000000000009FFFFFFFFFFFFFF9DB9F9DBCF99090DAD09090DA990F0F9FADA9AD0B9000900090000000000000000000000000000000000000000000000000000909009ADB00E000090B000B009E9AB900000000009000090090000000000000000000000009FFFDFBFFFFFFDF9FBDFFDBFDBDADA9099A9BC9B0D0B0B9EBDB9C90BCA90900090090000000000000000000000000000000000000000000000000000A0B090009B0000A90BB00DA09BD0E9000000090090000000000000000000000000000000BBFFBFDFFFFFFBFFDFFDBFFDBDBD9DB9009C9A9C9AD09C99FADA9A9099CB009000009000000000000000000000000000000000000000000000000009090F0090BB00A00A000B0A9F00B99A0000000090A0000009000000000000000000000009FFFFFBFBFFFFFFFFFDBC9009090B09CBDB0909A909A9A9ADBDBD0D0BCB0090009000000000000000000000000000000000000000000000000000000090909A090F0A00A9A9A099009F0FAD09000000009090009A00000000000000000000009FFFFF9E9FFFFFFFDBCB09BD9A9AD09E9090DAD090F09C90909BFAB0BC90DB0E90009000900000000000000000000000000000000000000000000000900F00090CBA0000000A09A0E9B0B0909000900009000000000000000000000000000000BFFBF9F9FFFFFFFDBC99FFFFADFF9E909009099ADB09A9ADA9BCF9D090B0B00900000000000000000000000000000000000000000000000000000000009090B09B0B00A00B00B0B09ACBDE9A9E90000000090000909000000000000000000009FBFFDA9BFBFFFBD9BFFFFFFFFFFEFF0F090A900900F0D090909B9EB0F09C90909090009000000000000000000000000000000000000000000000000909A0000000BF0000A0A0000B09B0B9C909A900900000090000000000000000000000000BFFFFBDA9FFFFFD0BFFFFFFFFFFFFFF9BFAD9AD00990B0F0BC9ADBDF90B0B0DA9A009000000000000000000000000000000000000000000000000000090909090900B000A0900A0B0B00F9E90B09C09000000900009900000000000000000009FFBF0F99FFFFF00BFFFFFFFFF9EBFF0BCFDA9F009000909909AD0BEB0F0909A90C90000000000000000000000000000000000000000000000000000000B0000A009AA000000A00000BCB9E9A90C9A90000000000000A0000000000000000009FFFFFF90BF9A9909FFFFFF9F009BDB0F0FBA9CB09AD00909E9A90BDB9D0BDA9090909A900000000000000000000000000000000000000000000000000090009090BA9F0A00A000A0B00B00B09A9B09A000000000900990000000000000000009BFDF990BDFFDF09BFFF9E9E99D000090909C0B0F009A000090DA99ADFA990DA9E9A0000000000000000000000000000000000000000000000000000009A900000009AB00000B00000A90F90F0D09A9D9000000000000E909000000000000000BFFBFE9A9B090900F9F9F9F9E9BD9B0CB090900909009090090090F9B0F00B0909090900900000000000000000000000000000000000000000000000090009009000B0F0000B0000000A9A0B09A9E9E0A9000000000009000000000000000009FFFF0909D09A909B9F9F9F9F9FCB0D99090B09909090000000090B00DFBDBC9ADA9CB00900000000000000000000000000000000000000000000000000090000009BCBB00A000A000A9A9AD09E9F90999C09000009009A90900000000000009FFF90B9F9A9FDFBFFFFFFFFFFFFBFDBF0BDA9CB0A90AD909090909909B90009A9090900900000000000000000000000000000000000000000000000009000000900B0A0A0000A00A00000B0BDA90ADB00A900000000000000000000000000000BFBE9090F9FFBFFFFFFFFFFFBFFDBF09F09DA9BD9E99A09A9A9A9ACB09E9B9AD09A9A09A0000000000000000000000000000000000000000000000090009000009009A9F00000B0000A00A00BCBD9ADBD00B0900009090909000000000000009FDF9900B0FADFFFFFFFFFFBDF9FBCBF09F0BDADA99E9DA9C90D099090B9C0D90B09C90D090900000000000000000000000000000000000000000000000000000009A00AB00A0000A000000B0B9A9ADA9A9009A0000000000000000000000000BFFBBCB099BDBFBFFFFFFBFFFAF9FBD09A0909909F09A99E9A9A90090900B9A0F09A990A90000000000000000000000000000000000000000000000000090000090A0B09F0000A9A0000A0B009ADADA9AD09000000090009090000000000000BFFDBC9909BCBFFFFFFFFFFFDBD9F0D0B9C99ADA9F09AD0F090D09E9B09E9900990D09A09009000000000000000000000000000000000000000000900000000090BC9000AA000000000000000B0DBDB0D09E9E909000000000000000000000099FBFDBF0BC099BDBFBFFFFDFBCA909A9009AC909B0BD0B90B0B0B0990F90B0F9E0B0B0C909A000000000000000000000000000000000000000000000000000900009A9AB0B0A000A000A00B000000BCBADA909A0090000000009000000000000BFFFBDBD9B9A9FADFFFFFFF9F99E09009A99A9B0C9CBD0F0D9C90B0FB0BD0909900909B00C90909000000000000000000000000000000000000000000000000009000A90BF0000A90000000000A9B0BD0B09E909000000000090000000000009FFFFFFFBF09D09FFBDFFFFBE9F099009000090C9B9A9A99B0B9BD9F90D0B9E9A0BDA9009A90000000000000000000000000000000000000000000000000009009ADA90A00B000000A0000A0A00900FF0B0DA90F000000000000000000000009BFFFBFDBD0DBA9BF9FFFFF9FDBE9B00900909009BC0DBD0F09CBC9E90F9BDA909D9009E9090A9000000000000000000000000000000000000000000000009000900900B0FBA0A00B000000000A00A90B9E9A9E90B00000000009090000000000FFFFFFFFBFBD9E99FBFFFFFBFDF9C9BC9B09C9B009B09A90F9BDBFBDB9E90DADB0A9B09A90900900000000000000000000000000000000000000000000000090A90A0B0B0AF00000A000000000009A9CBDAD09AD090000000000000000000009BFFFFFBDFDBE99F0BDBFFFFFFBFFBC9B0DA9A090909F09F90F0F99CBCB9F9B90D9C0D0900F0900090000000909000000000000000000000000000000000009009090B00AB9B0000A0000000A00B0A9A90B9A9E90BC000000000009000000000BFFFFFFFFBFF9FE9BDBFFFFFFFFDBCBD0F09D9F0F09A09E90FBDBEFBDBDA9E9E9A9B9A9A90900B0900009009E9ADA0000000000000000000000000000000000A900AB0A900EA0000900000000000A0B09E9E9F09A900000000000900000000009FFFFFFBFFF9FF9BCBDFFFFFFFFBFDB0B09F0B099009DB9BFBDBF9BDB0F9F9B9F9AD0D09C9009000009000B0909090000000000000000000000000000000090909A9009A9ABF0A0A0A00000000A09000A90BCBCBD09000000000000909000000FFFFFFFFF9FFF9FDBFBFFFFFFFFDFADF9DB0BC9E0D9A9CBD9DBCBFDBDF9E9E9E9C9A9A9A9A9A0900900009C909009B00000000000000000000000000000000900A90ABE0A00B0000A9000000000A0A0B0BC9A9B0AF0000000000000000000009BFFFFFFFFFFFBFBFDBDBFFFFFFFFBDB0F0DBDBB99A09B9EBEBFBF9FE9FBDB9F9B9BDB0D9090900900009009A90B00C90000000000000000000000000000000A909A990B00B0F00009A00A0A0000000900B0BCBCBD00900000000009000000000BFFFFFFFFFBDFFF9FFFFFFFFFFFBDFFDB9A909C9A99FCBDBDF9FDF9BFBC9F9ADE9E90F0BD0B09A00900009090C909B00000000000000000000000000000009009E0A0B00B0AA0A00A0A0909000A9A0A0B0F09A9B0B9E09000000000000000009FFFFFFFFFFFFF9FFFFBFFFFFFFFDFBCB9E9CBCB0D0F0B9F9FBF9FAFFDBDBE9F9B99CB990A9C9000900909A09090F000000000000000000000000000000000090BA90B00B0A0F0000B009AAA0000000000B0A90C9C009A0000000000900000000BFFFFFFFFFBFFFFBFFFFFFFFFFFBDDBDE90B909B0B9BDF9EBDEFBDF9ADBF9F0F0F0BC9E9DA900900000009000B09090000000000000000000000000000009A9AD0A9AA00A00B0A000BAE9000000A000090B9E9A09B90D9000000000000000009FFFFFFFFFFFFFBFFFFFFFFFFFFFFBE9A9F90FBCBD0F9A9F9FBDBFBDFF9F0F9F9F9F9B09A909A90909000009A90BCB00000000000000000000000000000000909A90090B00B0F0000B0B0A0A0A0A000A0A0A09A99AC09A0900000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFDF9F9F0BD09909B9CBDADBDBFDFBDBF9F9F09E90BC9F9E9E900A000909009C909000000000000000000000000000000009CADA0B0A000B00A000A0A00000000000000090BBC9E090BCB000000000000000009FFFFFFFFFFFFFFFBFFFFFFFFFFFFBCBD0BD0BDADB0FBDBFFFFDBFBDBF0F9F0BDB0BD9A9090909090900000900000900000000000000000000900000000009A90900A09A000AB000000000000A000A0000A000B09B09090B00000009000000009BFFFFFFFFFFFFFFFFFFFFFFFFFFFDF0BDA0F9A9BCB0DAF9BCBFCBDEBDBFE9F9AD9CBADADA9A9E9000000090B000000000000000000000000000000000009009A0B09A00A0A0F0000000A0A0000A0000A00A9A9F0D009A9C90000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFBF9F09DB09D099DBDBDFFBDBFFBDBFDB9F9F9BA9D909B0D09009090000000900000000000000000000000000000000000B00900A00B0009AA000000000009A000A00000000A0B9A9000B0000000000000000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCBDA09F9A9E9A9ADAF9FFF9FDBFDADFADAD0DB0BDAD9A909A000090000900000000000000000000000000000000009009E0A900A00A9A0B000000A0A00A000A000000B0B9BCAC00090C9000009000000009FFFFFFFFFDBFFBFFFFFFFFFFFFFFDBDA99F0AD0909FDBD9F0B0F0BFCB9FB9DB9FB0BDA9A090F0909000000900B00000000000000000000000000000009009009000A90A0000E00A0A090000000A9000A000A0A0B99090A90A00000000000009BFFFFFFBFFFFFFFFFFFFFFFFFFFFBF909F00990B9FA9ADBEBDBD9BD9BDFBDFADE90F099C9F09090DA90000000900000000000000000000000090000000A09A90A9A900A9A9A0B000000A0000A000A0A00000B0F0ACB0090090900009090000000BFFFFFFFFBDADBDBFFBFFFFFFFFDFFFA9BDADBC09FDBC99DADAF0BE9BDEBDB9BF99F0BB09A9A9A0000000000090000000000000000000000000000000900C0B000A0A000A00F00000A0000A000B0A0000A00BA90B00909000000000000000009FFFFFFFFBCB9B0BC9FDFFFFFFFFFF09D0909099BC9A9B0F0F9D9FD9FCB9DADFC9AD0BD0F0D09C9909000000000000000000000000000000000000000909A9900B00000A900AB0A00B0000A000A0A000A000B000B0DA00A009000009090000009FFFFFFFF9F9F0D99BDAFBFFFFFFFF9F0BCB0BCBC9A9C0F9B0B0A90B0B9FABDA9BDABD0B909A909A0000000000000000000000000000000000000000000090A0B0A00A90A0B0A00A000A0000000090A0000A0A9A9A0D009900000000000000009BFFFFFFFFFFBF9E90B99FDBFFFFF9E90909C909A909B90D0D09909090909DA9DABD09F0CB0D0B0090000000000000000000000000000000009000009099AC9A090B00A0B000F090A00000A00000A9A00009A000A9A0900AC0900900090000000BFFFFFFFFFEDBF9F9CBCBFFFFFFFFDBDB0B90B090DAD0B0B0F0FBCBCBC9A99E9D0F9A9B909A9090000000000000000000000000000000000000000000A09B09A0A00B000A00A9A000000A0000A00A00A00A00A09A90B00990000000900000000FFFFDFFFF9FBCBCBCB99F9FFFFFFFBE909C09C90B090BC9F9BD0090909AD0B9B0B9ADAD0F090CB09000000000000000000000000000000000000000090DA0A090B0A0A9A90AB0000A00A0000090A00000B00009ADAD0C900A000009000000009BFF9BBFF0FBC909099ACBDBFFFFFBD99CB09A9AC9BCBDBBD0F0F9BCB0909ADACBDAD990B090B90000000000000000000000000000000000009000009A9A9009AA00009A0A00FA000000000000A000000A0000A0BA9A9BC9090000000000000009FFFC909FFC0000000990B9BFFFFFADA9090009900909C0FBC900090BCB09099909ADAD09A90009000000000000000000000000000000000000000B0C9A0B0A090B0A000000B0A0A00A0000000A0000000A00000F0F00B0F0900000000000000BFF90090BFF00000000090FDFFFFDFBCB0E99000B090B9F0FE00000090090000090909A9C90DA900000000000000000000000000000000000000090B909A0F9A0A0A9A0B0A0F000000000000A0000000000000A9AA9BD0B09A000090000000009FBCB0009EF0900000090F9BFFFFFBD9099000B9D00900BFF00000000900090B0A9A9A9CB0B0900000000000000000000000000000000000900090900EB09A00B009A9A0000B00000A00000000000000A000A09E090F0ADAD000000000000000FFFFDA9009000000099A9B0FFFFFBC9A090009000BD009FFC0900000000000909D090D9A909000900000000000000000000000000000000000900DAC9B00A9AB0B0A0A9A000F0A0A000A0000000000000A0000ABFAB0F909A9000090900000099FFFBDA9000000900009C9FBFFFFFFE9DA0F009090A9F000F000000000009A0900B09A0900009000000000000000000000000000000000900009A09B00BE9A00A000B000A00B00000A000000000000000000000009AB0F09C00900000A900000ADBFFFFBD0B0900909C9B09FFFFFFBDA9900900D090090900000000009000900B09E99CA909000000000000000000000000000000000000090009000B00B09A90BA00A090A0F000000000000000000A00000000BAA90F9E9A900000009000009BFFFBFDFFBDF0F0A99A9CBFFFFFF9FF9CB09A90A90090F0F090009000090909090C90A900000000000000000000000000000000000000000000900BD0B0A0A9A0000A90A000A00A00000000000000000A00000A0090A9A090090000000000000BFFFFFFBE9E9B9F9DA9DBBDFBFFFFE9CB0C90099009000909CA9900D99CB09AD0B90B909000000000000000000000000000000000000009000000090A0B0A9ACAB0A90A0000B0000000000000000000000000009A0A9ADBE90000090009000090F9FFBFDF9F9E90B099A9FFFFFBFF9FBC9B00900090B0909A99CA990A0909C9A9CA90C00900000000000000000000000000000000000000000090B0A9A0B00AB0000A000000F0000A0000000000000000000000A9A00009009090000000000009FAF9E9ADA0909009AC9E9BFFFFFBFCBB0C9F0F0B09090BC90B99CB990B0B0090900B0900000000000000000000000000000000000000000000000D0A9A0B000B00A9A0A000B0A000000000000000000000000A0A09A90ADB00009B000090009BFF9E9C9090000090D9B9FFFFFDFFFBD0F9A090900000909A9C9A900E90D09F09A9D09A900000000000000000000000000000000000000900009090A9A090A9A00A00000000A000A00000000000000000000000900A0009000909000000000009FFFF9B00000909ADBAFFDFBFFBFFADAF90090000090909AD09ADBCB99E9A000090A00000000000000000000000000000000000000000000000000A9A0B0A9A00A00A0A0000F0A000000000000000000000000A0A000B0009000090900090900BFFFFFFFBDB9FBFFBDF9FBFF9FFFDFBDFE990090900000090BC90090009090909E090909000000000000000000000000000000000000000000909ADA900B000A00000000000B00A000000000000000000000000B0A9000F00B9000A0900000909FFFFFFFFFFFFFF9FBFFFFFFFFFFBFFBF9EBD0A000000000009A9A9CB000000B0900000000000000000000000000000000000000000000000000090A0A9EBA09A0A00000000F00000A00000000000000000000B0000A090090C0909000000000BFFFFFFFFFFFFFFFFFFFBFFFFBFFFFFDFFFDAF99090900900900D00900909090900909090000000000000000000000000000000000000000009A90A9E9A900AA000000A0000A0A00000000000000000000000A000A00000B009B000090000009FFFFFFFFFFFFFFBFFFFFFFFFFFFFFFBFFFBFFFDEF0B009009009009009CB09000000000000000000000000000000000000000000000000000000009A9A0A0A900A000000000F00A00000000000000000000000BA900A00900B00F090009000909FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFBDBCBDA09A09A09A90B00000000000090000000000000000000000000000000000000900009090FA00090000A00000000000B00000000000000000000000000000A900009009009E9A0009009FFFFFFFFFFFFFFFFFFFFFFFBFFFBFFFFFFFFFBFFFFBCBDBC9F0900009090000000000000000000000000000000000000000000000000000900A0900B0A00A00000000000000E00000000000000000000000000B0A00A0000090B00909909009ABFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFADFF9E9B0000090900000900000000000000000000000000000000000000000000000090090DA0B0A90A000000000000000B0A00000000000000000000000A0A000000A9B0090B0BCA00A909FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFDFFB9EBD0090900000000000000000000000000000000000000000000000000000000090000B09A0090A0000000000000000F000000000000000000000000000000000000099AD0D09090900BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFFFFBF9EF9009009090900000000000000000000000000000000000000000000000000009000090DA00B0A000000000000000000A0000000000000000000000000000000000000AC9A90B000009C9BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBFFFF9DE900900A090000009000000000000000000000000000000000000000000009000090A909A90B0A00000000000000000F00000000000000000000000000000000000090909CB0D0B0009BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFDAF9EB900DA90900000090000000000000000000000000000000000000000000000000000090BC90A00000000000000000000B000000000000000000000000000000000000A90B0B09A90090B0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFFBF9F9000B0900090009000000000000000000000000000000000000000000000090000090A9AD00B000000000000000000000E00000000000000000000000000000000000090090DAD0900009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBDFDBCB090090909000000000900000000000000000000000000000000000000000000000009C90BA0A00000000000000000000B00000000000000000000000000000000000009ADA90B00F009A9BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0BCBD0090B00B09000900900090000000000000000000000000000000000000900000900A90BD090000000000000000000000F00000000000000000000000000000000000A009090BCBD090009FFFFFFFFFFFBFFFFFFFFFFFFFFFFFFBFFFA90F9DB9A090D00900009000000000000000000000000000000000000000000000000000900090F00A0A000000000000000000000A0000000000000000000000000000000000000B0B0F0900B0099BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9FF9DA9C0909A90090000090900900000000000000000000000000000000000009000000000BDA9A9000000000000000000000000B000000000000000000000000000000A00000909C909EBD00B0C9BFFFFFFFFFDBFFFFFFFFFFFFFFFFFFFFBD909EBDA90009009009009000000000000000000000000000000000000000000000000090090009C00B00000000000000A00000000E000000000000000000000000000000000000009ADB090AD009A0FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9FBDB0F0900B900900000909000090000000000000000000000000000000000000000000009BCA9A0000000000000000000A00000B000000000000000000000000000000000000B0A909F9F9A900999FFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFDA9FADF90009C0000900900000900000000000000000000000000000000000009000090000B00990900A0000000000000000000000A00000000000000000000000000000000000000D0BE9A00DAD090BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFF090F9F90009A9090B0090090090000009000000000000000000000000000000000000009AD9E90E00A000000000000000000000000F00000000000000000000000000000000000B09A909E9F9A909A9E9FFFFFFFF9FFFFFFFFFFFFFFFFFFFDFF09F9F9A900090009000009090000900000000000000000000000000000000000000000000A09009000000000000000000000000000A000000000000000000000000000000000000009E9E9A9C9E90909BDBFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A9E9ED000909000900900009090000000000000000000000000000000000090000000099D0A9A0A09A00000000000000000000000B00000000000000000000000000000000000A09A909F9E9A90F09BCBFFFFFFFDBFFFFFFFFFFFFFFFFFFFFBC09BDA90909A00090000090900A0900009000000000000000000000000000000000000BCA009C9000A000000000000000000000000E0000000000000000000000000000000000099AD00B0A9E9E90BC0B9FBFFFFFAFFFFFFFFFFFFFFFFFFF9FC99FDA9000090909000909A0009090009000000000000009000000000000090000000090090B0B0A009000000000000000000000000B000000000000000000000000000000000A0A0DAFD099E909E90BD0F9FFFFFFDBFFFFFFFFFFFFFFFFFFFBF009A909009E0009A9000090900000000000090000000000000000000000000000009A0F009CBC000A00A0000000000000000000000B000000000000000000000000000000000090B09A9AC99FA99AD0B9ADABFFFFBFFFFFFFFFFFFFFFFFFFFDE900D0009009900000090900090909009000000000000000009000000000000000000D9009A90B00090000000000000000000000000E000000000000000A000000000000000000000DADADB0E9DAD09BC09BDF9FFFF9FFFFFFFFFFFFFFFFFFFAF099B09000B000909000009090000000000900090000000000000000000090000000B0AD0B0DA00000A000000000000000000000000B00000000000000000000000000000000000A9B0BDB0E9FADA9BCBDBCB9FFFF0FFFFFFFFFFFFFFFFFBF9FD0BC0009009009A900090900A09A9000900000000000000000000000000000000099C090B0DA9A09A00000000000000000000000000A00A0000000000A00000000000000A0000000009CB0F9B09F9E9B0B09ADBFFFFDBFFFFFFFFBFFFFFFFFFF0C0B0B000909000009000009090000900000090000009A0090000000000000000000BDA9C9A000A0000A00000000000000000000000F0000000000000000A0000000000000000000BCA9CBCF0F0BCBCE9CB0DADBFFFADBFFFDFFFFFFBFFFDBDFB09C909009E0900900090900009090000000000000000900009000000009000000B0009A0F9A009A000000000000000000000000000B00000000000000000000A00000A0000000000090B9B0F9E909B99A9CB9A9FFBDBFFDFBFFFFFFFFFFBEFBC9A900000090009000000009090000090009000009000000000000000B0000000909E90D90009A00000000000000000000000000000A0000000000000000000000A000000000A0000B000C0FDA90BCBCAFDB90FDFBDCBFFFFFFFDFFFDFBDFDBC9090B090090909009090909000A9000000000909000A900009000000900000009ADA90F00A0B0000000A00000000000000000000000F00000000000000A000000000000000000000000B0909ADF090DBD9A9E99A9FFB9FFBFFBFBFBFBFDAFBDB0000000090A0000000000000090009000009000A0090000000000000C90000900909E90B00000A00000000000000000000000000000B00000000000000000A00A000A000000000A000000A909ADF0B0FADDA9EBDA9FED9FF9FDFFFFDFAF9DAFCB0090000090900900000900900900000900009090000009000000090A00000009A090E900A9A0000A00000000000000000000000000A000000000000A000A0000000000A000000000A009000A9A0F0F9FAB9C90B9EDB9A9FFFBFADBFFD9EBDB9C090090090000900009000000000000000909000009009000900000090000000AD0F090A90000000000000000000000000000000000F000000000000000000000000000000000000009A009E9C9F9F0F0D0DA9E9C9B9E9FFDBDFDFFDB9A99F9E90000000000900000000009090090009000A000000000000900000090000000990B09A000A00000A0000A0000000A00000000000000A00000000000000A00000000000000000A0000000B0090B00B0F9FAFAD09A9E9E9ADBBEFBF9FBED0FF0F0000900009000000000009000000000000909000009A90000000009A0000900BCAD0A00A00000A00000000000A00000000000000A000B00000000000000000000A0000A00000000000A0000B00090CBCAD9F9A9C90909C9ADF9FDBF0F9A90BD9F00000000090090000000000000000900B00C09090000000900000090000009090A90B009A0000000000000000000000000000000000E000000000000A00A00A0000000000A0000000000A9009E0B09F9AF0DF09ADA9A909B0F9ADAF9E99F9A909090000000000000000000900009000900B000000900090000009000009009E0900A00B0000000A000000A00000000000000A0A0000B000000000000000000000000000000009000A000000B00909ADAD09A09A909C90E90DB0FDBDBC9E9A9DA00000009000000000090000009000A900D09009C90E900000009A0090900DA09A0B09A0000B00000000000000000000000000000000A00000000000000000B00000B000A0000A0A009000B00900C000BEDADBC9CB0009090BC9B0BC9B090DA090000000000000000000000000009090E9A0090A0A900000000000000000B00B0B00A0000A000A0000A0000000000000000000000000B000000000000000A000A0A00000000000009000A000A00B009099A90F0B0CB0B00090BC9D0BC9ADB0900000000000000000000000000900000900900090900900000009000900BC0B0B00F09A9A00000900A000A000A000000000000A000A00E000000000000A0000A00000A0A000A000000A00000000900B00009E90FCB09C0090E909A0BC9A9009000000000000000000000000090000B09A9D0B9E0000B00000009000000F00B000F0A0A000900A0A00090000000000000A000000000000B000000000000000000000000000A000A00A0000000000009009E9E90F0B0D0A900A90000909AD090000900000000000000000000000A09D0DAC9A00000009C00000900A009E900B0BEB0A909A9A0A09000A00A0000000000A00000000000000A0000000000000000A0000A000000090000000A000A000000090090CB0C9CBC9C9FD0009000009E000000000000009000900000009090900B0900090D009E9000000A0909A09A9B0A90090A0A000000A00000000000A000000900000090A0000F0000000000000000000A00000A000A00000009000000A00000B00B0F9B0B09A000AD00000090090000000000900000D009009000A00D0B0090BCBCB00B00000000900000DAD0ACBCA9A0B09000A9A000A00B0000000000000A0A00000000000A00000000000A000000A09000A000A000A000000A00000900000000900C00DAD09FF00000000E9000000000000009CB0000000090909A90DADA90900090000009000009CB090B0B0A90E9ACA0B000009009000A0000000000000000000A00000B0000000000000A00A900A0A000000000000A9A00000000A0000909A0FBC9A90FF000000000090009090000090C9A0000B00BC9A9E9E90CB000000000009009000009B0B0D0A0B009AA900B000A9A00A00A00000A000000000000000A0000000E000000000000000000A90000009A00A0000000000000A09A9000009090B0DCB00F090000090009A0009009000B00909000900BC9009CB0000000000000090A000900C9CB0B0DA9A0900B009A9000000B00A0A000000000A00A0000000000000B0000000000A000000000A00A000000000A0009A0000000000A00000000DAA9CB00009CB00C9AD0909E090CB0000000099E0F9000BCA0000009000000900A90000E9A9A9C9EB0000BCB00A000A0B0A00000900000000000000000000000A0000A0000000A00000A0000A009000A00A09A090A0000A0000000A90000090F09D0BC90B0E90FDBAD0ACBC09A0900D0D0B0F0E90000BC009000090000000900D00090900D0DA9B00B0FB0A00B0B0A090009000A000A000000000000000000A000000F000000000000000A000000A00000000000000A000000000000A0A000A0900A09AD009BE9A0C9AD9C0B0C9009A90BC90090D0E90090000000000090F00B00000A0BDA9A9AC0BCB0000B00000900A00A0A000A0000A0000A00A000A0000000000A00000A0000A000000000A00A000B00A000A0000000000000000000B0900B0D9EDA0D009C9DBC9A0B9CB0EBDADAD00AD000A9000000C009000000A000D000009C9009E9E9A90A0B0B00A9A9A0B00A00090A00000000000000000000000000000B0000000000000A00B00000900A000090A00000000000A00000000000A000A0B0FDAAF0F0A00ADDACB0D90E90000AD000AD00000009A000000000909A0090B00B0F09B09A0A9000A0B0000000000900A0090A90A000000000000000000A00000E0000000A0000B00000A9A0A000000A000000A000009000000000000009A0900B0BDD0D09D099A0BD0E0AD000009000009000000000090000000D0E0090A0C90C90BE0A00900A9A0900B0A0B0A000A000A0000000B0A000A000A0A0A00000000B0000000000A0000A00000000000A0000000000000A00000000000000A0090A0000FABEBEACA0C9C0A9090000000000900000000909000000909A90900C909ADBAF0090B0A0B0009A0A00900009A0009A90A0A00000000000000009000000000A000000000000A0000A9A9A00A000000A00A090A00000000A00000000000A09A9A909C9C9DBD09A09C00CB000000090000000000000000000AC00000D0BCBC9A090B0A000900A9A0009A0A0A9A0009A00A09009A000000000000B0A000000000B00000000000009A000A000B0000000900000A0000000A000000A0000000000000A0A9A0A00BE090A90090F090900A00000000000000000BC9090900A9090BE9AAC090A9A0A90000A00090B0009A000B09A0A000000A00000000009A00A00000E00000000000B0A000009A000000A00A000900000000000000000000000000A00000900909FC9CADC0F00009E000909AD0A000000009CB0000A0AC9E90F0F0B0D0B0A0900900A09000A9E000B0A00A000A0900A00A0000000000A9A000000000B000000000A000000A90A0000A00000000A0A0000000000000000000000000000000A00A0A0BFADB0B09CB0F00C00000009000009ADA00C9CB09C9A90F0BA90A0B0B090A0A0000A0B09A009A00000900B000A90000000000000B0000000000A0A00000000000A0A00000000A000B00A900000000A00A00A00A000000000000000000000009000DADEDE0AC90F9B000000000909BC909F09A90D0B09E9AF0DA09A0000A00000A90000A00B0A90A09A0A00900000000000000000000A0A0000090B00000000000000900A0A090B0000000A00900A000000000000A00A00000000A00000000000B0B0B0BD090000ACBCB09090DADADAFCA9E9CB0AD0FE9E90BA9A09A9A000000000A09A9A00B0AF9A00000A0A000A00000A000A00000900000A000E00000000000000A0A0909A000B0A90000A0000009A000000000000000A0000000A00A0000A0000000ADA0B00D90F0DADAFADADAD9BDE9FADFDAF09A9A00000000009A00000000000A0B00BB0A00B0B00000000000A0000000000A000000000AB0000000000000A000000A0A9A0000A00000A009A00000000000000000000A00000000000000A9A0B0900D0DB0ACB0F9E90D0F9ADAE0BDAD9A0B09A000000B0A00B0A000A90000B0B090A9ACB09A00009000000000000000000A000A000009A9A0000000000A0090B00A9A90000B00090A9000000000A09000000A0000000000A000090A000900000000B09A0D09C9ACBCB0B0ADAD9F0A90A0B0A09A9A9A0000B009000000A0A00000A00A9A0A009A0A0A00A000A0000000000000000000A000B0000000000000A0000000A0A900A00A000A9A0A000000A000000000A00A000000000A00000A00B0A0B00A09A09A009000000D090A009000900000000009A0900A0A0B00000090A9A90A9009009A009000000000000B000000009A90A0000000E000000000000000A000A90090A9000009A9009000A0000000A0009000000000000A0000000000009000900A09A0900A9A009A0A090A0A9A00090A00A00000A000000000A9A9A00000A90A0A00A09A0A9A090000A9A00A000A00A00000000A00B00000000000A00000A900A0A00009A0B00A0A00B00000000000A00A0000000000000000A0000009A0A0A0B09A0A00B00090A09000A909AC0A0A090009A00000009A9A0B0000009A0B00A0909A90A009000A0A090000B000000A90A0000A0000A0000000000000A00000A0900B00A0000A90900000090A00A00000000A00000000000A00009A0A0009090000A909000000A09A00B090A00B009000A000000B00A00000009A0A0A0000B090A0A0A009AA0B000000A00BA0A00000009A00000B00F00000000000000000009A0A00A90B0B00A0A0B000A0000000000000900A0000A00000009000090A0A000B090A000B000000A9A00A0B0B00B0A0B009A000000900A9A0A9A90090A9A00A00000909A090000A90009A0090000000A00000000000A0000000000000000A0A00090B0A00000B090000B00000B0000A00A0000000000000009A0A0900A9000A000A000A000B0B0090000900000B009000A000B0000A00000900A0A9A09A0B090B0B0A0A09A09A090A0A0000A0000A00000000000A00B000000000000000000900B0A0090A0B0000A90A0090A0009A0000000A0000A000000A00000A0A0000009A0090000A00000A0A9A00A0B0B0000A9A9000000A000009A0A9009009A9000A000009009A0BA00A0009000000A0000000A0000A0000E00000000000000000A0B0000B0A0900B0A90A0900A0000A0000090009000090000A900000900900A90A09A000A09000A00090000000000A0A900000B0A00090A0A000000A0A0A0A9A90A0A9A0A0A0BC9A900000A000B000000A0090A00009A0B000000000000000000000A900090A00000000A0000009000000A00A0A0000000A0000000A00A00000000000A000A00900000A000000009090A00A00009000A0900000000090900000A09000000090BA00A0B0A090000A9A00000A000000A000A0000000000000000000A900A9A0A90B0000B000A90A00000A000000900A000A0000A000B00090A90A09A0000090000000A00000B0000A0A0009A90A0A00A000000A09A090A0A0A90B0BA0A0B09A0A09A9000000A000A90000009000000009A0F000000000000000000000A000009A000B00009000000A00900009A000009A00009000A0000A000000000090000A000A000009000000B009009A0009000000B0A00000000A009090A0B0090000A0909A00A9A0A900A0B0A00000000A00000A00A0000000000000000000000A9A9A00B000A0000A00A9000A0000A0000A00000000A000900090A00A09A000A00A000B00000000A00A00000A0A00000A000000000B000A0A0090A00A9B0BA0B0B000A0ADA9000900A09009AB00A00A0000000000B0000000000000000000A09000A9A00A900B00000000000000B0000A900A0090A000A00A0A0000090009A000000000009A00A000000B0000090A9A009A000A0B00A0090900A900A0ABADB00A0B090A900A0B0A0000A0B0BCB000000000000000E000000000000000000000A0009A09A90A000A000000A000000009A000900A000000900900009A0A0A00000A90A900A00000000009000A9000A0009A000A9000BA90A0A0A000A09090BA0AB0000A09A0B000000B00000A9A0000009A00000A00B00000000000000000000000A9A09A00A09000000A0000A00A00A0000A009000000A00A0000A0090090A000000000B00A000000A0A00000A0B090A000000009ABCA0009009A0900A0FA00909A0B09A0B000A09A00A09A9A0A0000A0000000000A0000000000000000000000000DA009A9A0A00000000000000000000900A00A00A9000000A9000000000B0B0A00A00090000A009009A0B00000A90A00000A0A90B009A0A000A0A09A009A0A0900A09AC0B09A09A09A0000090A00000A00A0000B000000000000000000000A9A0A9A000090000A0000A00000000900A00000000000A090A000A9A0A0A00000090090A0A0A00000000000000B0B0A909A000900A9A90A909A09090A09A0A9000A090A09A00A00A0B0000A0B0A00B000000000000E0000000000000000000000009A0009A9A000000000000000A00A0000A900000A0090A009000000900900A0000A00900090B00A0A9A09A0B00A9A0A000A0000000AADA0A00A00A90A0900E9A90AC90A09A90B0900A9A90009AB000A00000A000B0000000000000000000A00A9A009A000000A000000000A0000000A0000A09A0090A00000A900A00A00A900A0090A00B00A009000000A0000A9000000000A0B0A9A90A900000A90A90A0A9A00A9A0A09A00A000A000000A9E000A0000A00000BA000000000000000A000090000B000A00A00000000000000000A09009A00A0000A0000A0000A090900000A09A0A00000A0000A00B0B090B0B0A9A9A9A0090000009AA9A9A9A90A90A09A9A9A900B09A00B090A0009A0B09A9A00000009A00000F000000000000000000000A9A00A0000000000000000000000000A00009090A9000B0900B0090A0A09A9090000090B0009A90000000A0000009A000000A000009A090A00000A90A09A000009A090AA0B00A009000A090A00A9A090000000000AA00000000000000000000A0000000000000000000000000000000000A0A00A00A9A00A0000A000000A00A0A9A9A0A00B0A00A9A9A9A90B0B0B09A9A00B00A00A00B0A9A9A0B00B0B09A9A9A00BA909000B00A0A0B09AB0BA9A000A0B0A0000A9B00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A000A00A000000000000000000000B00B0A00A000A00B000A0A9A000000000A000000000000000000000000000000000000000000105000000000000DFAD05FE")}, + {empKey(9),ToByteArray("151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E506963747572650001050000020000000700000050427275736800000000000000000020540000424D16540000000000007600000028000000C0000000DF0000000100040000000000A0530000CE0E0000D80E0000000000000000000000000000000080000080000000808000800000008000800080800000C0C0C000808080000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00D999D999999D9D999DB99DD9F9D9D9D99D99D9F9D999BD9FD9D9D9D9D9DFD9F9F9DBDBD9DBD9DB9FD9FDFFFFFFFFFFFFFFFFFFF9D9F9DB9D9DFD9DB9F9BDF9F9D9DBD9BD9F99DBD99999999D9DBDBD9BDDD9F9DBDBD9F9D9D99D9BD999D99D9D99DB99D999D9F9D9F9D9D9999D9B9BDBD99F9D9999D9DBD9FDBDBDBDBDBD9F9D9DBD9D99FD9F9DD9FDD9FFFFFFFFFFFFFFFFFFDBDF9DBDD9F9DBD9DD9DD9D9DDBD9D9DD9D9D99D99999D99D9F9D9D9DD99BD9D9D9D9F9DBD9FD9DD999DB9DBD99D99D999D999D9BD9D9DBDBDD9D9DD9D9DD999999999FD9F9D9FD9D9D9D9F9D9F9D9DBDF99F9DF9F9F9FFFFFFFFFFFFFFFFFFFFD99DF9DB9DF9D9D9F9F9F9F9BD9F99F9F9DBDF9D99D999D9D9D9D9F99BDD9D9F9F9F9D9D9D99BD999999D9D9F99D999999D9F9D9D99F99DD99B9D999D9B99D999D9D99FD9DBDD9DFDBDBD9D9D9F9F9D99DF9FD9FD9FDFFFFFFFFFFFFFFFFFFFFBDF99D9DFD9F9DBD9D9D9D9DD9D9DD9D9D9D99F99999999B9DBDBD9D9D99DBD9D9D9D9F9F9D9D9BD999D9F9D99DBD9D999BD9D99D9D9D999D9D99F9F9D9DB9D9999BD99F9DDB9F99D9DD9F9F9D9D9DBD99D9DBD9FD9FFFFFFFFFFFFFFFFFFFFFD9DDBDB9DBD9F9DBDBDBDBD99F9F99F9F9F9D99D9999D99D99D9D9DBD9F9D99F9DBDBD9D9DBD9DD9F99999DBD9999999F9D9DBD9BDBD9FD9BD99D9D9D9D9D999999D9DFD9F9DD9DF9F9DBD9D9F9D9D9D9FDF9DFD9FDDFFFFFFFFFFFFFFFFFFFFF9F9D9DDBD9F9D9D9D9D9D9FD9D9DF9D9D9DBD9F999999D99D9DBD9D9D9DBD9D9D9D9DBD9D9D9B9D999D9D9D99D9D9999D9F99D9D9D99D99D99F9D99DB999999D9D9DF99F9DDBDF9D9DBD9D9F9DBDBDBDDB9DBD9F9FFFFFFFFFFFFFFFFFFFFFFFD9FDBDBDBD9D9DBDBDF9F9D9BD9F9D9F9F9D9D999D9D999DBDBD9F9DBD9D9F9DBD9F9D9F9DBD9D9D9999BD9F9B99999D99D9D9BD9D9D9BD99D9D9BD99D99D999F9F99FD9DBD9D9D9F9D9DBD9D9D9D9D99DDBDF9DD9FFFFFFFFFFFFFFFFFFFFFF9F99D9D9DFD9F9D9DD9DD9FDD9D9DBD9D9D9DBD999999D99D9D9D9D9DBD9D9D9DBD9F9D9D9D99D9F999D9D9DD9D99999FD9F9D9D9BDBDD9DF99F9D9D9BD99999D9DD9D9F9DBDBDBD9F9F9D9F9F9F9DBD9BDDBDF9FDFFFFFFFFFFFFFFFFFFFFFFDDF9FDBD9DBD9DBDB9F9BD99F9F9D9D9F9DBD9FD999D9BD99D9F9DBD9D9F9DBD9D9D9DBD9F9D9BD9D99999F99D999D9D99D9DB99FD9D99D99DD9D9DB9D9999DBDDB9DBD9DBDDD9D9D9D9DBD9D9D9DBD9FD9FD9DF9FFFFFFFFFFFFFFFFFFFFFFFFBDD9DD9F9D9D9D9DD9DD9FD9D9D9F9F9D9D9D9999D99D9D99F9D9D9F9D9DBD9F9DBDBD9D9DB9D9F99999D9D999999DBDF9D99D9D99D9F9BDDB9DBD9D99D9D9D9B9DBD9DBDDB9F9F9F9FD9D9F9F9F9D9D9FDBDF9DFFFFFFFFFFFFFFFFFFFFFFFFD99F99F9DBDBDF9F9FDB9D99F9F9D9D9DBD9F9D9999D999BD9D9F9F9D9F9D9D9DBD9D9DBD9DD9D9D999D9D9D999D99D99DB9DBD9D9F9D9D99DBD99D9999B9DBDDD9DDF9DF9DDD9D9DD9BDBD9D9D9DBDBDBDDBDFFFFFFFFFFFFFFFFFFFFFFFFFFDFD9FDDBD9D9D9D9DD9DF9FD9D9F9D9F9D9D9F99D9DB9D9D99BD9D9DBD9DBDBD9DD9DF9DBD99F9F9D9999F999999F99D9D9D9D9DBD9D9D9FD9D9D9BD9D99D9D9BD9B9D999F9F9F9F99DD9DBDBDBD9D9D9DBDD9DDFFFFFFFFFFFFFFFFFFFFFFFFF9FD9BDDBD9F9F9F9BD99D9F9D9D9F9D9DB9D9D99B9D99D9DD99F9D9DBD9D9DBDB9F99D9D9F99D9DB99999D99999D9F9F9DBD9F9D9F9F9D99D9DBD99999D9DBDDD9DDBDFD9D9D9D9DF99F9D9D9D9F9F9F9DF9F9FFFFFFFFFFFFFFFFFFFFFFFFFFD9DFDBDD9D9D9D9DD9DF9D9DBD9D9D9BD9D9F99D9D9F99F99D9D9F9DD9F9F9D9DD9DF9F9D9DD99F9D99D9999D9F9D9D9D9D9D9D9D9D99D9F9D9D9D999D9F9D99B9F99D9D9F9F9F9F99F9D9F9F9F9D9D9DF9FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBD9DB9F9F9FDBDBDB9DBDBD9F9DBDD9D9F9D99999D9D99D9FDBD9F9BD9D9DBDBDB9D9DBD9B9D9D9999BD999D9DBD9DBD9DBD9DBD9D9F9D9BD9999D9BD9F9FDD9DFDBDBDD9DD9D99D9DBD9D9D9DBD9DBDF99FFFFFFFFFFFFFFFFFFFFFFFFFFFF9DBDFDD9D9DD9D9D9DD9D9D9D9D9D99F9D9DB9D9DF9D99DBD99D9D9DDDBDF9D9D9DD9F9D9D9D9F9D999D9999BD9D9F9D9F9D9F9D9DBD9D9D9DF9D99D9D9DD9BD999DD9D99F9DBDFD9D9D9F9F9D9DBDBD9DFDDFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD9DB9DBDB9F9F9F9BDBD9F9DBD9D9DDB9DD99999DBDD999D9FDBDBD9D9DBDBDB9FD9D9F9DBD9DBD999D99D9D9D9D9DBD9F9D9DBD9D9F9D999999D9D9F9BDD99DF99DBDF9DBD999F9F9D99D9F9FD9D9F9DBFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9BDBDD9D9DD9D9D9DD9D9F9D9D9F9D99D99F9D9DDD99BD9DBD99D9D9F9F9D9DDDD9DF9F9D9D9DBD999D99D9DBDBD9F9D9D9DF9F9D9F9D9BD9D99D9BDBD9DD999F99FD9D9DBD9D9D9D9D9FD9F9D99F9F9FFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDDD9DB9F9DBDBDF9F9F9FD9DBD9D9F9D9DF999F9B99DD9D9D9FD9DBD9D9D9FDB9BDB9D9DF9DBD9DD999B9B9D9D99D9F9D9F99D9D9D99D9D999D9BD9D9DD9BD99DDD99F9F9D9DBDBDBDBD9BD9D9FD99DFD9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9FD9D9F9D9DD9D9D9DD9F9DBD9D9F9F99D9D99DDF99DB9D999F9DBDBDBDD9DDD9DD9FD9D99DBDBD99D9D99F9DBD99DBDDDBD9DBDD9BD9F999D99D9F9BDD999B9DF9D9D9DBD9D9D9D9FDD9F9F9FDF9FFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD9FD9D9DBDB9F9F9F9BD9D9D9F9D9D9D9F99D999D9F9D9BDDD9D9D9D9DB9F9BDDBDF9BDBD9D99DBD9999DF9D9D9DF9D9F9D9F9D9BD99D99D999DBD9DD9BD99DD99D9F9F99D9BDBDBD99BD9D9D9D99D9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F99F9DBD9DD9D9D9DD9F9F99D999D99DD9DBD9D9D9D9DD9B9DBDBDBD9DD9DDDBD99DD9D9F9DDBD9999DF99DBDBD9D9F9DBD99D9DD9D99D99BD9DDBDBDD999DB9DBD9D9DDBDD9D9D9FDD9F9F9F9BDFFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D9D9DBD9DBDBDBDBDBD9D9DD9FDF99F9BD9D9DBDBD9F999DD9D9D9D9F9F9F9BD9FDDBDBD9DB9D9D99D99DF9D9D9DBD9D9D9D9F999DB9D99D9D9F9D9D99D99D9D9D9DBDB9D9BDBDBD99BD9D9D9DD9D9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9F9F9D9F9D9D9DD9D99F9F9BD9999D9DDB99F9D99D99DBD99BD9BDBD9D9DDDDBD9F9DD9DBD9DBDBD9DF9D9DBD9F9DBD9F9DBD9DF99D99F99DBD9DBD9DB999BD9DBD9D9D99D9D9D9DFD9F9F9FDBD9FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD99DBD9F9F9F9F9FDD9D9DD9DD9D9999D9D9D9DDBDD9D9FD99DD9DBDBDB99FDBD9F9F9D9D9D9D9D9BDDBD9D9D9D9D9F9D9D9D99D999D99D9D9F9D9BD9D99D999D9F9D9F99D9F9F999D9D9D9DD9F9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F9DBD9D9D9D9D9D9DBDBD99F9BD9F9DDF99DBDB9D9BD9D99DDB9D9D9D9DDF9D9DF9D9DBDBDBDFDBDDDBD9F9F9F9F9F9DBD9F9FD9F9D9D9F9D9D9F9D9999D99DF9D9DBD9DBDBD9D9DDBDBDBDB9F9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9F9D9F9F9F9F9F9F9D9D9FD99D9D9DB99D9D9D9D99D9DB9DB99D9F9F9F9F9F9DF9F9DBD9D99D99DBF9DF9DD9DD9DD9D9D9D9D99D99F99D9D9F9D9D99D999BD99DBD99D99D9D9DBDB9D9D9D9DD9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9DBD9D9D9D9D9D9F9F9D999D9F9999DF99F9D9F9F9D99D9D9DBD9D9D9D9D9FD9D9FDD9F9DDBDFDDDF9DF99F9BDB9F9F9DBD9D99D99DBD9FD9F9D9DB9D99D9D99D9FD9F99F9F9D9D9F9F9F99F9BDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F9DBDBDBDBD9F9D9D9F9DDF9D9DFD99DF9D9BD9D9DBDDBD9D9D9DBDBDBD9F9BDBD9BD9D9B9D99BDF9DF9DF9DDDDD9D9DD9DBDBD99D9D9D99D9DB999D999D99BD9D99D99D9D9DBDBD9D9D9DF9DDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9D9D9D9D9D9F9DBD9D9DB99D9D999D99D99DD9F9D9D999DB9D9F9D9DD9DBDDD9D9FDDF9DDDBDFD9DDF9DF9DBDB9BDBDB9F9D9D9D9F9DBD9FD999D9D9999F99D9DB9D99DBDB9F9D9DBDBDBD99F9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9DBD9F9F9F9D9D9DBDBD9DD9F99DDB9DB9DB9D9DBDBD9D99D9D99DBDBDBDDB9F9FD9F9DBDBD9D9F9F9F99FDD9DDD9D9DD9D9D9DBD9D9D9D99F9D9D9BD9D99D9D99D9F9D99DD9DBD9D9D9D9FD9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D9F9DD9D9F9F9F9D9D9DB9D99DB99D9D99DF9F9D9D9F9FD9F99D9D9D9DF9DD9D99F9F9D9DBDBDDFDDDFD9F9DB9FDBDBDF9F9F9D9DBDBDBD99D999D9999D999BDDBD999D9BDBD9DB9DBDBD9BD9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D9DBDBD9D9DD9F9F9F9DF9DF9D9D99FD99D9D9F9D9D999D9D9F9BDBD99F9F9FD9DD9F9FD9DDBDDBDBDBD9DBDD9DD9D9DD9D9D9F9D9D9D9D99DBD99D99BD9FD99D9DDB9D99D9F9D9BD9D9FD9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9DBDBD9D9F9DB9F9D9D9D99D99D9F99D99D999F9D9F9DBDDB9F99DD9D9FD9D9D9DF9BDD9DBDF9DFFDFDDDFDF9D9F99F9F9BDBD9F9D9F9D9DB9D99D9F99DD9D999F99B99D9D9F9D9BDDDBDBD9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99D9D9F9D9F9DD9D9F9DBDDBD9D99DB9D99FD9DBD9D9D999D99D999F9D9F9F9DB9DDDBDBDF9DBD9F9F9F9D9DF9DFD9D9DDD99D9DBD9D9F9D99D9999D999B99DD9D9D9D9DBD999DD9B9D9D9F9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF9DBD9F99D999F9D9D9D99D9B99D99DB9D99F9D9DBD9D9D9D999FD9DBD9D9F9DDBDBD9DD9FDFDFDFDF9FDF9DF9DBDBDB9FDBDD9D9DBD9D9D99F9D9BD9D9D9B9D9D9D9B9D9DDB99DD9F9F9DDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9DBD9D9DDF9DF9D9F9DBDF99DDD9B9D9D99D99DBD9D9F9F9F9DF999D9D9F9D9F99D9DBDBDF9DBDDBDBDFDBDDBDDBDDD9DD99D9BD9DBD9D9F99D9D99D999D99DDB9F9BDD99DB9D9DB9F9D9DBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D9F9F999D99DBD9D9D99DF99BD9D99D999D9F9D9F9D99D9DB9D9F9F9F9DBD9DFDBDDBDD9FFDFFFDFDF9DDBD9F9DB9FDB9DF9DDBD9D9BD99F9999D999F99D999D99D999F99D9DBDD9DBDBDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDDBD9D9FDF9FD9DBDBD9FD99DD9D999F99DB9D9F9D9D9D99D9D9D9D9D9DBD9DBD9DDBDDBDBD9F9DFDBDF9FDF9DF9DD99DD99DB9D9D9DD9D99D9DB9D999D99DBD9D9D9D99D9999D99F9D9DDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D9DBD999D99F9D9DBD99FDB9D9BD9D999D99D9DBD9F9F9F99F99DD9F9D9F9D9F9FDDBDFDFFD009FDFDF9FDF9DF9DFDBDF9DD9F9F9BD9F9D999D9BD9D9D99D99F99B9D9BD9F99F9D9F99FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9DB9DBDDBD9D9D9D9D9D999D9BD99D9D999D9BD9DBD9D9D9DD9DB9BD9F9DD9F9DF9F9DF9FDFF909BDF9FDF9DFD9DBD9D99DB9D9D9DD99D99F9D99D99F99BD9F999D9DB9D9999F9DBD9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9DD9D99D9FD9F9BD9F9DD99D99D9B9F99DB9D99D9DBD99F9BD9D9D9D9DBDF9FD9FDFD9FDBDF09909FFFDBDFDBDFD9F9FDF9DF9DBD9BD99D9999D99D999D999D9D9D99D99D9D9D9D9FD9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9DB9DBDBD9999D9D9D9DB9DB9D999D99D99D9DF9F9D9F9D9D9D9D9F9DBD99DD9F9DBDFDFDFD90D999DDFDF9FD9DBDD9D99D99D9D99D9DB9D9F99D999D999D9999B9999D9B99F9BD999FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99D9D9D99DFD9D9BD999D9D9BD9D9D999D9B99D9F9D9DBD9DBDBD9F9DDFDF9FDDFDBDBFDFF0B00D09B9FFD9FF9DBDBDF9FD9F99FD9D9D99D9D9B9DB9D9B9D9F9D99D99D9D99DDBDF9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9DDBD9D9F9999F9D99F9DB99D99999F99D9D9D9D99DBD9D9F9D9D9D9DB9DBDF9FF9FDFDFFDFC9D9099009DFFD9DF9DD99D99D9DF99F9D99D9999D999D99D9999999DB999999D99D99DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9BDDB9D9D9D99DBD99D9D9D9D9D9BD9DB999DB9DDBD9D9BD9DBD9F9FDDBD9DFD9FDF9FDFFF9B9099C900BD9FDF9DF9FDF9FD9D99D9D9BD9B9D99999999999D9999999DBD9D9BD99F9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D999DB99F99D999D9D99DB99B99D9999D9D99D999D9F9D9DBD9F9D9DBDDFBDBFDBDFDFFFD00E909909009F9FDF9D9D99D99DB9D99D9D99D99D999D9999D9999D99D99999BD99FD9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9D9F9D9D9D9BD9D999BD9DD9D9999D9D9DB9D9BDDB9D9D9D9D9D9FDBDDBDDFDDFDFFDFDFFB990DA9D0900099B9FF9DFD9DBD9DBD9B999999999D999D99999999999999D9D9D99D99FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9BD999D9999D999DBD9999999D9D9BD9B9D99D99D9D9F9F9F9F9D9FDBD9F9FBFDF9FFDFDF00CB990990DB00009D9F999F9D9D9D99D999D99D9999999999999999999D99999BD9B9DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D9D9F99DBD9DBD99999B9999999BD99D99D9BD99DF9D9D9D9D9FD9FDDFDFDDDBDFDFFFFF9DB000990F00009E90BD9FD9D99F999D99D9999999999999999999999D9999BD9D99DDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD99BD99F9D999999999D9DBD999D99D999DB9D99F999D9F9F9F9D9FD9F9F9FFFFFDFFDFDFDA909090999000090909999DBD999D9999999999999999999999999999999D9999F999FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D99D9999999D999D9999999B99D99D9F99D99D99DDBD9D9DDF9F9DFDFDFDBDFDFDFFFFF09000909090099009000999999D999999999999999999990990999999999999D9D9D99FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF999999D999DB999999D99999D999F9999D999F9D9B9DBDBDB9DF9F9FDBDFDFFDFFFDFFFF909A9090F09D000000900099999999999099999909999999909909999999D99999999DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9D9F9999D99999D99999D99999999D999F9D999F9D9D9D9DDBDDDFDBDF9FFDFFDFFFFF99000000C990B0900900000009999999999909999999099090909999999999999BD9F9BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF999999D99999D999999999D99D9D999D999999D9D9DBDBDDBDDBF9FDFDFFDFFDFFFFD9D9A0009DB0090000000000000000000099999909999999090909090909999999D99999DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9999D99F99D999999999999999999999999D9DBD9BD9D9F9D9FDDF9F9FDFFDFFFF9B9BF9C900E909099009A900000000000000000099909090990000909999999999999999D9BFFFFFFFFFFFFBFFFBFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D9B99999999999999999999999999999999999D9DBDD9FFD9F9DFDFDFDFFFFF99DC9D0900B090000900000000000000000000000000909090000900909099909999999D999DFFFFFFFFBFFBDF9FFDFF9FBF9FFBFFFFFFFFFFFFFFFFFFFFFFFD9999D99999999999999909999999999099999D99D9D9BD9DBFDFF9FFDFFFF999C9A9FF9909090A0990090900000000000000000000000000000000090909909999999999999FFFFFFFBFFFBFFBBFBFFBFBF9FB9BDBDBF9FFBFFFFFFFFFFFFFFF99999999999D9999909999990990999999D99BDF9F9FDFDBDDDBDFFDFFF909F09FDFF990B00B09000990A000000000000000000000000000000009009990999909099999999FFFFFFDF9FBF9BDFFFBBDBDBFBDBF9BBDBFBDFDBBDBFFFFFFFFFFD99D999999999909999999999090909999999D99D9D9D9BDDBFDFDFFB909909DF9BDFF9D09000B0900B90900000000000000000000000000000000900909090999909999999DFFFFBBFFBDFFFBDBFDFBFBDBFBDBFDBDBDBBBFDBFF9BFBFFFFFFF9999999D99909999990990909090990999999D9F9F9FDDBDDFFFF090900090B9C9B9D909000909E9BC09000000000000000000000000000000000009099909909990990999FFFFB9DB9FFBF9FBFDBBDBDBDBDBF9BFBFBDFDBFF9FFF9FBDBFFFF999999999999999909990999999999999999DBD9D9D99FDBF999099000900B0DB0D0B09090D000900990000000000000000000000000000000000000090099099099909909FFF99FBFFFBF9FBF9FBDBFBFBFBDBBDF9F9FBBBF9FBF9BF9F9FFFF999999999999999099900990999999999D999D9F9DBDFDBD9900900090009099090BD99C09BDA90900B0000000000000000000000000000000000000000900909909999990BFFA9B9BDBDBFBDBFB9F9F9FDBDBDFBFBFFBDFDBFBDBFDBFBF9BFF990999999099999990999999999909999B9D9F9DBD9999990000090909090900909D909B000990000090900000000000000000000000000000000000000090090990990999DF999BDBBFBDBFBDBFFBFBF9BFBFB9F9F9FBFBF9F9F9BBDBDBF9DBF9990999999999099909999909099099D99999999900900009000090000900B90F0AD0909990CF909090000000000000000000000000000000000000000000900909999909FB09B0B9F9FBF9FBF9BDBDBFF9FFFFBFFBFDBDBFBFBFDBBF9F9BB9099999999990999990909099999999999999990900000000000000090F900900099D990D9000B9A0000900000000000000000000000000000000000000000000909909999F0090999FDBFBDBDBDBDFBFBF9FF9FBDF9FDBFBFDBDBDB9F9BF9F990909999990999090999999990999999999900000000000000000009099009099B90909900009090BC900000000000000000000000000000000000000000000000000909099F909B9B9BBDBBFBFBFBBDBDBFBFF9FFBFFBFBDBFFFBFBF9FB9B9A990990990999999990090999999990990900000000090909009000009AD090B000F0F909DB09090C9009090000000000000000000000000000000000000000000000099990DB090090BDBBDBDBDBDFBFBFFFDBFBF9F9FF9FF9FB9F9FB9BDBD99A9909909990909099990990999099990900000000000000000000009099A90099099CB90B00000B90B000000000000000000000000000000000000000000000000000000909000909B9BBDBFBFBFBBDFDBF9BF9F9FBFBDFBFFBDFBF9FFDB9BB99909099909999999090990999090000000000000000009000000000000090990AD0F9D09090909909009009000000000000000000000000000000000000000000000000900900909099BDBDBDFBDBDFBBF9FF9FBFBDF9FBFDBDFBF9FB9B9F99DB0909090990909909009099909000000000000000000000090900000909009A099B9FBD09A000009090090000000000000000000000000000000000000000000000000000000000090BDBBFBF9FBFFBFDBF9BBB9BDBBFBDBFFFBDBF9DBFB99A909000909099990909090909000000000000000000000000000000000090A909900CF0DBD9900900B009A090090000000000000000000000000000000000000000000000090000000090B9F9FBF9F9FB9BDBF9DBF9BDB9FBDB9FBF9FBBD9FB99B9B99090990909009000090000000000000000000000000000090090000900DADB999FB909E90A09090090000000000000000000000000000000000000000000000000000000000009099F9FBDBFBFBDBFB9BBB99BDBDBBDBFFFDFBF9FBF9DB090900009009090900090000000000000000000000000000090900000090090B90D00B9D09999909A90090009090000000000000000000000000000000000000000000000000000000009BBFBDBFBDBDBDB9F9D99FB9B9BDBBDB9BBBDB9F9BB999B00909009909000090000000000000000000000000000000000000090A90990F909DCB9090C90090B0009A0000000000000000000000000000000000000000000000000000000000090DBDBFBDBBDBB9F99BB9B999BDB9F9BFFFDFBDBBF9DB090900009000000090000000000000000000000000000000000000090009090A90F90B90909B9090B009000909009000000000000000000000000000000000000000000000000000000099BF9BFBDBF9F9BBD99B9BDB9B9B9F99B9BF9F99FBBDB090900000900090000000000000000000000000000000000000000009009A9909090DFBA9009A90909009000000000000000000000000000000000000000000000000000000000000900BF9FBDBF99B9BD9B9BDBDB9F9F9F9BF9FB9FBFB9F9B9909000000000000000000000000000000000000000000000000090000A909C0B0F9CB9D900B009C00A9000090B0000000000000000000000000000000000000000000000000000000009B9FBDBB9BBBDB9B9BDBDBDF9BF9F9F99B9F99B9FF9B00900000000000000000000000000000000000000000000000090009090909A9099090DF09009A9B90909090000900000000000000000000000000000000000000000000000000000000BDB9FBF9F9D9B9B9F9BDBDBBFDBF9F9FBDB9FF9F9BF9B9000000000000000000000000000000000000000000000000000000000009099BC9F9AD90090900990900A90090000000000000000000000000000000000000000000000000000000000BFBDB9B9BB99D9F9F9BFFDFBFDBFBF9F9DB9BF9F9BF9090000000000000000000000000000000000000000000000000000009090A9AD09AD99FB0909A9B00A00990B000900000000000000000000000000000000000000000000000000000009BDBBDBDB9DB9BB9FFFFDBFFFFFFDFFFBFBF9F9BB9F9B90000000000000000000000000000000000000000000000090000090000BD090BD9FE9F9C90B9A90999F000909000000000000000000000000000000000000000000000000000000009BDBBDB9B99B9FBDFFBDFBFFBDBFFBFF9FDF9F9BD9FBDB00000000000000000000000000000000000000000900000000000000090900B9DAF990FB90B09090F0B0D0B00090000000000000000000000000000000000000000000000000000000ADBDBB9B9DB9F9FF9FFBFFFFFFFFFFFFFFBFFFBDBB9B9F9000000000000000000000000000000000000000000000900000000090009900F9DFD9D0B90D00A90909B00090000000000000000000000000000000000000000000000000000000009B9BDBDB9B9FBF9FFFFFFFFFFFFFFFFFFFFFBDFBD9BDB9B0000000000000000000000000000000000000000009000000090090A9000BD9DF9FA99909DA9B9E909A909000900000000000000000000000000000000000000000000000000000009BFB9B999BDBDFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDB9F900000000000000000000000000000000000000009000009090000AC900B000B0FDFD9C099A9B00990A909090000000000000000000000000000000000000000000009000000000009F9BDB09BBDBFBFFBFFFFFFFFFFFFFFFFFFFFFFBDBDBDB9000000000000000000000000000000000000009000090900C0000909909090999FFF909BB0900090090090B009000000000000000000000000000000000000000000B0F00000000000BF9BD9BD9BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFBDB9000000000000000000000000000000000000000090000F9F0090090A09CBDBC9DFDBD99BB99A90B0000A9C90090000000000000000000000000000000000000000009AF000000000B9BDB9B9BFDBDFBFDBFFFFFBFFFFFFFFFFFFFFFFFFFF9F9B0000000000000000000000000000000000000000009000F0900000990B0909909FF9E9B900A90B900090099000000000000000000000000000000000000000000000090F000000009F9B9B9FBDBFFBFFFFFFFFFFFFBFFBFBFFFFFBFFFFFDFBFD00000000900000000000000000000000000000000900909000B09000D09ADF0F9F9E9900BD9DB00900009A009000000000000000000000000000000000000000000AD0FB90000000F9BBDBDBDBFFFFFFBFFBFFBFF9FF9FFFFFFBFFDFBFFBF9FB9000000000000000000000000000000000000909009B000009090009A9ADB9F9C9E9909F0B0B0900B009009000000000000000000000000000000000000000000909A090CF000009BBD9B9FBFFFBFFBDFBDFBFDBFFBFFBF9FBDFFFBFFFFFFF9FF00000000000000000000000000000000000000A900009090000090BD9DBDE9F999BC9D9F9F9FAB009CA90A90000000000000000000000000000000000000000000090FB9BB000009BB9DB9FBFFFBFFBFFBFDBFF9FF9FFFFFFFBFFFFBFFFFFFBDF0000009B0000000000000000000000000000000090000090900090F09DBDFFCBC9BDAF9B09090B0A990900090000000000000000000000000000000000000009AF9A90FF000009F99FBFFFFDBFDFBFBDFFBFDBFFBFF9F9FBFFDBFFDFBF9F9FFB90000000000000000000000000000000900009090090900000090F9B0BDBD9F99DF999D0909BA9E990000900000000000000000000000000000000000000009E09009EF900009BB9FBDBDBFBFFFBDFDBF9FFBFDBDB9FBFFFDBFFDFBFFFFFFFBDF90000D000000000000000000000000000090000000000009090F9909D0FBFF9CB9CBDB99A90DA9A09BB0A9000000000000000000000000000000000000000B9B090FFF00000099F9BFBFFFFFFBFFBFFFFB9DB99B9F9DBDBFFFFBFFBDBFBF9FFBF0000B000000000000000000000000000009000909090900B090B0FCBDFDDBF9E99ABC9D00B999900909000000000000000000000000000000000000000000000A9FF9000000F9BFDBDBF9FBFDBDFBF9BDB99B9DB9B99BDBDBFFFFFFFFFFF9BDBF00090000000000000000000000000000009000000A009009A9099FF9FBD0F9909B99B09B090A099A0009000000000000000000000000000000000000000000000FB0000009B9F9BFBFFFFF9FFBFDBF9B9FDDFBDFDFFDBDBFDBDBDBDBDBFFFFFDA000000000000000000000000000000000000900909009909090F9FFD9E9FF990BDA9DB0B9090B00090000000000000000000000000000000000000000000009B90000000BDFBFF9FBDBF9FFBF9BD99FDFBFBDBDBBDBFFDFBFFFBFBFFFF9F9FB90000000000000000000000000000090090A9009C090B9A90BCB9DBFFFDFD00ADA9BB0990B0090A900090000000000000000000000000000000000000000000000000000DBB9F9BFDFBFBFFBDBDB9FFFBDBDFFBFFDBF99B9DBDBDBDBF9FFFFFFF909000000000000000000000000000000900090B9F99C9BC999CB990FBFFBD9FB0DAB009B9B09009B000000900000000000000000000000000000000000000900000000BDFBFF9FBDFFDBF9F9BDB99DBFFBFFDBFFFFF9F99B9FB9BDBFBFBFBFDF000000000000000000000000000000000900A90B00B909B0A9BDADBDF9FFFDF9BB90DBBFB9009B9A90000000000000000000000000000000000000000000000000000BDB9F9BFBFBF9BF9F9F999FB9FFBFFFBFFF9FFFFBD099909BFFFDFDFFBF00000000000000000000000000000090000BD0909990BD0B9C99DFDBDFF9FBD9A9B0B9FB9B0B00090000000000000000000000000000000000000000000000000000BDBFFFFFFDFFDBF9F9B999BF9FFBDFFB9FBFFFFBFF900099BFFDBFBFBDFF9000000000000000000000009000000009090B09A9CBD0B909B0F9FFDF9F090BBB0B0B9B0B9090B09A000000900000000000000000000000000000000000000000009BDBF9FBFBFBFB99B9900BDBF9F9B9B9999B99F9BB900009F9FBFFFFFFBFB0000000000000000000000000090000009E90BD0B90B990B0D99F9FFBFFD990BEB09099A9B9A9CB0900090000000000000000000000000000000000000009000000FBF9FFBDBDFDBDB9900090BFB9B9999D9B999999999999F9BBFDBFBDBFDFFD0000000000000000000000000000909090BD0A90F90DA90B090F909DBDB09BDB90A9A9B009DA99A9A900000000000000000000000000000000000000000000000BF9FFFBFFFBFBFB990000099999999BD9B9D9F9B9D9DBFB9BDBDBBDFFFFBFFBB000000000000000000000009000000009D0B9B09A9B09A99FD9EBFBCB0900BB0A909B0DB9A99A9B0000009000000000000000000000000000000000000F90000F9FFB9F9FBDBF99F9B9D999999999F9FBFDBF9F9FBDFBFDBDBDBFDBF9F9FFFD000000000000000000000090000090A9F9AF90C9A9090B9B0F9F9D0099D09B9A9BFA9A9B0F9F0900B000000000000000000000000000000000000000000000000BFBFFFFBDBBDBFB9BDBBDB9FDBFFFFBDB9B9FBFFDFBFDFBF9B9B9B9FBBFFFBFD00000000000000000000000009000900FD0DB9B09B0B0BC9FFCBAB9A90B00B00A9B09A9BB909F9A9B09000900000000000000000000000000000000000B00009F9F9FBDBBD9B99FBDBDBFDFBFDBF9B9B09999B9FBFFBFBDBF9BDB9F9FDBBDFFB0000000000000000000000000A9000909BD909DBCBD9F9B00F9D90090B9F09ABDB090990009AB09000009000000000000000000000000000000000000090000BFBFFBDBDBBF9FB9B9B9F9FBFFBF9F0999F9BD9F9F9FFFDBDB99B9F9F9BDFBFF9A0000000000000000000000090000009F09090F0BDAF9F099FF9E9F9EDE90ABDAC900000090B909A909A0000000000000000000000000000000000000000009F9FDBDBFB9F99B9DBDBFBFBFDBDB9B9BDB99F9B9BFFF9FBDB99F9F9F9BFF9FFFBD00000000000000000009009000909B90999CBD9F9B9B00B09DBDF9A9B99B09A9B9A09A000900B00A0009090000000000000000000000000000000000F0000ABFBFBB9F9F9FBDB9B9F99FDBFBF9B999B9FF9F9F999BF9FB9B9BDBFBFDF9FBFFF9000000000000000000009000900000C9E9A90F9F0909090DABC9FDFF9A9000000F9A090000000B090900A000000000000000000000000000000000009B9B9D9FBDFDB9B9F9FBDB99BFFBF9F9B99999F9FFFFF9F9F9FF9F99F9FDBDBFBF9F9FF9000000000000000000000000090009090909900FD00A9A9FDDBDBFDFDFF0000099A9A00090000000A009900000000000000000000000000000000009F9F9FBFBDBB9BDBF9F9FBF9B99BDBFBF9F9FBFFFFFFFFFFFFFDBB99B9FFBFFFDFFFFFFFF00000000000000000000009000009A90909A09F90B09C9F9FBFF9CBFFFD000999AB00000000009009090009000000000000000000000000000000F0FFBFFBDF9FBDBF9F9FFBDF9F99BDBF9F9F9FFDFFFFFFFFFFFFFFBDB9DFDBFDFFBF9FF9FBDA000000000000000000090000000000BDA9090909090B9BCBD9999F9FFF000000090000900000000000090000000000000000000000000000000099FBDFBDBBFBDBF9BFBF9FFBF9F9999FB9FFFFBFFFFFFFFFFFFFFF999BFBFFFFBDFFFFFFFFF900000000000000000000000900909909C90000B009BD0D9FB09A9DFFDF00009000000900000A0009A09A90090000000000000000000000000000B9FFBFFBDBDBF9FFDBDFFBDFFFFFB9B9DB9FFFFFFFFFFFFFFFFFFFB9F9FFFFFFFFFFFFFFFFFF0000000000000000000009000000E99F9E9090DB0D0F999D0BC90BDBBF009000F009A00009A900000900000000000000000000000000000000009FF9FB9DBFBF9FBDBFFBDFFBDFF9FF99B9FFFBDFFFFFFFFFFFFFF999FFFFFFFFFFFFFFFFFBFB000000000000000000000000090090FCB909FA90DB990D09F09BD9ADDF00009A9E00090000000090900000000000000000000000000000000000FFBFB9FBF9FDBFFBFFFFFBFFFBFFF9F999FBDFFFFFFFFFFFFFFFDBFFFFFFFFFFFFFFFFFFFFDF90000000000000000009000900090BDBDE9EBD9DBC0099B9F90F9090BF00000909A090000A000000A0900900000000000000000000000000000BDBDBDB9BDBFBF9FFDBFDFF9FFFFFFFFBF99FBFFBFFFFFFFFFFFBF9FFFFFFFFFFFFFFFFFFFFFFB00000000000000000000900000C9090B9F9D909DF9B009D0BD0F9009909A900009909000900000090090000000000000000000000000000000FBFFFBDBDBDBDBFBFFFFBFFFFFFFFFFFFFBF9F9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFF0000000000000000000000000B00B09090B0B90BDFDFF0990BDF0B9C900000B09000009A000000909000000900000000000000000000000009BDFB9FB9BBFBFFFDFFFFFFFFFFFFFFFFFFFFF9FBDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB00000000000000000090900909900B0D909009CFFDF90B09090909B90000B00A09009A9A0000900A09090000000000000000000000000000BDFBDB9B9FDBDBDBFFBFFFFFFFFFFFFFFFFFDBFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF90000000000000000000009A9009909B09A9F9BB9BB9090909AB0F090000090900B000090090A909000A0000000000000000000000000000FBFFB9FDB9BFFFFFFFFFFBFFBFFFFFFFFFFBFFBDBFDFFFFFFFFFFFFFFFFFFBDFFFFFFFFFFFFFFFDBF00000000000000009000909A0B00B09DB90909C900A9CB9B090F9B0B000A000900B0B009A09C90090909090000000000000000000000009FF9F9BB99B9BFFBFBFDFFDFFFFFFFFFFFDFFF9FFFBFFFFFFFFFFFFFFFFBFFFFBFFFFFFFFFFFFFFBDB00000000000009000090A909909E9F0DCBDADBBF99CB90C09A9CBD900B0900009A0900B00909A90A000000000000000000000000000000FB9B9BDBB90FFDBFFFFBFBFFFFFFBFDBFFBFFBFFBDFFBFFFFFFFFFFFFFFFFBFFFFFFFFFFFFFFFFF9BD0000000000000000000090F0A9F9909B9FD9F0D90FFDADB9A9FB90B0B0B00B09A09A0B0A9CA90090090B09000000000000000000000009FBDB9F9F90F9FBFDF9FFFFFBFFFFDFBFF9FFBDF9FFF9FFFFFFFFFFFFBF9FBDFFBFBFFFFFFFFFFFF99B0000000000000000900B0909DB9A90B0DFBFFDBDDF9A9B90DFF9CF00009B009090A9A090B0909A0000000000000000000000000000000FF9B90BB099BFFFFBFFBF9FFFDBFBBFFDBFB9FBFF9FBFFFFFFFFFBFDBDBF9FB99F9FBFFFFFFFFFFFB0F900000000000000000900B0A9009C909B0D9DBDFBFDF90DB90FDB9BB0A0B90000B009A00AB090900B09090000000000000000000000009B9B9909B009FBFFFBFFFFBDFFFFDFB9BDBDBDB9BFDFBDFFFFFFFFFBFB9B999B99BDF9FFBFFFFFFFD9B0000000000000000900990990BDB9A9C9B90DFFFDBDB090D99DB0C90000B00909CBA90BFB90B009A00A00000000000000000000000000F9090909009B9FDBDFF9FBFFBF9BB99B9A9B9B9F9BBFDBFFFFFFFDBD9BD9B9BDB9F9BFBFDFFFFFFFB9F9000000000000090009A090AD0B0D90B000BFF9DBDF9D09FD9A9D9A0A9B0BF0BABADBA9A90A9A9A90909000000000000000000000000BB0900090909FFFBFFBFFBFDBF9BD99F999999909BDF9BFFFFFFFBBFBB99B9BD9BDBBFFDFFFFFFFFF90B000000000000000009009DAD9B0909090B9D99DA9DB09BDF9099A900000B09B0F9FBCBB9A90B9A9A0A9A900000000000000000000000990909000000B9FFBFF9FDFBDBF9B9B9A9F9B099B9B9FFDFFFFFFDF999B90990B9A9D9BBFBFFFBFFF9F9B0000000000000090090B09B00B09A9090099CBDFF090D9FF9009A900A9AB0B0BB0FB9A9A00B00B90900A000000000000000000000000990D0909009DB9FDFBFBFBFB9B09B999B9999B9090B9BBBFFFFFBFF0909FB090090B99999B9FFFFF0B900000000000009000BC909E9F90F090B0FA9DBDBDBD090D9DAD0DDFA09A90B0B90BFFA9A9B000B00B00B9000000000000000000000009A9BB9A90000BFFBFB9F9B9999099090909ADB909B99F9FDFFFFFDB9B90BD09000009000DB9F9FFFF9909000000000000000009A90909EB90B09C9909D9EFDB0099FF99BBF909BA9B0B00AFFF900F0A0000B090000000000000000000000000909F9F9090009F9BDF9F9B990000000000009F0009099BDBBFBFFFB909090B00009000009A9DBFFFFF000000000000000000999090F009B9CBC9FBF09F9BD900090FF9CA090B0A0900A9AB0BBFEBBFF0B00B0BA9A9000000000000000000000090B9B09000009BFFBFBFBDBDB000000000000B990090B9BDFFFFBDBDB90900000000000DB9BFFFFFFB00000000000000009000A9A90B9009B9FBC9DBD0D0BDB90B9ADB999E99B000B00A9A90DFFCBFFFCFA9ADBCDA0000000000000000000000909B990900000DBDBDBF9BFB9F900000000000009009CB9B9FBFFFFBD09090900000A9B90999FBFFFF90000000000009000B99C909D0B90D0909FBC99999D90090DFFDBC99A9000A00A9A9ABBB9BFFFFBB0FFFFBB090000000000000000000000990000000000BBF9BFDFF9F9B9090900000009000999B9FBFDF9FBDBB9009909A9B9900B9FBDFFFFD000000000000000090009A9E99C0D0B09A90BD9DAD09D9FFFFDFDBDA900B090BB9A9B9009A9BBBBDFFFF0FD000000000000000000000009000900000009BD9ABDBB9B9B909B00090090000A9B0B9F9F9FBFBDB990B000099000099DBF9FBFFFB0000000000009009009B09090F9DB900090BD90B909FFFFDBFBFBC9D0900FBA9E0F000BA000909BABB9FBFA000C000000000000000000090900000000009BBD9B9090909B909B90900900990999B9FBFBDBDBBDB999990009099A9B90FBDFFF90000000000000000A900909090F909909AD9099D90FFDF9A9009DBDB09A9A9E9BBA9A00B00A0009900A90B909A9A9000000000000000000BA9000000000099B9900000000090909090099099B0DB999F9BFBDB9FBC9A99000900909B990B9FFF900000000000000900B09A9F090F9009C9FFFF09CBDFBDF9FFF0909D900FBFBFADBAC9AD0B00B0A00000000000000000000000000000000990000000009B9B000090090009000000009009A909B9FBF9F9BDB9F99B990090000909090BFDFFB900000000000000B009C90D909BD90F9FDFDF9099DF9FDF9FDBD0F909090BB09A9B0B9AB0B0000000A0090900000900000000000000000000000000000009000099B000090000000000009090DBDB9F9BDBDBDBBDA90909000900009ADFDBFF9F0000000000000900909B0B09F0FFFDFFBBBFD90FF9FBF9BDBC9F9CB099BCBB000A90A9000000000009A00A00090A09000000000000000000000000000000900B0B0F90000000000000000909A9B9F9F9B9B9BD9BD99A90A90A009D099BFF9FB900000000090000900A90D0B009099A999C0DBE9D9FC99C90990F9F9F09BB000000000B00B0000009009A0900009000000000000000000000000000000000000999909B0000000009009A90B99BDB9B9F9F9B9B9BBFBDBD9F99FDA9FBE9BDBDB0900000000000000B009A909B09909DBCB990D90FD9B00BDB0BD9E9F9F90BB000000000000000000A0000F0CB0000B00000000000000000000000000000000090B0B9B0900909909D09BDB909F99BDBDB9B909DAD990BDBF9FF9BDB9099FB0B900000000000000B0090090B099A0F909909A99099BD09D909090BDBD090000000000000000000000000FFFFF0A009000000000000000000000000000000000009099099000090009A9B09909B9B0F9B9B9DA99B99BFBF9F9FB9BDB90B9F099900000000000000900D09B09C9AC9990B9C90D0000BD0BC9E9AD09990090900000000B00000000000009AFFFFFFDA000000000000000000000000000000000000009A9B0B9000009A999090B90909B9BDBDA99900909999B9F90909909909B900900000000000000090A000B9E9BE9E900B0B09B0909D99F9FDBD000B9AD0000000000A000000000000ADFFFFFFFDF0000000000000000000000000000000000000090990900000090B99B9000090090B9B9BB9000B00B9F9BFB9000B09090B9B0000000000000009A99090099099F9E99090909909F90D9F9ADBD900DBD9000000BA9000000000000A9FFFFFFFFFB000000000000000000000000000000000000099090000009000909A909900009099A9D9009909909D0BD99000B99A90B090000000000000090000090DB0F0BD9F9E0A9DA9A0DBDA9909C9FF9A9DBC9B00000B0000000000000B09ABBBFFDEBF0000000000000000000000000000000000000000000000090090009909B00009009F99A90900CB090B9F9B09009909090900900000000000000909B00B0909D0A9FDBD0BC909B009D09DBFDBD09A9990000000B0000000000000A000900BFBD900090000000000000000000000000000000000000000000090099000909F90000909B990090B99B009B99090B090B900009000000000000000B00909099A9909900BDA9F9F0B09B9B90BDFF90B09A90900000A0B00000000000B000000000BA00000F000000000000000000000000000000000000000000909009900B909900090B0DA909009A9C900FB900909A9009090000000000000000009A0B0B090DA90990FFDB0F9BDBC900D90FF9F9DA9DAD9000000000000000000B00000AC09A09009FF000D00000000000000000000000000000000000000B090B9000990B9B0090099B9900099DBB9099900B09090900000090000000000000000909090B0990DA0BFFB0FF09ADBDBDF0990FDFFFDA99000000000A00090000000000BCB0A09009EB09A9A0000000000000000000000000000000000000909099000B0909900000909090009A9A99090B09099B090000009000000000000000090900B0F099A9099FFFFDF99DF9FDFDBD90F9E9FFFF90B0000A0009A00A00000000000B0909A000BDB0000000000000000000000000000000000000000009000000909A9000000009A9000099999090F9009B09000000000000000000000000000A90999CBEDA99E9FF9A9F0BDF9F0BD0B09D90F9FFD9000009A0000B00B00000000009A000090000F09E90000000000000000000000000000000000090009090900909909000009090B00000000BC90B9009B9900000000000000000000000009000E0B99FBDADFEBD099090B90D9DB990B0B0BCBFBCB000000000000B000000000B00000000009B09E900B000000000000000000000000000000000009000000090B000B00009000990009099B9B999009B00000090000000000000000090000090990FCBDAF9B9FA9FA0B09099FF9F99C9909B9C9BD00000B00000B00000000A0000000000000ADA0B000000000000000000000000000000000000000009090909909900009009090900009A9090000009909009000000000000000000009009A000BDBDBF90FFFDFF999009FDF0B90A99A0F0DB9CB0000B0000A00000000A00B0000000000009A9BCBD00000000000000000000000000000000000009000000900000900009000000000009090B09000000000009000000000000000000000009B90FFB090B09FBFFF0A9B099DBDA990BD99F9C9B000000A9000000000000B0000000000A0009AC9BCBE90000000000000000000000000000000000000090000B900000000090000090090000090090000090900000000000000000000000900000B9FDE909FF9DFFFFD00FC9FDBDA99EBE9FFBCF900B0090A0900000000000000000000900009B0FBDB0000000000000000000000000000000000000000090900009000000000000000009090009000000000000090000000000000000090090900FFB90BEFFFFFFDFA9FBDBDBC9F9A9D9FFBCBDA0000A0A9A00000000000000000000B0090000090B000000000000000000000000000000000000000090000909000090A90000909B0000009000000000000090000090000000000900900A00009BFF90B99FFFFFBFDFFDF9D99F909DB0B99F9FD0000000090000000000000000A0A00000000000000000000000000000000000000000000000000000000900009090090D09900A9099009000900000000000000000A900000000000A00090909A09FCB0FA9FF9F9BFDFB9D0D90D9E9FD9FA9CBB00A9A90A0000000000000000090900B09000A00000000000000000000000000000000000000000000000090000B090090B09F9009AC990B090009000000000000000000000000900909000090900BFDFDFFFFFF099F0DB990FDFDBF9F009B090000000A9A00000000000000000A0A900A0B009000000000000000000000000000000000000000000000090000000009A999A90990990B09000900000000000000009000090000000000A900A00009FFFFFFFFDBF0FBDBC9DBDB9AD9FA9B0DBCB0000B0009000000A000000000A9090A90900900B0A900000000000000000000000000000000000000000000009909009900999A009A909009000000000000000000000000000000090090099C9000FFFBFFFFBF09ADF09DB999ADB0B9D09A9A900000B000000000000A9A0000000000000B0A00090C9000000000000000000000000000000000000000000000009090009B0B0D90090090900B00000000000000900000000000000000009A0B00000BFDBFFBCD99DBDFD909E90BCBDBA90909000A0000000000000000B0B000000A090A0090900A9A0A9000000000000000000000000000000000000000000000000990099090B009900A0909090000000000000000000000090090009009C0F9009F0FCB9FB90A99FFBDB09BD999CDB0900F00000B000A00000A09A9A9000000A00009000000B00909000000000000000000000000000000000000000000000000900B0000B09090A9090000000000000000000000000000000000900090B90B000A9BB9ED09BD9E9FD9DBD09E9CB90F09B09000B00A090000009A9A00000000090A0000000000900000000000000000000000000000000000000000000000000000900909909000909000090000000000000000000000000909000009A900AD000D9000B90F0BF9DFF90909F9FF90B90FCBF0000B09A0000000A00000000000000900090A0900000000000000000000000000000000000000000000000000000090090000090B0900000900000000000000000000000000000A000B00009090B00BEFF99099D00FBDFF9A909FDBB09A99BFD000000A9A0000000B0000A000000A00A90A090000000000000000000000000000000000000000000000000000000000009009A900009000000000000000000000000000000000009000090BCB009009FFFE9F0AFBD0FBD9C90F09FD0BC9EB09F00A000900000000000A09090000090090000000090B00000000000000000000000000000000000000000000000000000000900009000090000000000000000000000000000900900090900C9009A09FFFFFF0BDFFFB9DB09DF9909ABDBF90FF9000000A0000000B0BA900A000000000000909090A0000000000000000000000000000900000000000000000000000000000000900000000000000000000000000009000000000009A000A090B0090CFFFFFFF9BFFB09A09DBBD0F9FDFFF0FFDB000000000A9A0000000B9000000000B0B000A9A900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090909A90DA09BFFFFFFB0A9FFFF990B00BDF0BFBDBF9BFD0000000B00000B0B09A0A00A0000A00009A90000090000000000000000000900090000000000000000000000000000000000000000000000000000000000000000000900000000900090000FB0D0CFFFFFFF99C909FBA909BDDFB90DDF09F09F0000A9A00000A000A009000009A90000A0000B0F00009000000000000000B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000A90BD00FBFFFFFFFF9AFB09BF9B0FBCBFB90FBE9BFADF9000009A9A00B900B000000000A000009090A90C9000000000000000000900009000000000000000000000000000000000000000000000000000000000000000000000000000000009090C90BDBDFFFFFFFFFDF90BE9F9FB9B9B09A9F9FDEDBD9000A9A0009A0AB00000A00000090B000A0900BFAF0900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0B09EFF9FFFFFFFFFFB099FFFFFC9009ADA990B9B0B900000000000900000A090A9000A000A09A0B0FDFBC0000000000000000000900A00000000000000000000000000000000000009000000000000000000000000000000000000000009A9090E9B09FFFFFFFFFFFF909FDFF99F9B0999009009D000009A9A9A00000000900000000900009A900A9EBD9A90000000000000000900009000000000000000000000000000000000000000000000000000000000000000000000000000900000F0B900BE9ADFFFFFFFFFFF09B9A9E9009E000D0DB0A99090A000A000A00000A0A000A000A00A0F00B9F9F0A000000000000000090000900000900000000090000000000000000000900000000000000000000000000000000000000000009C9B009000099FFFFFFFFFFFFFF09A9D9BD9F9B0B0B90D99A0A00BB09000090A09000A9090B009090B0B000A09090000000000000000A0000000000000000000000000000000000000000000A9000000000000000000000000000000000000000B0090009000A9BFFFFFFFFFFFFF0D9AD0F90F090D0909F0900000BA00000A090A0090A0A00B0A000000B099A09A9000000000000000090000000000000090000000000000000000000000000C0900000000000000000000000000000000000009F00900000090CFFFFFFFFFFFFBB0BDB90F90BDABB09F0F00000B09A000090A090B0A900B000000000900A099A000000000000000090000000000000000000000000000000000000000000090B000000000000000000000000000000000A00090090A009000009BFFFFFFFFFF90990BD9F90B909D0BC9F990009A9A90000A009AA09000B00B090000A0009BEA090000000000000000000B0000000000000000000000000000000000000000A0000000000000000000000000000000000090000B90090900090B00B9B9F9BF990A00AD0D00B0D0B0B0BB09A000A000000A900A090B009A09A000A0B009A9A099000000900900000000000000000000000000B00000000000000000000000900000000000000000000000000000000000000900900F000009000090009A9A90A00000999A900B0B0B099CB90900900000000009A00000A9A09BA0900B90009A0A000000000000000000000009000A0000000000900B0000000000000000000009000000000000000000000000000000000000000909900000B09000000009A9000000A09009B0B909A0B90900A00A00A000A0BA90000A9A90A0900A90A0B0A090900909000000000009000900A0900000000000A0000000000000000000000000000000000000000000000000000000000009009A90A00000090B0000000000000000090B9A0BDA90F9EB00B0000000000009A9A00900000B0B0A009A909AD0000000000900000000000000009000C00900000090000000000000000000000000000000000000000000000000000000009A90A900009000099E900000000A0000000900BD00B9BBDBDB9B0B9A0A900000000A9B00A0009A9ADA90B0A9E9A9A09000B0000A900000000000000000009000000000000900000000000000000000000000000000000000000000000000000000009009F09C009A09A000000009A0A0000A0090900A9FBF00B99CB0900A0A000A09AF0B9A90A000B0A9E9B0B0B09A0B00000B0000000000000000000000A00009000000000000000000000000000000000000000000000000000000000000009009090009A909090990000000000900A000909B0909A9B99ACB0900A0B0009A09AB0BB0B00A9A9AB09EB0A9AF0F09000909000009000000000090000090000000000000000000000000000000000000000000000000000000000000000090090A9ACADA9090B0009A0000000000A0A900F90AD090009A90B99090B009A9A9A0BBA9BB0B00B9A9AB9BA90909A9B0A0090A000009000000000000000000000900000000000000000000000000000000000000000000000000000000000000A0B009090909000D090F00900000000009000F0099090A09000B09A90BB09A9A9A9BF09A9A90BA9A9A9BE9BB0BBA9A0909A00009A90A0000000000000000000000000000000000000000000000000000000000000009000000000000000000A909090F0F09A9009A9090B9A900000000A0B0B09B009F000A0009A9C9A99BA9A09AB0BBE9A9A009A9A9A9BA9AF0F0F9A000000900000090000090000000000000000000000000000000000000000000000A000000000A00000000000000000900900C0909F9C9A009CB0B09000000000000000AC09A00F99000009A90DAB09AB0AF9FB0BB9A9A9AA9B0FA99BF9BB9A090A90900A90090000090000090000000000000000000000000000000C0B0000000F000000AD0000000000000000000000000B0B0B090B0D09A90090000000000009A00099B00999A000000F9E9A99BAB90B9BA9BB0A9A9AB9BEBB9BAB0BB00A9000000A090000000900A0000009000000000000000009FD000000009000000009A00C0F0000090000000D0090F00009009E9C90D090BDB0B009090B0000000000A0000B0A0A90DA000009B00B9BDA9F0BFAFB9A0B09A9AB9A9B9BA9B9B009A90A09090900009A909A90090000000900000009000000000A9A90A0000A90000A00C909A00000A000000A009A0009090A9009A9A9A9AD09CB0DA90A090A00000000000B0090900A90DB00000BB0A9A9ABB0B9BA9BA9A0FB9EB9ABA9B0B09ADADA90000A009A90000A000900A9009A0000000000000000009000000900900C000DEF00AC90C900000000009009F0B00A900B00909A9C9A90BDB09A990A909A00000009A0B9A0A90900090B0000DB9E909A9BFBA9B0900BB0BB9A9B9A9A9A09A9A9A009A90A0000009009A0A900000090B090000000000000000000000C0ADBDEFFFFFDFFEFF0E90C09CA900A900909A9C090090B090B90DB9009AD00B00000900000A00BCA09D000999E90000BA9A99BA9ABB09A0A00BBCBB9ABB9A9A000BA9A0B009000000000A0009A00909A0B00000000000009000000000900000B0D0A0B0F9B9BF99B9A90F90CB9C0090E9ADAD0B9A09A000DA9E9B0009090A9090009A0009000B0B09B0A009A09A000009090A09A909A00090B0BBB0B09AB0B09A9000090A00A0000090009A000900A090090A0900000000000000000009A0900E90C9C9AD0A000A009A90A0B00B9EA090909A90090099A9A990009D00A0900A00B0009A00A9A00A9A090A9C9B00900000A0B9BB9A9A90000A9B0BFBBB090A0B09A9A9A0900000000A0A90000A00A900A90A090A09A9000000A00090000090A0900B0BACB0BC9A90000009090B0009CB0F0B09A9AC9A00909E99A90B0090A90900099009A09A9A90A9A0090BC9B0090B00900A90A9A00A00A90A9B0B000A090A0A000000A0B0900B000000000900000B0009000900000090090000000000C909E9F0909B0BCB000000000A0A00F0B0B090B0DA9090B09A0B090A909990009000A90A00900B0A09AF0A9A90B09A000A000A0A90AB9B0B000000A9A0B09A0000A90000000B0B00A000000000A90A0000000000A09A0A9A00000009090AD0F9A9EB09A0F0B0A9A90B000000A9090B0B0009ABC9A9CA9A09AD9099AD9A0D009000A090009B0B0B0B0BA9A9A90A09900B0090A9090A90A0B0090000000B0A00900B00B000000000A90B00A90B00000000000000B09A0090090A00F00A0A900B00909C9A9B0F9A9CB0000A00000CA0A9009A9AD0B9DAB9A9A090ADA090AD9A9000A0090A9B00900B0B9E9A9ADA90900B0090A90A00A909BA900A0A09A0B0B0B0A0B0B0000009A0B0A000090000000000000A00000A090A0B0A9000009090A909BAF0A90BCA9A0DA90B0000900B0B0B0AB0A9A0B90BA90FAD0B0B99990B9AD9D09090BA9009B09B0F9AB9A90A9AAD0BD0B0000A90B00A9A900A00090A09000B0B0B0B0B0A90B0000090B0A00A00000000000000A0909A909009E9E9A9A0B09B0A0D0BDAF0B9E9BB0AB0B090A0000A90BC9A9ADB0A909EFBDABCB00A0CB00909A9090B090B9E90BCBBAB9A90A9A9B0B09B0B0B09A00009AB0A0009A0A900A0BCB0B9A9A009AA0A09A9AA0090090A0A00A009A90A90A0A00A009A9A90000B0AB0A99BAFAFDAF0B0B0B90009AA909AB9AB0BA9E9B0F9F0BFBDEBDBB0BDB99A90B099E90909E9A9B9E9B0BDAB0A9ADB0B09A0B0000A0000B090900B0A090A9009EBDB0A9A9A9A0909A00A90B0A00A00909000A00A90A9009A90B0A9A90A9A9CBF0B9A0009FFFFDB0BCB0ADABA90AA000A9CB09A9B0BB0BFBDFF9FAFCB0B0F0B000F9A999A9A9AB0B0BA0B9A90BCB0BA9E9A9F0A00000B000A9A0B0009AA90A0A0BEFFB9A9A9A9A0B00B00A9A9A9A0B0A0A0000B00A90A9000000B0B0A9000A909B009A0B0FFFFFEF0B0B0B09AB909B0B0AB0B0B0A90A90B0FBFFFFFBFB09990B909A9AABC9BDB9F9F9BB0BBA9AB0B0BB0BDA9B09A09000009A0B0B0B009A0090B9FF9EBF9A90A9A00B00B00B0BE9B0B0B09A9000B0A900000B0B0B0B00A909ABA09A0090BFFFFFFFF0B0A0BE90EBA0B0B90A9A0B9AB9AB0BBCFBDFFFF0F0B0F9EB0DB999BA9ABA9ABA90B90A9B0B0B00B0A9A9A000A00A09A9A9A0B0A9A00B0A9EFFEFF0A9AA90B0B00B0AB0B90BCB0A09A00A0B009A00A0B0B0B0B09A90A0B009A00B0BFFFFFFFFF0B099A9ABB90B9A00A9A0B90A9CADFADFFEFFFFFFFF0BBFB9DBBFBEBDB9A9B9A9BBA0A90A9A9A9A9A90BE090B00909ADB9A9A0A9A9A9A9BBFBFFFFFF009AB0B0FA0B00F0AB0B0A90A0B0900ABA9A99A0BCB0B0A000A9B0B0A90B0A9EFFFFFFFFF0B0A9A9A9EB0A9B09A090AB9BBFFFFFFFFFFFFFFFF909B9A9BFB9B9AFA9A9A9A9009000B0B0000000A90B0A0B0A0A9B0000909A0B0B0F0FCFFFFFFFBA90B0E90B00A9A90BCABCA9A90AA9009A9A0A9A9B0B0F9B0B0A0B0B0B0A9BB9FFFFFFFFFACB0A9ADA90B0A9AA9AA900BCBFFFFFFFDFFFFFFFFB009A909B9EBB9B9ADAB0BAB00000000B0090009A90B00009A9ABBB0B0A00B0FFFBFBFFFFFFFFEDBA9A9A000F9A9AC0A90A9A9EB90ABBE9A9B0A9ACBCB0A0F0B9BD0A9A9B0BCBFFFFFFFFFF9A09ADA9AB0B0A09A90A0BFBFFFFFFFFFBFFFFFFF000A9CB0BFB9EB0F9A90B09000000000000A000000A00B0BA9A9A90A0009ADFFFBCFFFFFFFFFFFB0000000E9A0AC0A900E90A9A90AF909A9A0A900B0B0B09A0BCB0B9B0BADBF9FFFFFFFFFFE9FA9A90E09A9E9AADA9FFFFFFFFFFFBFDFFFFFFF00090B0BDA9A99B00B0BA9A900000000000909000090B0BCBDA909FFBB0ADAFFFFFDFEFFFFFFFF00F00000000B0B00A0000B0A90A90AFABC09000E0E0F0E00F0B0B0A0B09AF9EFFFFFFFFFF9A00E0A090ACBFFF9AFFFFFFFFFFFFFDFEFFFFFFF0000B090B9B9A00A90B0900000000000000000000000BCBF0B0000FFCFB0BFFFFFBEBDFFFFFFFFF00A00A00A0C00C00CA900D0A9CA90090B0A000009A9A9E00BCBC90BCBB9EF9BFFFFFFFFFFDA909C0AC9ACFFFFFFFFFFFFFFFFFBFF9FFFFFFFB000000000009A90000000000000000000000000000A0B0FF00000FFFB0FFFFFFFDFFFFFFFFFFFE0000000000A00A00A0CA00AC0A0CA0E00000000E0F0F00F0CB09A9E900CBDEDFFFFFFFFF0A0E0A0A00A0FFFFFFFFFFFFFFFFFFDE9FFFFFFFF0000000000000000000000000000000000000000000090DADB0000FFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000000000000B0F00E00A90B0000A9BFFFBFFFFFFFFFF00000000000FFFFFFFFFFFFFFFFFFFBFFFFFFFFF0000000000000000000000000000000000000000000000BFF00000000000000000000000000105000000000000AEAD05FE")}, + }; + } + + public static byte[] GetImage(int id) + { + return Images[GetImageKey(id)]; + } + + private static string GetImageKey(int id) + { + return typeof(T).Name + id; + } + + public static byte[] ToByteArray(string hexString) + { + var numberChars = hexString.Length; + var bytes = new byte[numberChars / 2]; + for (var i = 0; i < numberChars; i += 2) + { + bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); + } + return bytes; + } + + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ServiceModel/Northwind.dtos.cs b/tests/Northwind.Common/ServiceModel/Northwind.dtos.cs new file mode 100644 index 000000000..81ecc371b --- /dev/null +++ b/tests/Northwind.Common/ServiceModel/Northwind.dtos.cs @@ -0,0 +1,710 @@ +using System; +using System.Runtime.Serialization; +using ProtoBuf; +using ServiceStack.Model; + +namespace Northwind.Common.ServiceModel +{ + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + [Serializable] + public class EmployeeDto + : IHasIntId, IEquatable + { + [DataMember] + public int Id { get; set; } + + [DataMember] + public string LastName { get; set; } + + [DataMember] + public string FirstName { get; set; } + + [DataMember] + public string Title { get; set; } + + [DataMember] + public string TitleOfCourtesy { get; set; } + + [DataMember] + public DateTime? BirthDate { get; set; } + + [DataMember] + public DateTime? HireDate { get; set; } + + [DataMember] + public string Address { get; set; } + + [DataMember] + public string City { get; set; } + + [DataMember] + public string Region { get; set; } + + [DataMember] + public string PostalCode { get; set; } + + [DataMember] + public string Country { get; set; } + + [DataMember] + public string HomePhone { get; set; } + + [DataMember] + public string Extension { get; set; } + + //Some serializers can't handle bytes so disabling for all + // + //[DataMember] + public byte[] Photo { get; set; } + + [DataMember] + public string Notes { get; set; } + + [DataMember] + public int? ReportsTo { get; set; } + + [DataMember] + public string PhotoPath { get; set; } + + public bool Equals(EmployeeDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(LastName, other.LastName) && string.Equals(FirstName, other.FirstName) && string.Equals(Title, other.Title) && string.Equals(TitleOfCourtesy, other.TitleOfCourtesy) && BirthDate.Equals(other.BirthDate) && HireDate.Equals(other.HireDate) && string.Equals(Address, other.Address) && string.Equals(City, other.City) && string.Equals(Region, other.Region) && string.Equals(PostalCode, other.PostalCode) && string.Equals(Country, other.Country) && string.Equals(HomePhone, other.HomePhone) && string.Equals(Extension, other.Extension) && Equals(Photo, other.Photo) && string.Equals(Notes, other.Notes) && ReportsTo == other.ReportsTo && string.Equals(PhotoPath, other.PhotoPath); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((EmployeeDto)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode * 397) ^ (LastName != null ? LastName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (FirstName != null ? FirstName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Title != null ? Title.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (TitleOfCourtesy != null ? TitleOfCourtesy.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ BirthDate.GetHashCode(); + hashCode = (hashCode * 397) ^ HireDate.GetHashCode(); + hashCode = (hashCode * 397) ^ (Address != null ? Address.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (City != null ? City.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Region != null ? Region.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (PostalCode != null ? PostalCode.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Country != null ? Country.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (HomePhone != null ? HomePhone.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Extension != null ? Extension.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Photo != null ? Photo.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Notes != null ? Notes.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ ReportsTo.GetHashCode(); + hashCode = (hashCode * 397) ^ (PhotoPath != null ? PhotoPath.GetHashCode() : 0); + return hashCode; + } + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + + [DataContract] + [Serializable] + public class CategoryDto : IHasIntId, IEquatable + { + [DataMember] + public int Id { get; set; } + + [DataMember] + public string CategoryName { get; set; } + + [DataMember] + public string Description { get; set; } + + //[DataMember] + public byte[] Picture { get; set; } + + public bool Equals(CategoryDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(CategoryName, other.CategoryName) && string.Equals(Description, other.Description) && Equals(Picture, other.Picture); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CategoryDto) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode*397) ^ (CategoryName != null ? CategoryName.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Description != null ? Description.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Picture != null ? Picture.GetHashCode() : 0); + return hashCode; + } + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + [Serializable] + public class CustomerDto + : IHasStringId, IEquatable + { + [DataMember] + public string Id { get; set; } + + [DataMember] + public string CompanyName { get; set; } + + [DataMember] + public string ContactName { get; set; } + + [DataMember] + public string ContactTitle { get; set; } + + [DataMember] + public string Address { get; set; } + + [DataMember] + public string City { get; set; } + + [DataMember] + public string Region { get; set; } + + [DataMember] + public string PostalCode { get; set; } + + [DataMember] + public string Country { get; set; } + + [DataMember] + public string Phone { get; set; } + + [DataMember] + public string Fax { get; set; } + + //[DataMember] + public byte[] Picture { get; set; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CustomerDto) obj); + } + + public bool Equals(CustomerDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Id, other.Id) && string.Equals(CompanyName, other.CompanyName) && string.Equals(ContactName, other.ContactName) && string.Equals(ContactTitle, other.ContactTitle) && string.Equals(Address, other.Address) && string.Equals(City, other.City) && string.Equals(Region, other.Region) && string.Equals(PostalCode, other.PostalCode) && string.Equals(Country, other.Country) && string.Equals(Phone, other.Phone) && string.Equals(Fax, other.Fax) && Equals(Picture, other.Picture); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Id != null ? Id.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (CompanyName != null ? CompanyName.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ContactName != null ? ContactName.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (ContactTitle != null ? ContactTitle.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Address != null ? Address.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (City != null ? City.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Region != null ? Region.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (PostalCode != null ? PostalCode.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Country != null ? Country.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Phone != null ? Phone.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Fax != null ? Fax.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Picture != null ? Picture.GetHashCode() : 0); + return hashCode; + } + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + + [DataContract] + [Serializable] + public class ShipperDto : IHasIntId, IEquatable + { + [DataMember] + public int Id { get; set; } + + [DataMember] + public string CompanyName { get; set; } + + [DataMember] + public string Phone { get; set; } + + public bool Equals(ShipperDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(CompanyName, other.CompanyName) && string.Equals(Phone, other.Phone); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ShipperDto) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode*397) ^ (CompanyName != null ? CompanyName.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Phone != null ? Phone.GetHashCode() : 0); + return hashCode; + } + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + + [DataContract] + [Serializable] + public class SupplierDto : IHasIntId + { + [DataMember] + public int Id { get; set; } + + [DataMember] + public string CompanyName { get; set; } + + [DataMember] + public string ContactName { get; set; } + + [DataMember] + public string ContactTitle { get; set; } + + [DataMember] + public string Address { get; set; } + + [DataMember] + public string City { get; set; } + + [DataMember] + public string Region { get; set; } + + [DataMember] + public string PostalCode { get; set; } + + [DataMember] + public string Country { get; set; } + + [DataMember] + public string Phone { get; set; } + + [DataMember] + public string Fax { get; set; } + + [DataMember] + public string HomePage { get; set; } + + public override bool Equals(object obj) + { + var other = obj as SupplierDto; + if (other == null) return false; + + return this.Id == other.Id + && this.CompanyName == other.CompanyName + && this.ContactName == other.ContactName + && this.ContactTitle == other.ContactTitle + && this.Address == other.Address + && this.City == other.City + && this.Region == other.Region + && this.PostalCode == other.PostalCode + && this.Country == other.Country + && this.Phone == other.Phone + && this.Fax == other.Fax + && this.HomePage == other.HomePage; + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + + [DataContract] + [Serializable] + public class OrderDto + : IHasIntId + { + + [DataMember] + public int Id { get; set; } + + [DataMember] + public string CustomerId { get; set; } + + [DataMember] + public int EmployeeId { get; set; } + + [DataMember] + public DateTime? OrderDate { get; set; } + + [DataMember] + public DateTime? RequiredDate { get; set; } + + [DataMember] + public DateTime? ShippedDate { get; set; } + + [DataMember] + public int? ShipVia { get; set; } + + [DataMember] + public decimal Freight { get; set; } + + [DataMember] + public string ShipName { get; set; } + + [DataMember] + public string ShipAddress { get; set; } + + [DataMember] + public string ShipCity { get; set; } + + [DataMember] + public string ShipRegion { get; set; } + + [DataMember] + public string ShipPostalCode { get; set; } + + [DataMember] + public string ShipCountry { get; set; } + + public override bool Equals(object obj) + { + var other = obj as OrderDto; + if (other == null) return false; + + return this.Id == other.Id + && this.CustomerId == other.CustomerId + && this.EmployeeId == other.EmployeeId + && this.OrderDate == other.OrderDate + && this.RequiredDate == other.RequiredDate + && this.ShippedDate == other.ShippedDate + && this.ShipVia == other.ShipVia + && this.Freight == other.Freight + && this.ShipName == other.ShipName + && this.ShipAddress == other.ShipAddress + && this.ShipCity == other.ShipCity + && this.ShipRegion == other.ShipRegion + && this.ShipPostalCode == other.ShipPostalCode + && this.ShipCountry == other.ShipCountry; + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + [Serializable] + public class ProductDto : IHasIntId, IEquatable + { + [DataMember] + public int Id { get; set; } + + [DataMember] + public string ProductName { get; set; } + + [DataMember] + public int SupplierId { get; set; } + + [DataMember] + public int CategoryId { get; set; } + + [DataMember] + public string QuantityPerUnit { get; set; } + + [DataMember] + public decimal UnitPrice { get; set; } + + [DataMember] + public short UnitsInStock { get; set; } + + [DataMember] + public short UnitsOnOrder { get; set; } + + [DataMember] + public short ReorderLevel { get; set; } + + [DataMember] + public bool Discontinued { get; set; } + + public bool Equals(ProductDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(ProductName, other.ProductName) && SupplierId == other.SupplierId && CategoryId == other.CategoryId && string.Equals(QuantityPerUnit, other.QuantityPerUnit) && UnitPrice == other.UnitPrice && UnitsInStock == other.UnitsInStock && UnitsOnOrder == other.UnitsOnOrder && ReorderLevel == other.ReorderLevel && Discontinued == other.Discontinued; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ProductDto) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Id; + hashCode = (hashCode*397) ^ (ProductName != null ? ProductName.GetHashCode() : 0); + hashCode = (hashCode*397) ^ SupplierId; + hashCode = (hashCode*397) ^ CategoryId; + hashCode = (hashCode*397) ^ (QuantityPerUnit != null ? QuantityPerUnit.GetHashCode() : 0); + hashCode = (hashCode*397) ^ UnitPrice.GetHashCode(); + hashCode = (hashCode*397) ^ UnitsInStock.GetHashCode(); + hashCode = (hashCode*397) ^ UnitsOnOrder.GetHashCode(); + hashCode = (hashCode*397) ^ ReorderLevel.GetHashCode(); + hashCode = (hashCode*397) ^ Discontinued.GetHashCode(); + return hashCode; + } + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + [Serializable] + public class OrderDetailDto + : IHasStringId + { + [DataMember] + public string Id { get; set; } + //public string Id { get { return this.OrderId + "/" + this.ProductId; } } //Protobuf no like + + [DataMember] + public int OrderId { get; set; } + + [DataMember] + public int ProductId { get; set; } + + [DataMember] + public decimal UnitPrice { get; set; } + + [DataMember] + public short Quantity { get; set; } + + [DataMember] + public double Discount { get; set; } + + public override bool Equals(object obj) + { + var other = obj as OrderDetailDto; + if (other == null) return false; + + return this.Id == other.Id + && this.OrderId == other.OrderId + && this.ProductId == other.ProductId + && this.UnitPrice == other.UnitPrice + && this.Quantity == other.Quantity + && this.Discount == other.Discount; + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + + [DataContract] + [Serializable] + public class CustomerCustomerDemoDto : IHasStringId, IEquatable + { + [DataMember] + public string Id { get; set; } + + [DataMember] + public string CustomerTypeId { get; set; } + + public bool Equals(CustomerCustomerDemoDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Id, other.Id) && string.Equals(CustomerTypeId, other.CustomerTypeId); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CustomerCustomerDemoDto) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Id != null ? Id.GetHashCode() : 0)*397) ^ (CustomerTypeId != null ? CustomerTypeId.GetHashCode() : 0); + } + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + + [DataContract] + [Serializable] + public class CustomerDemographicDto : IHasStringId, IEquatable + { + [DataMember] + public string Id { get; set; } + + [DataMember] + public string CustomerDesc { get; set; } + + public bool Equals(CustomerDemographicDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Id, other.Id) && string.Equals(CustomerDesc, other.CustomerDesc); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CustomerDemographicDto) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Id != null ? Id.GetHashCode() : 0)*397) ^ (CustomerDesc != null ? CustomerDesc.GetHashCode() : 0); + } + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + + [DataContract] + [Serializable] + public class RegionDto : IHasIntId, IEquatable + { + [DataMember] + public int Id { get; set; } + + [DataMember] + public string RegionDescription { get; set; } + + public bool Equals(RegionDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && string.Equals(RegionDescription, other.RegionDescription); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((RegionDto) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Id*397) ^ (RegionDescription != null ? RegionDescription.GetHashCode() : 0); + } + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + [Serializable] + public class TerritoryDto : IHasStringId, IEquatable + { + [DataMember] + public string Id { get; set; } + + [DataMember] + public string TerritoryDescription { get; set; } + + [DataMember] + public int RegionId { get; set; } + + public bool Equals(TerritoryDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Id, other.Id) && string.Equals(TerritoryDescription, other.TerritoryDescription) && RegionId == other.RegionId; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TerritoryDto) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Id != null ? Id.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (TerritoryDescription != null ? TerritoryDescription.GetHashCode() : 0); + hashCode = (hashCode*397) ^ RegionId; + return hashCode; + } + } + } + + [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, InferTagFromName = true)] + [DataContract] + [Serializable] + public class EmployeeTerritoryDto : IHasStringId, IEquatable + { + [DataMember] + public string Id { get; set; } + //public string Id { get { return this.EmployeeId + "/" + this.TerritoryId; } } //Protobuf no like + + [DataMember] + public int EmployeeId { get; set; } + + [DataMember] + public string TerritoryId { get; set; } + + public bool Equals(EmployeeTerritoryDto other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Id, other.Id) && EmployeeId == other.EmployeeId && string.Equals(TerritoryId, other.TerritoryId); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((EmployeeTerritoryDto) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Id != null ? Id.GetHashCode() : 0); + hashCode = (hashCode*397) ^ EmployeeId; + hashCode = (hashCode*397) ^ (TerritoryId != null ? TerritoryId.GetHashCode() : 0); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/tests/Northwind.Common/ServiceModel/NorthwindDtoFactory.cs b/tests/Northwind.Common/ServiceModel/NorthwindDtoFactory.cs new file mode 100644 index 000000000..f5120b2c0 --- /dev/null +++ b/tests/Northwind.Common/ServiceModel/NorthwindDtoFactory.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Northwind.Common.ServiceModel +{ + public static class NorthwindDtoFactory + { + public static CategoryDto Category(int id, string categoryName, string description, byte[] picture) + { + return new CategoryDto + { + Id = id, + CategoryName = categoryName, + Description = description, + Picture = picture + }; + } + + public static CustomerDto Customer( + string customerId, string companyName, string contactName, string contactTitle, + string address, string city, string region, string postalCode, string country, + string phoneNo, string faxNo, + byte[] picture) + { + return new CustomerDto + { + Id = customerId, + CompanyName = companyName, + ContactName = contactName, + ContactTitle = contactTitle, + Address = address, + City = city, + Region = region, + PostalCode = postalCode, + Country = country, + Phone = phoneNo, + Fax = faxNo, + Picture = picture + }; + } + + public static EmployeeDto Employee( + int employeeId, string lastName, string firstName, string title, + string titleOfCourtesy, DateTime? birthDate, DateTime? hireDate, + string address, string city, string region, string postalCode, string country, + string homePhone, string extension, + byte[] photo, + string notes, int? reportsTo, string photoPath) + { + return new EmployeeDto + { + Id = employeeId, + LastName = lastName, + FirstName = firstName, + Title = title, + TitleOfCourtesy = titleOfCourtesy, + BirthDate = birthDate, + HireDate = hireDate, + Address = address, + City = city, + Region = region, + PostalCode = postalCode, + Country = country, + HomePhone = homePhone, + Extension = extension, + Photo = photo, + Notes = notes, + ReportsTo = reportsTo, + PhotoPath = photoPath, + }; + } + + public static ShipperDto Shipper(int id, string companyName, string phoneNo) + { + return new ShipperDto + { + Id = id, + CompanyName = companyName, + Phone = phoneNo, + }; + } + + public static SupplierDto Supplier( + int supplierId, string companyName, string contactName, string contactTitle, + string address, string city, string region, string postalCode, string country, + string phoneNo, string faxNo, + string homePage) + { + return new SupplierDto + { + Id = supplierId, + CompanyName = companyName, + ContactName = contactName, + ContactTitle = contactTitle, + Address = address, + City = city, + Region = region, + PostalCode = postalCode, + Country = country, + Phone = phoneNo, + Fax = faxNo, + HomePage = homePage + }; + } + + public static OrderDto Order( + int orderId, string customerId, int employeeId, DateTime? orderDate, DateTime? requiredDate, + DateTime? shippedDate, int shipVia, decimal freight, string shipName, + string address, string city, string region, string postalCode, string country) + { + return new OrderDto + { + Id = orderId, + CustomerId = customerId, + EmployeeId = employeeId, + OrderDate = orderDate, + RequiredDate = requiredDate, + ShippedDate = shippedDate, + ShipVia = shipVia, + Freight = freight, + ShipName = shipName, + ShipAddress = address, + ShipCity = city, + ShipRegion = region, + ShipPostalCode = postalCode, + ShipCountry = country, + }; + } + + public static ProductDto Product( + int productId, string productName, int supplierId, int categoryId, + string qtyPerUnit, decimal unitPrice, short unitsInStock, + short unitsOnOrder, short reorderLevel, bool discontinued) + { + return new ProductDto + { + Id = productId, + ProductName = productName, + SupplierId = supplierId, + CategoryId = categoryId, + QuantityPerUnit = qtyPerUnit, + UnitPrice = unitPrice, + UnitsInStock = unitsInStock, + UnitsOnOrder = unitsOnOrder, + ReorderLevel = reorderLevel, + Discontinued = discontinued, + }; + } + + public static OrderDetailDto OrderDetail( + int orderId, int productId, decimal unitPrice, short quantity, double discount) + { + return new OrderDetailDto + { + OrderId = orderId, + ProductId = productId, + UnitPrice = unitPrice, + Quantity = quantity, + Discount = discount, + }; + } + + public static CustomerCustomerDemoDto CustomerCustomerDemo( + string customerId, string customerTypeId) + { + return new CustomerCustomerDemoDto + { + Id = customerId, + CustomerTypeId = customerTypeId, + }; + } + + public static RegionDto Region( + int regionId, string regionDescription) + { + return new RegionDto + { + Id = regionId, + RegionDescription = regionDescription, + }; + } + + public static TerritoryDto Territory( + string territoryId, string territoryDescription, int regionId) + { + return new TerritoryDto + { + Id = territoryId, + TerritoryDescription = territoryDescription, + RegionId = regionId, + }; + } + + public static EmployeeTerritoryDto EmployeeTerritory( + int employeeId, string territoryId) + { + return new EmployeeTerritoryDto + { + EmployeeId = employeeId, + TerritoryId = territoryId, + }; + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/BookShelfBenchmarks.cs b/tests/ServiceStack.Text.Benchmarks/BookShelfBenchmarks.cs new file mode 100644 index 000000000..1ca72a1b3 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/BookShelfBenchmarks.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using BenchmarkDotNet.Attributes; +using Newtonsoft.Json; + +namespace ServiceStack.Text.Benchmarks +{ + [DataContract] + public class BookShelf + { + [DataMember] + public List Books + { + get; + set; + } + + [DataMember] + private string Secret; + + public BookShelf(string secret) + { + Secret = secret; + } + + public BookShelf() // Parameterless ctor is needed for every protocol buffer class during deserialization + { } + } + + [DataContract] + public class Book + { + [DataMember] + public string Title; + + [DataMember] + public int Id; + } + + public static class BookUtils + { + public static BookShelf Data(int nToCreate) + { + var lret = new BookShelf("private member value") + { + Books = Enumerable.Range(1, nToCreate).Select(i => new Book { Id = i, Title = $"Book {i}" }).ToList() + }; + return lret; + } + } + + public class BookShelfooksBenchmarksBase + { + protected BookShelf data; + protected MemoryStream ssStream; + protected ReadOnlyMemory ssSpan; + protected string ssJson; + + protected MemoryStream jnStream; + protected string jnJson; + + protected void Init(int count) + { + data = BookUtils.Data(10000); + + ssStream = new MemoryStream(); + ServiceStack.Text.JsonSerializer.SerializeToStream(data, ssStream); + ssJson = ssStream.ReadToEnd(); + ssSpan = ssJson.AsMemory(); + + jnStream = new MemoryStream(); + var writer = new StreamWriter(jnStream, Encoding.UTF8, 1024, leaveOpen: true); + var jsonWriter = new JsonTextWriter(writer); + var serializer = new Newtonsoft.Json.JsonSerializer(); + serializer.Serialize(jsonWriter, data); + jsonWriter.Flush(); + + jnJson = jnStream.ReadToEnd(); + $"DATA ServiceStack length = {ssJson.Length}, JSON.NET length = {jnJson.Length}".Print(); + } + } + +/* + Method | N | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +----------------------------- |------ |---------:|----------:|----------:|---------:|---------:|---------:|----------:| + DeserializeFromStream | 10000 | 7.069 ms | 0.0331 ms | 0.0277 ms | 226.5625 | 109.3750 | 39.0625 | 1.25 MB | + DeserializeFromStreamJsonNet | 10000 | 8.432 ms | 0.1628 ms | 0.2059 ms | 218.7500 | 109.3750 | 31.2500 | 1.25 MB | + SerializeToString | 10000 | 2.649 ms | 0.0200 ms | 0.0167 ms | 304.6875 | 156.2500 | 156.2500 | 1.21 MB | + SerializeToStringJsonNet | 10000 | 4.632 ms | 0.0664 ms | 0.0621 ms | 304.6875 | 257.8125 | 156.2500 | 1.45 MB | + + */ + [MemoryDiagnoser] + public class BookShelf10000BooksBenchmarks : BookShelfooksBenchmarksBase + { + [Params(10000)] public int N; + + [GlobalSetup] + public void Setup() + { + Init(10000); + } + + [Benchmark] + public object DeserializeFromStream() => JsonSerializer.DeserializeFromStream(ssStream); + + [Benchmark] + public object DeserializeFromStreamJsonNet() + { + jnStream.Position = 0; + var serializer = new Newtonsoft.Json.JsonSerializer(); + var sr = new StreamReader(jnStream); + var jsonTextReader = new JsonTextReader(sr); + return serializer.Deserialize(jsonTextReader); + } + +// [Benchmark] +// public Task DeserializeFromStreamAsync() => JsonSerializer.DeserializeFromStreamAsync(ssStream); + +// [Benchmark] +// public object DeserializeFromSpan() => JsonSerializer.DeserializeFromSpan(ssSpan.Span); +// +// [Benchmark] +// public object DeserializeFromString() => JsonSerializer.DeserializeFromString(ssJson); + +// [Benchmark] +// public object DeserializeFromStringJsonNet() => JsonConvert.DeserializeObject(jnJson); + + [Benchmark] + public string SerializeToString() => ServiceStack.Text.JsonSerializer.SerializeToString(data); + + [Benchmark] + public string SerializeToStringJsonNet() => Newtonsoft.Json.JsonConvert.SerializeObject(data); + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/JsonDeserializationBenchmarks.cs b/tests/ServiceStack.Text.Benchmarks/JsonDeserializationBenchmarks.cs new file mode 100644 index 000000000..fac14a010 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/JsonDeserializationBenchmarks.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; +using System.Text; +using BenchmarkDotNet.Attributes; +using ServiceStack.Text; +using ServiceStack.Text.Json; +using StackExchange.Profiling; + +namespace ServiceStack.Text.Benchmarks +{ + public class StringType + { + public string Value1 { get; set; } + public string Value2 { get; set; } + public string Value3 { get; set; } + public string Value4 { get; set; } + public string Value5 { get; set; } + public string Value6 { get; set; } + public string Value7 { get; set; } + + public static StringType Create() + { + var st = new StringType(); + st.Value1 = st.Value2 = st.Value3 = st.Value4 = st.Value5 = st.Value6 = st.Value7 = "Hello, world"; + return st; + } + } + + [MemoryDiagnoser] + public class JsonDeserializationBenchmarks + { + static ModelWithCommonTypes commonTypesModel = ModelWithCommonTypes.Create(3); + static MemoryStream stream = new MemoryStream(32768); + const string serializedString = "this is the test string"; + readonly string serializedString256 = new string('t', 256); + readonly string serializedString512 = new string('t', 512); + readonly string serializedString4096 = new string('t', 4096); + + static string commonTypesModelJson; + static ReadOnlyMemory commonTypesModelJsonSpan; + + static string stringTypeJson; + static ReadOnlyMemory stringTypeJsonSpan; + + static JsonDeserializationBenchmarks() + { + commonTypesModelJson = JsonSerializer.SerializeToString(commonTypesModel); + commonTypesModelJsonSpan = commonTypesModelJson.AsMemory(); + + stringTypeJson = JsonSerializer.SerializeToString(StringType.Create()); + stringTypeJsonSpan = stringTypeJson.AsMemory(); + } + + [Benchmark(Description = "Deserialize Json: class with builtin types (string)")] + public void DeserializeJsonCommonTypesString() + { + var result = JsonSerializer.DeserializeFromString(commonTypesModelJson); + } + + [Benchmark(Description = "Deserialize Json: class with builtin types (span)")] + public void DeserializeJsonCommonTypesSpan() + { + var result = JsonSerializer.DeserializeFromString(commonTypesModelJson); + } + + [Benchmark(Description = "Deserialize Json: class with 10 string properties")] + public void DeserializeStringType() + { + var result = JsonSerializer.DeserializeFromString(stringTypeJson); + } + + [Benchmark(Description = "Deserialize Json: Complex MiniProfiler")] + public MiniProfiler ComplexDeserializeServiceStack() => ServiceStack.Text.JsonSerializer.DeserializeFromString(_complexProfilerJson); + + private static readonly MiniProfiler _complexProfiler = GetComplexProfiler(); + private static readonly string _complexProfilerJson = _complexProfiler.ToJson(); + + private static MiniProfiler GetComplexProfiler() + { + var mp = new MiniProfiler("Complex"); + for (var i = 0; i < 50; i++) + { + using (mp.Step("Step " + i)) + { + for (var j = 0; j < 50; j++) + { + using (mp.Step("SubStep " + j)) + { + for (var k = 0; k < 50; k++) + { + using (mp.CustomTiming("Custom " + k, "YOLO!")) + { + } + } + } + } + } + } + return mp; + } + } +} diff --git a/tests/ServiceStack.Text.Benchmarks/MemoryProviderBenchmarks.cs b/tests/ServiceStack.Text.Benchmarks/MemoryProviderBenchmarks.cs new file mode 100644 index 000000000..a2a97a6a4 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MemoryProviderBenchmarks.cs @@ -0,0 +1,152 @@ +using System; +using System.Globalization; +using BenchmarkDotNet.Attributes; + +namespace ServiceStack.Text.Benchmarks +{ +/* + Method | N | Mean | Error | StdDev | +-------------------- |------- |----------:|----------:|----------:| + DefaultParseBoolean | 1000 | 30.00 ns | 0.6018 ns | 0.5911 ns | + NetCoreParseBoolean | 1000 | 20.09 ns | 0.1783 ns | 0.1489 ns | + DefaulParseDecimal | 1000 | 54.57 ns | 1.3897 ns | 1.7067 ns | + NetCoreParseDecimal | 1000 | 189.92 ns | 3.3742 ns | 3.3139 ns | + DefaulParseFloat | 1000 | 117.37 ns | 0.9250 ns | 0.8652 ns | + NetCoreParseFloat | 1000 | 102.20 ns | 0.6069 ns | 0.5677 ns | + DefaulParseDouble | 1000 | 118.04 ns | 2.3821 ns | 2.3395 ns | + NetCoreParseDouble | 1000 | 101.87 ns | 1.1663 ns | 0.9739 ns | + DefaulParseInt32 | 1000 | 19.23 ns | 0.0790 ns | 0.0700 ns | + NetCoreParseInt32 | 1000 | 72.63 ns | 1.4722 ns | 1.8080 ns | + DefaulParseInt64 | 1000 | 18.35 ns | 0.1577 ns | 0.1231 ns | + NetCoreParseInt64 | 1000 | 69.79 ns | 0.5492 ns | 0.5138 ns | + DefaulParseUInt32 | 1000 | 18.66 ns | 0.1509 ns | 0.1260 ns | + NetCoreParseUInt32 | 1000 | 72.68 ns | 0.3446 ns | 0.3054 ns | + DefaultParseBoolean | 10000 | 28.35 ns | 0.1571 ns | 0.1470 ns | + NetCoreParseBoolean | 10000 | 19.39 ns | 0.0590 ns | 0.0552 ns | + DefaulParseDecimal | 10000 | 57.40 ns | 1.1499 ns | 1.8239 ns | + NetCoreParseDecimal | 10000 | 192.12 ns | 2.7811 ns | 2.6014 ns | + DefaulParseFloat | 10000 | 126.15 ns | 2.5386 ns | 4.2414 ns | + NetCoreParseFloat | 10000 | 106.11 ns | 1.6362 ns | 1.5305 ns | + DefaulParseDouble | 10000 | 122.10 ns | 1.9895 ns | 1.8610 ns | + NetCoreParseDouble | 10000 | 107.38 ns | 2.0829 ns | 1.9483 ns | + DefaulParseInt32 | 10000 | 19.97 ns | 0.3522 ns | 0.3122 ns | + NetCoreParseInt32 | 10000 | 72.41 ns | 0.8461 ns | 0.7500 ns | + DefaulParseInt64 | 10000 | 18.19 ns | 0.0536 ns | 0.0502 ns | + NetCoreParseInt64 | 10000 | 69.90 ns | 0.8358 ns | 0.6979 ns | + DefaulParseUInt32 | 10000 | 18.58 ns | 0.1056 ns | 0.0936 ns | + NetCoreParseUInt32 | 10000 | 72.05 ns | 0.3941 ns | 0.3291 ns | + + DefaultParseBoolean | 100000 | 28.40 ns | 0.1632 ns | 0.1447 ns | + NetCoreParseBoolean | 100000 | 19.38 ns | 0.1022 ns | 0.0853 ns | + + DefaulParseDecimal | 100000 | 51.66 ns | 0.2337 ns | 0.2071 ns | + NetCoreParseDecimal | 100000 | 185.71 ns | 1.0153 ns | 0.9000 ns | + + DefaulParseFloat | 100000 | 119.73 ns | 2.4091 ns | 3.0467 ns | + NetCoreParseFloat | 100000 | 103.48 ns | 0.9406 ns | 0.8798 ns | + + DefaulParseDouble | 100000 | 117.82 ns | 1.0134 ns | 0.9480 ns | + NetCoreParseDouble | 100000 | 102.38 ns | 0.3972 ns | 0.3316 ns | + + DefaulParseInt32 | 100000 | 19.41 ns | 0.2591 ns | 0.2424 ns | + NetCoreParseInt32 | 100000 | 72.14 ns | 0.7835 ns | 0.7329 ns | + + DefaulParseInt64 | 100000 | 18.33 ns | 0.1323 ns | 0.1237 ns | + NetCoreParseInt64 | 100000 | 70.20 ns | 0.5148 ns | 0.4815 ns | + + DefaulParseUInt32 | 100000 | 19.11 ns | 0.4070 ns | 0.4180 ns | + NetCoreParseUInt32 | 100000 | 74.99 ns | 1.3515 ns | 1.1980 ns | +*/ + + public class MemoryProviderBenchmarks + { + [Params(100000)] public int N; + + [Benchmark] + public bool DefaultParseBoolean() => DefaultMemory.Provider.ParseBoolean(bool.TrueString); + + [Benchmark] + public bool NetCoreParseBoolean() => NetCoreMemory.Provider.ParseBoolean(bool.TrueString); + + [Benchmark] + public decimal DefaulParseDecimal() => DefaultMemory.Provider.ParseDecimal("123456.123456"); + + [Benchmark] + public decimal NetCoreParseDecimal() => NetCoreMemory.Provider.ParseDecimal("123456.123456"); + + [Benchmark] + public float DefaulParseFloat() => DefaultMemory.Provider.ParseFloat("123456.123456"); + + [Benchmark] + public float NetCoreParseFloat() => NetCoreMemory.Provider.ParseFloat("123456.123456"); + + [Benchmark] + public double DefaulParseDouble() => DefaultMemory.Provider.ParseDouble("123456.123456"); + + [Benchmark] + public double NetCoreParseDouble() => NetCoreMemory.Provider.ParseDouble("123456.123456"); + + [Benchmark] + public int DefaulParseInt32() => DefaultMemory.Provider.ParseInt32("-123456789"); + + [Benchmark] + public int NetCoreParseInt32() => NetCoreMemory.Provider.ParseInt32("-123456789"); + + [Benchmark] + public long DefaulParseInt64() => DefaultMemory.Provider.ParseInt64("123456789"); + + [Benchmark] + public long NetCoreParseInt64() => NetCoreMemory.Provider.ParseInt64("123456789"); + + [Benchmark] + public uint DefaulParseUInt32() => DefaultMemory.Provider.ParseUInt32("123456789"); + + [Benchmark] + public uint NetCoreParseUInt32() => NetCoreMemory.Provider.ParseUInt32("123456789"); + } + + + /* + Method | N | Mean | Error | StdDev | + -------------------- |------ |----------:|----------:|----------:| + NetCoreParseDecimal | 10000 | 192.23 ns | 3.8236 ns | 4.8357 ns | + CustomParseDecimal | 10000 | 45.22 ns | 0.4465 ns | 0.3728 ns | + */ + public class MemoryDecimalBenchmarks + { + [Params(10000)] public int N; + + [Benchmark] + public decimal NetCoreParseDecimal() => decimal.Parse("123456.123456", + NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + [Benchmark] + public decimal CustomParseDecimal() => DefaultMemory.Provider.ParseDecimal("123456.123456", allowThousands: true); + } + + +/* + Method | N | Mean | Error | StdDev | Median | +------------------ |------ |---------:|----------:|----------:|---------:| + NetCoreParseInt32 | 10000 | 58.28 ns | 0.5349 ns | 0.5004 ns | 58.19 ns | + NetCoreParseInt34 | 10000 | 59.95 ns | 1.2276 ns | 2.6425 ns | 58.93 ns | + CustomParseInt32 | 10000 | 12.11 ns | 0.2584 ns | 0.2417 ns | 12.05 ns | + CustomParseInt64 | 10000 | 11.28 ns | 0.0723 ns | 0.0564 ns | 11.27 ns | +*/ + public class MemoryIntegerBenchmarks + { + [Params(10000)] public int N; + + [Benchmark] + public int NetCoreParseInt32() => int.Parse("1234"); + + [Benchmark] + public long NetCoreParseInt34() => long.Parse("1234"); + + [Benchmark] + public int CustomParseInt32() => DefaultMemory.Provider.ParseInt32("1234".AsSpan()); + + [Benchmark] + public long CustomParseInt64() => DefaultMemory.Provider.ParseInt64("1234".AsSpan()); + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTiming.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTiming.cs new file mode 100644 index 000000000..6ecc24636 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTiming.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.Serialization; + +namespace StackExchange.Profiling +{ + /// + /// A client timing probe + /// + [DataContract] + public class ClientTiming + { + /// + /// Gets or sets the name. + /// + [DataMember(Order = 1)] + public string Name { get; set; } + + /// + /// Gets or sets the start. + /// + [DataMember(Order = 2)] + public decimal Start { get; set; } + + /// + /// Gets or sets the duration. + /// + [DataMember(Order = 3)] + public decimal Duration { get; set; } + + /// + /// Unique Identifier used for sql storage. + /// + /// Not set unless storing in Sql + public Guid Id { get; set; } + + /// + /// Used for sql storage + /// + /// Not set unless storing in Sql + public Guid MiniProfilerId { get; set; } + } +} diff --git a/tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTimings.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTimings.cs new file mode 100644 index 000000000..0e3dbae35 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTimings.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; + +namespace StackExchange.Profiling +{ + /// + /// Times collected from the client + /// + [DataContract] + public class ClientTimings + { + private const string TimingPrefix = "clientPerformance[timing]["; + private const string ProbesPrefix = "clientProbes["; + + /// + /// Gets or sets the list of client side timings + /// + [DataMember(Order = 2)] + public List Timings { get; set; } + + /// + /// Gets or sets the redirect count. + /// + [DataMember(Order = 1)] + public int RedirectCount { get; set; } + + /// + /// Returns null if there is not client timing stuff + /// + /// The form to transform to a . + public static ClientTimings FromForm(IDictionary form) + { + ClientTimings timing = null; + // AJAX requests won't have client timings + if (!form.ContainsKey(TimingPrefix + "navigationStart]")) return timing; + long.TryParse(form[TimingPrefix + "navigationStart]"], out long navigationStart); + if (navigationStart > 0) + { + var timings = new List(); + timing = new ClientTimings(); + int.TryParse(form["clientPerformance[navigation][redirectCount]"], out int redirectCount); + timing.RedirectCount = redirectCount; + + var clientPerf = new Dictionary(); + var clientProbes = new Dictionary(); + + foreach (string key in + form.Keys.OrderBy(i => i.IndexOf("Start]", StringComparison.Ordinal) > 0 ? "_" + i : i)) + { + if (key.StartsWith(TimingPrefix, StringComparison.Ordinal)) + { + long.TryParse(form[key], out long val); + val -= navigationStart; + + string parsedName = key.Substring( + TimingPrefix.Length, (key.Length - 1) - TimingPrefix.Length); + + // just ignore stuff that is negative ... not relevant + if (val > 0) + { + if (parsedName.EndsWith("Start", StringComparison.Ordinal)) + { + var shortName = parsedName.Substring(0, parsedName.Length - 5); + clientPerf[shortName] = new ClientTiming + { + Duration = -1, + Name = parsedName, + Start = val + }; + } + else if (parsedName.EndsWith("End", StringComparison.Ordinal)) + { + var shortName = parsedName.Substring(0, parsedName.Length - 3); + if (clientPerf.TryGetValue(shortName, out var t)) + { + t.Duration = val - t.Start; + t.Name = shortName; + } + } + else + { + clientPerf[parsedName] = new ClientTiming { Name = parsedName, Start = val, Duration = -1 }; + } + } + } + + if (key.StartsWith(ProbesPrefix, StringComparison.Ordinal)) + { + int endBracketIndex = key.IndexOf(']'); + if (endBracketIndex > 0 && int.TryParse(key.Substring(ProbesPrefix.Length, endBracketIndex - ProbesPrefix.Length), out int probeId)) + { + if (!clientProbes.TryGetValue(probeId, out var t)) + { + t = new ClientTiming(); + clientProbes.Add(probeId, t); + } + + if (key.EndsWith("[n]", StringComparison.Ordinal)) + { + t.Name = form[key]; + } + + if (key.EndsWith("[d]", StringComparison.Ordinal)) + { + long.TryParse(form[key], out long val); + if (val > 0) + { + t.Start = val - navigationStart; + } + } + } + } + } + + foreach (var group in clientProbes + .Values.OrderBy(p => p.Name) + .GroupBy(p => p.Name)) + { + ClientTiming current = null; + foreach (var item in group) + { + if (current == null) + { + current = item; + } + else + { + current.Duration = item.Start - current.Start; + timings.Add(current); + current = null; + } + } + } + + foreach (var item in clientPerf.Values) + { + item.Name = SentenceCase(item.Name); + } + + timings.AddRange(clientPerf.Values); + timing.Timings = timings.OrderBy(t => t.Start).ToList(); + } + + return timing; + } + + private static string SentenceCase(string value) + { + var sb = new StringBuilder(); + for (int i = 0; i < value.Length; i++) + { + if (i == 0) + { + sb.Append(char.ToUpper(value[0])); + continue; + } + + if (value[i] == char.ToUpper(value[i])) + { + sb.Append(' '); + } + + sb.Append(value[i]); + } + + return sb.ToString(); + } + } +} diff --git a/tests/ServiceStack.Text.Benchmarks/MiniProfiler/CustomTiming.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/CustomTiming.cs new file mode 100644 index 000000000..545a7c09f --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/CustomTiming.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.Serialization; + +namespace StackExchange.Profiling +{ + /// + /// A custom timing that usually represents a Remote Procedure Call, allowing better + /// visibility into these longer-running calls. + /// + [DataContract] + public class CustomTiming : IDisposable + { + private readonly decimal? _minSaveMs; + + /// + /// Don't use this. + /// + [Obsolete("Used for serialization")] + public CustomTiming() { /* serialization only */ } + + /// + /// Returns a new CustomTiming, also initializing its and, optionally, its . + /// + /// The to attach the timing so. + /// The descriptive command string for this timing, e.g. a URL or database command. + /// (Optional) The minimum time required to actually save this timing (e.g. do we care?). + public CustomTiming(MiniProfiler profiler, string commandString, decimal? minSaveMs = null) + { + _profiler = profiler; + _startTicks = profiler.ElapsedTicks; + _minSaveMs = minSaveMs; + CommandString = commandString; + + Id = Guid.NewGuid(); + StartMilliseconds = profiler.GetRoundedMilliseconds(profiler.ElapsedTicks); + + if (true/*!MiniProfiler.Settings.ExcludeStackTraceSnippetFromCustomTimings*/) + { + StackTraceSnippet = Helpers.StackTraceSnippet.Get(); + } + } + + private readonly MiniProfiler _profiler; + private readonly long _startTicks; + + /// + /// Unique identifier for this . + /// + [DataMember(Order = 1)] + public Guid Id { get; set; } + + /// + /// Gets or sets the command that was executed, e.g. "select * from Table" or "INCR my:redis:key" + /// + [DataMember(Order = 2)] + public string CommandString { get; set; } + + // TODO: should this just match the key that the List is stored under in Timing.CustomTimings? + /// + /// Short name describing what kind of custom timing this is, e.g. "Get", "Query", "Fetch". + /// + [DataMember(Order = 3)] + public string ExecuteType { get; set; } + + /// + /// Gets or sets roughly where in the calling code that this custom timing was executed. + /// + [DataMember(Order = 4)] + public string StackTraceSnippet { get; set; } + + /// + /// Gets or sets the offset from main MiniProfiler start that this custom command began. + /// + [DataMember(Order = 5)] + public decimal StartMilliseconds { get; set; } + + /// + /// Gets or sets how long this custom command statement took to execute. + /// + [DataMember(Order = 6)] + public decimal? DurationMilliseconds { get; set; } + + /// + /// OPTIONAL - how long this tim took to come back initially from the remote server, + /// before all data is fetched and command is completed. + /// + [DataMember(Order = 7)] + public decimal? FirstFetchDurationMilliseconds { get; set; } + + /// + /// Whether this operation errored. + /// + [DataMember(Order = 8)] + public bool Errored { get; set; } + + internal string Category { get; set; } + + /// + /// OPTIONAL - call after receiving the first response from your Remote Procedure Call to + /// properly set . + /// + public void FirstFetchCompleted() + { + FirstFetchDurationMilliseconds = FirstFetchDurationMilliseconds ?? _profiler.GetDurationMilliseconds(_startTicks); + } + + /// + /// Stops this timing, setting . + /// + public void Stop() + { + DurationMilliseconds = DurationMilliseconds ?? _profiler.GetDurationMilliseconds(_startTicks); + + if (_minSaveMs.HasValue && _minSaveMs.Value > 0 && DurationMilliseconds < _minSaveMs.Value) + { + _profiler.Head.RemoveCustomTiming(Category, this); + } + } + + void IDisposable.Dispose() => Stop(); + + /// + /// Returns for debugging. + /// + public override string ToString() => CommandString; + } +} diff --git a/tests/ServiceStack.Text.Benchmarks/MiniProfiler/FlowData.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/FlowData.cs new file mode 100644 index 000000000..d03b10539 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/FlowData.cs @@ -0,0 +1,52 @@ +#if NETFX +using System.Runtime.Remoting; +using System.Runtime.Remoting.Messaging; +#else +using System.Threading; +#endif + +namespace StackExchange.Profiling.Internal +{ + /// + /// Internal MiniProfiler extensions, not meant for consumption. + /// This can and probably will break without warning. Don't use the .Internal namespace directly. + /// + /// Shim class covering for AsyncLocal{T} in pre-.NET 4.6 which didn't have it. + /// + /// The type of data to store. + public class FlowData + { +#if NETFX + // Key specific to this type. +#pragma warning disable RCS1158 // Avoid static members in generic types. + private static readonly string _key = typeof(FlowData).FullName; +#pragma warning restore RCS1158 // Avoid static members in generic types. + + /// + /// Gets or sets the value of the ambient data. + /// + public T Value + { + get + { + var handle = CallContext.LogicalGetData(_key) as ObjectHandle; + return handle != null + ? (T)handle.Unwrap() + : default(T); + } + set { CallContext.LogicalSetData(_key, new ObjectHandle(value)); } + } +#else + private readonly AsyncLocal _backing = new AsyncLocal(); + + /// + /// Gets or sets the value of the ambient data. + /// + public T Value + { + get => _backing.Value; + set => _backing.Value = value; + } +#endif + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfiler.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfiler.cs new file mode 100644 index 000000000..884dbfe47 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfiler.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using System.Diagnostics; +using StackExchange.Profiling.Internal; + +namespace StackExchange.Profiling +{ + /// + /// A single MiniProfiler can be used to represent any number of steps/levels in a call-graph, via Step() + /// + /// Totally baller. + [DataContract] + public partial class MiniProfiler + { + /// + /// Initialises a new instance of the class. + /// Obsolete - used for serialization. + /// + [Obsolete("Used for serialization")] + public MiniProfiler() { /* serialization only */ } + + /// + /// Initialises a new instance of the class. Creates and starts a new MiniProfiler + /// for the root . + /// + /// The name of this , typically a URL. + public MiniProfiler(string name) + { + Id = Guid.NewGuid(); + MachineName = Environment.MachineName; + Started = DateTime.UtcNow; + + // stopwatch must start before any child Timings are instantiated + Stopwatch = Stopwatch.StartNew(); + Root = new Timing(this, null, name); + } + + /// + /// Whether the profiler is currently profiling + /// + internal bool IsActive { get; set; } + + /// + /// Gets or sets the profiler id. + /// Identifies this Profiler so it may be stored/cached. + /// + [DataMember(Order = 1)] + public Guid Id { get; set; } + + /// + /// Gets or sets a display name for this profiling session. + /// + [DataMember(Order = 2)] + public string Name { get; set; } + + /// + /// Gets or sets when this profiler was instantiated, in UTC time. + /// + [DataMember(Order = 3)] + public DateTime Started { get; set; } + + /// + /// Gets the milliseconds, to one decimal place, that this MiniProfiler ran. + /// + [DataMember(Order = 4)] + public decimal DurationMilliseconds { get; set; } + + /// + /// Gets or sets where this profiler was run. + /// + [DataMember(Order = 5)] + public string MachineName { get; set; } + + /// + /// Keys are names, values are URLs, allowing additional links to be added to a profiler result, e.g. perhaps a deeper + /// diagnostic page for the current request. + /// + /// + /// Use to easily add a name/url pair to this dictionary. + /// + [DataMember(Order = 6)] + public Dictionary CustomLinks { get; set; } + + /// + /// Json used to store Custom Links. Do not touch. + /// + /*public string CustomLinksJson + { + get => CustomLinks?.ToJson(); + set + { + if (value.HasValue()) + { + CustomLinks = value.FromJson>(); + } + } + } */ + + private Timing _root; + /// + /// Gets or sets the root timing. + /// The first that is created and started when this profiler is instantiated. + /// All other s will be children of this one. + /// + [DataMember(Order = 7)] + public Timing Root + { + get => _root; + set + { + _root = value; + RootTimingId = value.Id; + + // TODO: Add serialization tests, then move Timing.Children to get/set + // and for() { .ParentTiming = this; } over in the local setter set there + // let serialization take care of the tree instead. + // I bet the current method doesn't even work, since it depends on .Root being set + // last in deserialization order + // TL;DR: I bet no one ever checked this, or possible no one cares about parent after deserialization. + + // when being deserialized, we need to go through and set all child timings' parents + if (_root.HasChildren) + { + var timings = new Stack(); + timings.Push(_root); + while (timings.Count > 0) + { + var timing = timings.Pop(); + + if (timing.HasChildren) + { + var children = timing.Children; + + for (int i = children.Count - 1; i >= 0; i--) + { + children[i].ParentTiming = timing; + timings.Push(children[i]); // FLORIDA! TODO: refactor this and other stack creation methods into one + } + } + } + } + } + } + + /// + /// Id of Root Timing. Used for Sql Storage purposes. + /// + public Guid? RootTimingId { get; set; } + + /// + /// Gets or sets timings collected from the client + /// + [DataMember(Order = 8)] + public ClientTimings ClientTimings { get; set; } + + /// + /// RedirectCount in ClientTimings. Used for sql storage. + /// + public int? ClientTimingsRedirectCount { get; set; } + + /// + /// Gets or sets a string identifying the user/client that is profiling this request. + /// + /// + /// If this is not set manually at some point, the IUserProvider implementation will be used; + /// by default, this will be the current request's IP address. + /// + [DataMember(Order = 9)] + public string User { get; set; } + + /// + /// Returns true when this MiniProfiler has been viewed by the that recorded it. + /// + /// + /// Allows POSTs that result in a redirect to be profiled. implementation + /// will keep a list of all profilers that haven't been fetched down. + /// + [DataMember(Order = 10)] + public bool HasUserViewed { get; set; } + + // Allows async to properly track the attachment point + private readonly FlowData _head = new FlowData(); + + /// + /// Gets or sets points to the currently executing Timing. + /// + public Timing Head + { + get => _head.Value; + set => _head.Value = value; + } + + /// + /// Gets the ticks since this MiniProfiler was started. + /// + internal long ElapsedTicks => Stopwatch.ElapsedTicks; + + /// + /// Gets the timer, for unit testing, returns the timer. + /// + internal Stopwatch Stopwatch { get; set; } + + /// + /// Starts a new MiniProfiler based on the current . This new profiler can be accessed by + /// . + /// + /// + /// Allows explicit naming of the new profiling session; when null, an appropriate default will be used, e.g. for + /// a web request, the url will be used for the overall session name. + /// + //public static MiniProfiler Start(string sessionName = null) => + // Settings.ProfilerProvider.Start(sessionName); + + /// + /// Ends the current profiling session, if one exists. + /// + /// + /// When true, clears the , allowing profiling to + /// be prematurely stopped and discarded. Useful for when a specific route does not need to be profiled. + /// + //public static void Stop(bool discardResults = false) => + // Settings.ProfilerProvider.Stop(discardResults); + + /// + /// Asynchronously ends the current profiling session, if one exists. + /// This invokves async saving all the way down if th providers support it. + /// + /// + /// When true, clears the , allowing profiling to + /// be prematurely stopped and discarded. Useful for when a specific route does not need to be profiled. + /// + //public static Task StopAsync(bool discardResults = false) => + // Settings.ProfilerProvider.StopAsync(discardResults); + + /// + /// Deserializes the JSON string parameter to a . + /// + /// The string to deserialize int a . + //public static MiniProfiler FromJson(string json) => json.FromJson(); + + /// + /// Returns the 's and this profiler recorded. + /// + /// a string containing the recording information + public override string ToString() => Root != null ? Root.Name + " (" + DurationMilliseconds + " ms)" : string.Empty; + + /// + /// Returns true if Ids match. + /// + /// The to compare to. + public override bool Equals(object obj) => obj is MiniProfiler && Id.Equals(((MiniProfiler)obj).Id); + + /// + /// Returns hash code of Id. + /// + public override int GetHashCode() => Id.GetHashCode(); + + /// + /// Walks the hierarchy contained in this profiler, starting with , and returns each Timing found. + /// + public IEnumerable GetTimingHierarchy() + { + var timings = new Stack(); + + timings.Push(_root); + + while (timings.Count > 0) + { + var timing = timings.Pop(); + + yield return timing; + + if (timing.HasChildren) + { + var children = timing.Children; + for (int i = children.Count - 1; i >= 0; i--) timings.Push(children[i]); + } + } + } + +#if !NETCORE + /// + /// Create a DEEP clone of this MiniProfiler. + /// + public MiniProfiler Clone() + { + var serializer = new DataContractSerializer(typeof(MiniProfiler), null, int.MaxValue, false, true, null); + using (var ms = new System.IO.MemoryStream()) + { + serializer.WriteObject(ms, this); + ms.Position = 0; + return (MiniProfiler)serializer.ReadObject(ms); + } + } +#endif + + internal Timing StepImpl(string name, decimal? minSaveMs = null, bool? includeChildrenWithMinSave = false) + { + return new Timing(this, Head, name, minSaveMs, includeChildrenWithMinSave); + } + + //internal IDisposable IgnoreImpl() => new Suppression(this); + + internal bool StopImpl() + { + if (!Stopwatch.IsRunning) + return false; + + Stopwatch.Stop(); + DurationMilliseconds = GetRoundedMilliseconds(ElapsedTicks); + + foreach (var timing in GetTimingHierarchy()) + { + timing.Stop(); + } + + return true; + } + + /// + /// Returns milliseconds based on Stopwatch's Frequency, rounded to one decimal place. + /// + /// The tick count to round. + internal decimal GetRoundedMilliseconds(long ticks) + { + long z = 10000 * ticks; + decimal timesTen = (int)(z / Stopwatch.Frequency); + return timesTen / 10; + } + + /// + /// Returns how many milliseconds have elapsed since was recorded. + /// + /// The start tick count. + internal decimal GetDurationMilliseconds(long startTicks) => + GetRoundedMilliseconds(ElapsedTicks - startTicks); + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfilerExtensions.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfilerExtensions.cs new file mode 100644 index 000000000..9c54b3b98 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfilerExtensions.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; + +namespace StackExchange.Profiling +{ + /// + /// Contains helper methods that ease working with null s. + /// + public static class MiniProfilerExtensions + { + /// + /// Wraps in a call and executes it, returning its result. + /// + /// the type of result. + /// The current profiling session or null. + /// Method to execute and profile. + /// The step name used to label the profiler results. + /// the profiled result. + /// Throws when is null. + public static T Inline(this MiniProfiler profiler, Func selector, string name) + { + if (selector == null) throw new ArgumentNullException(nameof(selector)); + if (profiler == null) return selector(); + using (profiler.StepImpl(name)) + { + return selector(); + } + } + + /// + /// Returns an () that will time the code between its creation and disposal. + /// + /// The current profiling session or null. + /// A descriptive name for the code that is encapsulated by the resulting Timing's lifetime. + /// the profile step + public static Timing Step(this MiniProfiler profiler, string name) => profiler?.StepImpl(name); + + /// + /// Returns an () that will time the code between its creation and disposal. + /// Will only save the if total time taken exceeds . + /// + /// The current profiling session or null. + /// A descriptive name for the code that is encapsulated by the resulting Timing's lifetime. + /// The minimum amount of time that needs to elapse in order for this result to be recorded. + /// Should the amount of time spent in child timings be included when comparing total time + /// profiled with ? If true, will include children. If false will ignore children. + /// + /// If is set to true and a child is removed due to its use of StepIf, then the + /// time spent in that time will also not count for the current StepIf calculation. + public static Timing StepIf(this MiniProfiler profiler, string name, decimal minSaveMs, bool includeChildren = false) + { + return profiler?.StepImpl(name, minSaveMs, includeChildren); + } + + /// + /// Returns a new that will automatically set its + /// and + /// + /// The current profiling session or null. + /// The category under which this timing will be recorded. + /// The command string that will be recorded along with this timing, for display in the MiniProfiler results. + /// Execute Type to be associated with the Custom Timing. Example: Get, Set, Insert, Delete + /// + /// Should be used like the extension method + /// + public static CustomTiming CustomTiming(this MiniProfiler profiler, string category, string commandString, string executeType = null) + { + return CustomTimingIf(profiler, category, commandString, 0, executeType: executeType); + } + + /// + /// Returns a new that will automatically set its + /// and . Will only save the new if the total elapsed time + /// takes more than . + /// + /// The current profiling session or null. + /// The category under which this timing will be recorded. + /// The command string that will be recorded along with this timing, for display in the MiniProfiler results. + /// Execute Type to be associated with the Custom Timing. Example: Get, Set, Insert, Delete + /// The minimum amount of time that needs to elapse in order for this result to be recorded. + /// + /// Should be used like the extension method + /// + public static CustomTiming CustomTimingIf(this MiniProfiler profiler, string category, string commandString, decimal minSaveMs, string executeType = null) + { + if (profiler == null || profiler.Head == null || !profiler.IsActive) return null; + + var result = new CustomTiming(profiler, commandString, minSaveMs) + { + ExecuteType = executeType, + Category = category + }; + + // THREADING: revisit + profiler.Head.AddCustomTiming(category, result); + + return result; + } + + /// + /// Returns an that will ignore profiling between its creation and disposal. + /// + /// + /// This is mainly useful in situations where you want to ignore database profiling for known hot spots, + /// but it is safe to use in a nested step such that you can ignore sub-sections of a profiled step. + /// + /// The current profiling session or null. + /// the profile step + //public static IDisposable Ignore(this MiniProfiler profiler) => profiler?.IgnoreImpl(); + + /// + /// Adds 's hierarchy to this profiler's current Timing step, + /// allowing other threads, remote calls, etc. to be profiled and joined into this profiling session. + /// + /// The to add to. + /// The to append to 's tree. + public static void AddProfilerResults(this MiniProfiler profiler, MiniProfiler externalProfiler) + { + if (profiler?.Head == null || externalProfiler == null) return; + profiler.Head.AddChild(externalProfiler.Root); + } + + /// + /// Adds the and pair to 's + /// dictionary; will be displayed on the client in the bottom of the profiler popup. + /// + /// The to add the link to. + /// The text label for the link. + /// The URL the link goes to. + public static void AddCustomLink(this MiniProfiler profiler, string text, string url) + { + if (profiler == null || !profiler.IsActive) return; + + lock (profiler) + { + profiler.CustomLinks = profiler.CustomLinks ?? new Dictionary(); + } + + profiler.CustomLinks[text] = url; + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/MiniProfiler/StackTraceSnippet.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/StackTraceSnippet.cs new file mode 100644 index 000000000..70ea9d1fb --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/StackTraceSnippet.cs @@ -0,0 +1,78 @@ +using System.Diagnostics; +using System.Reflection; +using System.Text; + +namespace StackExchange.Profiling.Helpers +{ + /// + /// Gets part of a stack trace containing only methods we care about. + /// + public static class StackTraceSnippet + { + // TODO: Uhhhhhhh, this isn't gonna work. Let's come back to this. Oh and async. Dammit. + private const string AspNetEntryPointMethodName = "System.Web.HttpApplication.IExecutionStep.Execute"; + + /// + /// Gets the current formatted and filtered stack trace. + /// + /// Space separated list of methods + public static string Get() + { +#if !NETCOREAPP1_1 + var frames = new StackTrace().GetFrames(); +#else // TODO: Make this work in .NET Standard, true fix isn't until 2.0 via https://github.com/dotnet/corefx/pull/12527 + StackFrame[] frames = null; +#endif + if (frames == null /*|| MiniProfiler.Settings.StackMaxLength <= 0*/) + { + return string.Empty; + } + + var sb = new StringBuilder(); + int stackLength = -1; // Starts on -1 instead of zero to compensate for adding 1 first time + + foreach (StackFrame t in frames) + { + var method = t.GetMethod(); + + // no need to continue up the chain + if (method.Name == AspNetEntryPointMethodName) + break; + + if (stackLength >= 120 /*MiniProfiler.Settings.StackMaxLength*/) + break; + + var assembly = method.Module.Assembly.GetName().Name; + //if (!ShouldExcludeType(method) + // && !MiniProfiler.Settings.AssembliesToExclude.Contains(assembly) + // && !MiniProfiler.Settings.MethodsToExclude.Contains(method.Name)) + { + if (sb.Length > 0) + { + sb.Append(' '); + } + sb.Append(method.Name); + stackLength += method.Name.Length + 1; // 1 added for spaces. + } + } + + return sb.ToString(); + } + + /*private static bool ShouldExcludeType(MethodBase method) + { + var t = method.DeclaringType; + + while (t != null) + { + if (MiniProfiler.Settings.TypesToExclude.Contains(t.Name)) + return true; + + t = t.DeclaringType; + } + + return false; + } + */ + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/MiniProfiler/Timing.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/Timing.cs new file mode 100644 index 000000000..5c1b90b13 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/Timing.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace StackExchange.Profiling +{ + /// + /// An individual profiling step that can contain child steps. + /// + [DataContract] + public class Timing : IDisposable + { + /// + /// Offset from parent MiniProfiler's creation that this Timing was created. + /// + private readonly long _startTicks; + private readonly decimal? _minSaveMs; + private readonly bool _includeChildrenWithMinSave; + + /// + /// Initialises a new instance of the class. + /// Obsolete - used for serialization. + /// + [Obsolete("Used for serialization")] + public Timing() { /* serialization */ } + + /// + /// Creates a new Timing named 'name' in the 'profiler's session, with 'parent' as this Timing's immediate ancestor. + /// + /// The this belongs to. + /// The this is a child of. + /// The name of this timing. + /// (Optional) The minimum threshold (in milliseconds) for saving this timing. + /// (Optional) Whether the children are included when comparing to the threshold. + public Timing(MiniProfiler profiler, Timing parent, string name, decimal? minSaveMs = null, bool? includeChildrenWithMinSave = false) + { + Id = Guid.NewGuid(); + Profiler = profiler; + Profiler.Head = this; + + // root will have no parent + parent?.AddChild(this); + + Name = name; + + _startTicks = profiler.ElapsedTicks; + _minSaveMs = minSaveMs; + _includeChildrenWithMinSave = includeChildrenWithMinSave == true; + StartMilliseconds = profiler.GetRoundedMilliseconds(_startTicks); + } + + /// + /// Gets or sets Unique identifier for this timing; set during construction. + /// + [DataMember(Order = 1)] + public Guid Id { get; set; } + + /// + /// Gets or sets Text displayed when this Timing is rendered. + /// + [DataMember(Order = 2)] + public string Name { get; set; } + + /// + /// Gets or sets How long this Timing step took in ms; includes any Timings' durations. + /// + [DataMember(Order = 3)] + public decimal? DurationMilliseconds { get; set; } + + /// + /// Gets or sets The offset from the start of profiling. + /// + [DataMember(Order = 4)] + public decimal StartMilliseconds { get; set; } + + /// + /// Gets or sets All sub-steps that occur within this Timing step. Add new children through + /// + [DataMember(Order = 5)] + public List Children { get; set; } + + /// + /// lists keyed by their type, e.g. "sql", "memcache", "redis", "http". + /// + [DataMember(Order = 6)] + public Dictionary> CustomTimings { get; set; } + + /// + /// Returns true when there exists any objects in this . + /// + public bool HasCustomTimings => CustomTimings?.Values.Any(v => v?.Count > 0) ?? false; + + /// + /// Gets or sets Which Timing this Timing is under - the duration that this step takes will be added to its parent's duration. + /// + /// This will be null for the root (initial) Timing. + [IgnoreDataMember] + public Timing ParentTiming { get; set; } + + /// + /// The Unique Identifier identifying the parent timing of this Timing. Used for sql server storage. + /// + [IgnoreDataMember] + public Guid ParentTimingId { get; set; } + + /// + /// Gets the elapsed milliseconds in this step without any children's durations. + /// + [IgnoreDataMember] + public decimal DurationWithoutChildrenMilliseconds + { + get + { + var result = DurationMilliseconds.GetValueOrDefault(); + + if (HasChildren) + { + foreach (var child in Children) + { + result -= child.DurationMilliseconds.GetValueOrDefault(); + } + } + + return Math.Round(result, 1); + } + } + + /// + /// Gets a value indicating whether this is less than the configured + /// , by default 2.0 ms. + /// + [IgnoreDataMember] + public bool IsTrivial => DurationMilliseconds <= 2.0M /*MiniProfiler.Settings.TrivialDurationThresholdMilliseconds*/; + + /// + /// Gets a value indicating whether this Timing has inner Timing steps. + /// + [IgnoreDataMember] + public bool HasChildren => Children?.Count > 0; + + /// + /// Gets a value indicating whether this Timing is the first one created in a MiniProfiler session. + /// + [IgnoreDataMember] + public bool IsRoot => Equals(Profiler.Root); + + /// + /// Gets a value indicating whether how far away this Timing is from the Profiler's Root. + /// + [IgnoreDataMember] + public short Depth + { + get + { + short result = 0; + var parent = ParentTiming; + + while (parent != null) + { + parent = parent.ParentTiming; + result++; + } + + return result; + } + } + + /// + /// Gets a reference to the containing profiler, allowing this Timing to affect the Head and get Stopwatch readings. + /// + internal MiniProfiler Profiler { get; set; } + + /// + /// The unique identifier used to identify the Profiler with which this Timing is associated. Used for sql storage. + /// + [IgnoreDataMember] + public Guid MiniProfilerId { get; set; } + + /// + /// Returns this Timing's Name. + /// + public override string ToString() => Name; + + /// + /// Returns true if Ids match. + /// + /// The to comare to. + public override bool Equals(object obj) + { + return obj is Timing && Id.Equals(((Timing)obj).Id); + } + + /// + /// Returns hash code of Id. + /// + public override int GetHashCode() => Id.GetHashCode(); + + /// + /// Completes this Timing's duration and sets the MiniProfiler's Head up one level. + /// + public void Stop() + { + if (DurationMilliseconds != null) return; + DurationMilliseconds = Profiler.GetDurationMilliseconds(_startTicks); + Profiler.Head = ParentTiming; + + if (_minSaveMs.HasValue && _minSaveMs.Value > 0 && ParentTiming != null) + { + var compareMs = _includeChildrenWithMinSave ? DurationMilliseconds : DurationWithoutChildrenMilliseconds; + if (compareMs < _minSaveMs.Value) + { + ParentTiming.RemoveChild(this); + } + } + } + + /// + /// Stops profiling, allowing the using construct to neatly encapsulate a region to be profiled. + /// + void IDisposable.Dispose() => Stop(); + + /// + /// Add the parameter 'timing' to this Timing's Children collection. + /// + /// The child to add. + /// + /// Used outside this assembly for custom deserialization when creating an implementation. + /// + public void AddChild(Timing timing) + { + Children = Children ?? new List(); + + Children.Add(timing); + timing.Profiler = timing.Profiler ?? Profiler; + timing.ParentTiming = this; + timing.ParentTimingId = Id; + if (Profiler != null) + timing.MiniProfilerId = Profiler.Id; + } + + internal void RemoveChild(Timing timing) => Children?.Remove(timing); + + /// + /// Adds to this step's dictionary of + /// custom timings, . Ensures that is created, + /// as well as the 's list. + /// + /// The kind of custom timing, e.g. "http", "redis", "memcache" + /// Duration and command information + public void AddCustomTiming(string category, CustomTiming customTiming) + { + GetCustomTimingList(category).Add(customTiming); + } + + internal void RemoveCustomTiming(string category, CustomTiming customTiming) + { + GetCustomTimingList(category).Remove(customTiming); + } + + private readonly object _lockObject = new object(); + + /// + /// Returns the list keyed to the , creating any collections when null. + /// + /// The kind of custom timings, e.g. "sql", "redis", "memcache" + private List GetCustomTimingList(string category) + { + lock (_lockObject) + { + CustomTimings = CustomTimings ?? new Dictionary>(); + } + + List result; + lock (CustomTimings) + { + if (!CustomTimings.TryGetValue(category, out result)) + { + result = new List(); + CustomTimings[category] = result; + } + } + return result; + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/ModelWithCommonTypes.cs b/tests/ServiceStack.Text.Benchmarks/ModelWithCommonTypes.cs new file mode 100644 index 000000000..0ae5ae3eb --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/ModelWithCommonTypes.cs @@ -0,0 +1,59 @@ +using System; + +namespace ServiceStack.Text.Benchmarks +{ + public class ModelWithCommonTypes + { + public char CharValue { get; set; } + + public byte ByteValue { get; set; } + + public sbyte SByteValue { get; set; } + + public short ShortValue { get; set; } + + public ushort UShortValue { get; set; } + + public int IntValue { get; set; } + + public uint UIntValue { get; set; } + + public long LongValue { get; set; } + + public ulong ULongValue { get; set; } + + public float FloatValue { get; set; } + + public double DoubleValue { get; set; } + + public decimal DecimalValue { get; set; } + + public DateTime DateTimeValue { get; set; } + + public TimeSpan TimeSpanValue { get; set; } + + public Guid GuidValue { get; set; } + + public static ModelWithCommonTypes Create(byte i) + { + return new ModelWithCommonTypes + { + ByteValue = i, + CharValue = (char)i, + DateTimeValue = new DateTime(2000, 1, 1 + i), + DecimalValue = i, + DoubleValue = i, + FloatValue = i, + IntValue = i, + LongValue = i, + SByteValue = (sbyte)i, + ShortValue = i, + TimeSpanValue = new TimeSpan(i), + UIntValue = i, + ULongValue = i, + UShortValue = i, + GuidValue = Guid.NewGuid(), + }; + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/ParseBuiltinsBenchmarks.cs b/tests/ServiceStack.Text.Benchmarks/ParseBuiltinsBenchmarks.cs new file mode 100644 index 000000000..96a90eae2 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/ParseBuiltinsBenchmarks.cs @@ -0,0 +1,45 @@ +using System; +using System.Globalization; +using BenchmarkDotNet.Attributes; +using ServiceStack.Text; + +namespace ServiceStack.Text.Benchmarks +{ + public class ParseBuiltinBenchmarks + { + const string int32_1 = "1234"; + const string int32_2 = "-1234"; + const string decimal_1 = "1234.5678"; + const string decimal_2 = "-1234.5678"; + const string decimal_3 = "1234.5678901234567890"; + const string decimal_4 = "-1234.5678901234567890"; + const string guid_1 = "{b6170a18-3dd7-4a9b-b5d6-21033b5ad162}"; + + [Benchmark] + public void Int32Parse() + { + var res1 = JsonSerializer.DeserializeFromString(int32_1); + var res2 = JsonSerializer.DeserializeFromString(int32_2); + } + + [Benchmark] + public void DecimalParse() + { + var res1 = JsonSerializer.DeserializeFromString(decimal_1); + var res2 = JsonSerializer.DeserializeFromString(decimal_2); + } + + [Benchmark] + public void BigDecimalParse() + { + var res1 = JsonSerializer.DeserializeFromString(decimal_3); + var res2 = JsonSerializer.DeserializeFromString(decimal_4); + } + + [Benchmark] + public void GuidParse() + { + var res1 = JsonSerializer.DeserializeFromString(guid_1); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/Program.cs b/tests/ServiceStack.Text.Benchmarks/Program.cs new file mode 100644 index 000000000..a039e7f77 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/Program.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Columns; +using BenchmarkDotNet.Attributes.Exporters; +using BenchmarkDotNet.Attributes.Jobs; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Validators; + +namespace ServiceStack.Text.Benchmarks +{ + public class Md5VsSha256 + { + private const int N = 10000; + private readonly byte[] data; + + private readonly SHA256 sha256 = SHA256.Create(); + private readonly MD5 md5 = MD5.Create(); + + public Md5VsSha256() + { + data = new byte[N]; + new Random(42).NextBytes(data); + } + + [Benchmark] + public byte[] Sha256() => sha256.ComputeHash(data); + + [Benchmark] + public byte[] Md5() => md5.ComputeHash(data); + } + + public class AllowNonOptimized : ManualConfig + { + public AllowNonOptimized() + { + Add(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS + + Add(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default + Add(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default + Add(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default + } + } + + public class Program + { + public static void Main(string[] args) + { +#if true + Summary summary; +// summary = BenchmarkRunner.Run(); +// summary = BenchmarkRunner.Run(); +// summary = BenchmarkRunner.Run(); +// summary = BenchmarkRunner.Run(); + summary = BenchmarkRunner.Run(); +#else + var test = new BookShelf10000BooksBenchmarks(); + test.Setup(); + for (var i = 0; i < 200; i++) + { +// test.DeserializeFromString(); + test.SerializeToString(); + } +#endif + } + } +} diff --git a/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj b/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj new file mode 100644 index 000000000..32aeb35ff --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj @@ -0,0 +1,16 @@ + + + Exe + net6.0 + + + + + + + + + + $(DefineConstants);NETSTANDARD;NETCORE;NETCORE2_1 + + \ No newline at end of file diff --git a/tests/ServiceStack.Text.Net40.Tests/DynamicJsonTests.cs b/tests/ServiceStack.Text.Net40.Tests/DynamicJsonTests.cs deleted file mode 100644 index fdab14e2a..000000000 --- a/tests/ServiceStack.Text.Net40.Tests/DynamicJsonTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -#if NET40 -using NUnit.Framework; - -namespace ServiceStack.Text.Tests -{ - [TestFixture] - public class DynamicJsonTests - { - [Test] - public void Can_serialize_dynamic_instance() - { - var dog = new { Name = "Spot" }; - var json = DynamicJson.Serialize(dog); - - Assert.IsNotNull(json); - json.Print(); - } - - [Test] - public void Can_deserialize_dynamic_instance() - { - var dog = new { Name = "Spot" }; - var json = DynamicJson.Serialize(dog); - var deserialized = DynamicJson.Deserialize(json); - - Assert.IsNotNull(deserialized); - Assert.AreEqual(dog.Name, deserialized.Name); - } - } -} -#endif diff --git a/tests/ServiceStack.Text.Net40.Tests/Properties/AssemblyInfo.cs b/tests/ServiceStack.Text.Net40.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 47f79534f..000000000 --- a/tests/ServiceStack.Text.Net40.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Text.Net40.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("ServiceStack.Text.Net40.Tests")] -[assembly: AssemblyCopyright("Copyright © Microsoft 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("3a61bec0-ce86-4ba8-858b-cc724a4c3408")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/ServiceStack.Text.Net40.Tests/ServiceStack.Text.Net40.Tests.csproj b/tests/ServiceStack.Text.Net40.Tests/ServiceStack.Text.Net40.Tests.csproj deleted file mode 100644 index ea0a4edd0..000000000 --- a/tests/ServiceStack.Text.Net40.Tests/ServiceStack.Text.Net40.Tests.csproj +++ /dev/null @@ -1,338 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {BAE532C7-F01D-4ADC-A0A9-A104F4C2F3BD} - Library - Properties - ServiceStack.Text.Tests - ServiceStack.Text.Tests - v4.0 - 512 - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET40 - prompt - 4 - x86 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - ..\..\lib\tests\Northwind.Common.dll - - - False - ..\..\lib\tests\nunit.framework.dll - - - ..\..\lib\tests\ServiceStack.dll - - - ..\..\lib\tests\ServiceStack.Client.dll - - - ..\..\lib\tests\ServiceStack.Common.dll - - - ..\..\lib\tests\ServiceStack.Common.Tests.dll - - - ..\..\lib\tests\ServiceStack.Interfaces.dll - - - ..\..\lib\tests\ServiceStack.ServiceInterface.dll - - - - - - - - - - - - - - - AdhocModelTests.cs - - - AnonymousTypes.cs - - - AotTests.cs - - - BasicStringSerializerTests.cs - - - BclStructTests.cs - - - CsvSerializerTests.cs - - - CsvStreamTests.cs - - - CsvTests\CustomHeaderTests.cs - - - CsvTests\DictionaryTests.cs - - - CsvTests\NewLineTests.cs - - - CultureInfoTests.cs - - - CustomStructTests.cs - - - CyclicalDependencyTests.cs - - - DataContractTests.cs - - - DataTests.cs - - - DateTimeOffsetAndTimeSpanTests.cs - - - DdnContentIngestTests.cs - - - DdnDtoTests.cs - - - DictionaryTests.cs - - - DynamicModels\ComplexObjectGraphTest.cs - - - DynamicModels\DataModel\CustomCollection.cs - - - DynamicModels\DataModel\CustomCollectionDto.cs - - - DynamicModels\DataModel\CustomCollectionItem.cs - - - DynamicModels\DataModel\CustomException.cs - - - DynamicModels\DataModel\DataContainer.cs - - - DynamicModels\DataModel\DataContainerBase.cs - - - DynamicModels\DataModel\DynamicType.cs - - - DynamicModels\DataModel\ObjectGraph.cs - - - DynamicModels\DataModel\StrictType.cs - - - DynamicModels\DynamicMessageTests.cs - - - DynamicModels\GoogleMapsApiTests.cs - - - DynamicModels\ModelWithAllTypes.cs - - - DynamicObjectTests.cs - - - GenericCollectionTests.cs - - - InterfaceTests.cs - - - JsonObjectTests.cs - - - JsonTests\AnonymousDeserializationTests.cs - - - JsonTests\BackingFieldTests.cs - - - JsonTests\BasicJsonTests.cs - - - JsonTests\BasicPropertiesTests.cs - - - JsonTests\CamelCaseTests.cs - - - JsonTests\ContractByInterfaceTests.cs - - - JsonTests\CustomSerializerTests.cs - - - JsonTests\DictionaryTests.cs - - - JsonTests\EscapedCharsTests.cs - - - JsonTests\JsonArrayObjectTests.cs - - - JsonTests\JsonDataContractCompatibilityTests.cs - - - JsonTests\JsonDateTimeTests.cs - - - JsonTests\JsonObjectTests.cs - - - JsonTests\ModelWithAllTypesTests.cs - - - JsonTests\PolymorphicListTests.cs - - - JsonTests\ThrowOnDeserializeErrorTest.cs - - - JsvTests\JsvDeserializeTypeTest.cs - - - JsvTests\TypeSerializerToStringDictionaryTests.cs - - - MessagingTests.cs - - - NullableTypesTests.cs - - - QueryStringSerializerTests.cs - - - QueryStringWriterTests.cs - - - ReflectionExtensionTests.cs - - - ReportedIssues.cs - - - SpecialTypesTests.cs - - - StringConverterUtilsTests.cs - - - StringExtensionsTests.cs - - - StringSerializerNorthwindDatabaseTests.cs - - - StringSerializerTests.cs - - - StringSerializerTranslationTests.cs - - - StringTests.cs - - - StructTests.cs - - - Support\BenchmarkTests.cs - - - Support\DdnDtos.cs - - - Support\MovieDtos.cs - - - Support\TableItem.cs - - - SystemTimeTests.cs - - - TestBase.cs - - - TypeConverterTests.cs - - - UseCases\CentroidTests.cs - - - UseCases\GitHubRestTests.cs - - - UseCases\GithubV3ApiTests.cs - - - UseCases\GMapDirectionsTests.cs - - - Utils\DateTimeSerializerTests.cs - - - Utils\JsvFormatterTests.cs - - - XmlSerializerTests.cs - - - - - - - - {15BF26C8-92F8-445D-8FFC-7882A519B67D} - ServiceStack.Text.Net40 - - - - - - \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests.MonoTouch/Aot.cs b/tests/ServiceStack.Text.Tests.MonoTouch/Aot.cs deleted file mode 100644 index 7b119e6f4..000000000 --- a/tests/ServiceStack.Text.Tests.MonoTouch/Aot.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using MonoTouch.Foundation; - -namespace ServiceStack.Text.Tests -{ - public static class Aot - { - public static void Init () - { - JsConfig.RegisterForAot (); - - JsConfig.RegisterTypeForAot (); - - JsConfig.RegisterTypeForAot> (); - JsConfig.RegisterTypeForAot> (); - - JsConfig.RegisterTypeForAot> (); - JsConfig.RegisterTypeForAot> (); - } - } -} - diff --git a/tests/ServiceStack.Text.Tests.MonoTouch/AppDelegate.cs b/tests/ServiceStack.Text.Tests.MonoTouch/AppDelegate.cs deleted file mode 100644 index dfcfb701f..000000000 --- a/tests/ServiceStack.Text.Tests.MonoTouch/AppDelegate.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using MonoTouch.Foundation; -using MonoTouch.UIKit; -using MonoTouch.NUnit.UI; - -namespace ServiceStack.Text.Tests.MonoTouch -{ - // The UIApplicationDelegate for the application. This class is responsible for launching the - // User Interface of the application, as well as listening (and optionally responding) to - // application events from iOS. - [Register ("AppDelegate")] - public partial class AppDelegate : UIApplicationDelegate - { - // class-level declarations - UIWindow window; - TouchRunner runner; - - // - // This method is invoked when the application has loaded and is ready to run. In this - // method you should instantiate the window, load the UI into it and then make the window - // visible. - // - // You have 17 seconds to return from this method, or iOS will terminate your application. - // - public override bool FinishedLaunching (UIApplication app, NSDictionary options) - { - Aot.Init(); - - // create a new window instance based on the screen size - window = new UIWindow (UIScreen.MainScreen.Bounds); - runner = new TouchRunner (window); - - // register every tests included in the main application/assembly - runner.Add (System.Reflection.Assembly.GetExecutingAssembly ()); - - window.RootViewController = new UINavigationController (runner.GetViewController ()); - - // make the window visible - window.MakeKeyAndVisible (); - - return true; - } - } -} - diff --git a/tests/ServiceStack.Text.Tests.MonoTouch/Info.plist b/tests/ServiceStack.Text.Tests.MonoTouch/Info.plist deleted file mode 100644 index 4d114089c..000000000 --- a/tests/ServiceStack.Text.Tests.MonoTouch/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - MinimumOSVersion - 3.2 - UIDeviceFamily - - 1 - 2 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/tests/ServiceStack.Text.Tests.MonoTouch/Main.cs b/tests/ServiceStack.Text.Tests.MonoTouch/Main.cs deleted file mode 100644 index 85ea6f9cc..000000000 --- a/tests/ServiceStack.Text.Tests.MonoTouch/Main.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using MonoTouch.Foundation; -using MonoTouch.UIKit; - -namespace ServiceStack.Text.Tests.MonoTouch -{ - public class Application - { - // This is the main entry point of the application. - static void Main (string[] args) - { - // if you want to use a different Application Delegate class from "AppDelegate" - // you can specify it here. - UIApplication.Main (args, null, "AppDelegate"); - } - } -} diff --git a/tests/ServiceStack.Text.Tests.MonoTouch/ServiceStack.Text.Tests.MonoTouch.csproj b/tests/ServiceStack.Text.Tests.MonoTouch/ServiceStack.Text.Tests.MonoTouch.csproj deleted file mode 100644 index e89754d68..000000000 --- a/tests/ServiceStack.Text.Tests.MonoTouch/ServiceStack.Text.Tests.MonoTouch.csproj +++ /dev/null @@ -1,266 +0,0 @@ - - - - Debug - iPhoneSimulator - 10.0.0 - 2.0 - {C7844556-6217-4D62-868F-9FF8968401BA} - {6BC8ED88-2882-458C-8E55-DFD12B67127B};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Exe - ServiceStack.Text.Tests - Resources - ServiceStackTextTestsMonoTouch - - - True - full - False - bin\iPhoneSimulator\Debug - DEBUG;MONOTOUCH; - prompt - 4 - False - True - True - None - - - none - True - bin\iPhoneSimulator\Release - prompt - 4 - False - None - MONOTOUCH; - - - True - full - False - bin\iPhone\Debug - DEBUG;MONOTOUCH - prompt - 4 - False - iPhone Developer - True - True - - ARMv6 - -aot "ntrampolines=2048" -aot "nrgctx-trampolines=2048" -aot "nimt-trampolines=1024" -nosymbolstrip -nostrip - - - none - True - bin\iPhone\Release - prompt - 4 - False - iPhone Developer - MONOTOUCH - - ARMv6 - - -aot "ntrampolines=2048" -aot "nrgctx-trampolines=2048" -aot "nimt-trampolines=1024" -nosymbolstrip -nostrip - - - none - True - bin\iPhone\Ad-Hoc - prompt - 4 - False - True - iPhone Distribution - - ARMv6 - - - none - True - bin\iPhone\AppStore - prompt - 4 - False - iPhone Distribution - - ARMv6 - - - - - - - - - - - - AdhocModelTests.cs.orig - - - ServiceStack.Text.Tests.csproj - - - - - - - JsonTests\BackingFieldTests.cs - - - JsonTests\BasicJsonTests.cs - - - JsonTests\BasicPropertiesTests.cs - - - JsonTests\JsonArrayObjectTests.cs - - - JsonTests\JsonDateTimeTests.cs - - - JsonTests\JsonObjectTests.cs - - - JsonTests\ModelWithAllTypesTests.cs - - - JsonTests\PolymorphicInstanceTest.cs - - - JsonTests\PolymorphicListTests.cs - - - JsonTests\PropertyConventionTests.cs - - - TestBase.cs - - - DynamicModels\ComplexObjectGraphTest.cs - - - DynamicModels\DynamicMessageTests.cs - - - DynamicModels\GoogleMapsApiTests.cs - - - DynamicModels\ModelWithAllTypes.cs - - - DynamicModels\DataModel\CustomCollection.cs - - - DynamicModels\DataModel\CustomCollectionDto.cs - - - DynamicModels\DataModel\CustomCollectionItem.cs - - - DynamicModels\DataModel\CustomException.cs - - - DynamicModels\DataModel\DataContainer.cs - - - DynamicModels\DataModel\DataContainerBase.cs - - - DynamicModels\DataModel\DynamicType.cs - - - DynamicModels\DataModel\ObjectGraph.cs - - - DynamicModels\DataModel\StrictType.cs - - - - Utils\DateTimeSerializerTests.cs - - - JsvTests\JsvDeserializeTypeTest.cs - - - AnonymousTypes.cs - - - BasicStringSerializerTests.cs - - - CultureInfoTests.cs - - - CustomStructTests.cs - - - CyclicalDependencyTests.cs - - - DateTimeOffsetAndTimeSpanTests.cs - - - DictionaryTests.cs - - - DynamicObjectTests.cs - - - JsonObjectTests.cs - - - NullableTypesTests.cs - - - QueryStringSerializerTests.cs - - - ReflectionExtensionTests.cs - - - ReportedIssues.cs - - - SpecialTypesTests.cs - - - StringConverterUtilsTests.cs - - - StringExtensionsTests.cs - - - StringSerializerTests.cs - - - StringSerializerTranslationTests.cs - - - StructTests.cs - - - SystemTimeTests.cs - - - TypeConverterTests.cs - - - - - - {1137B5AC-2259-413C-A473-93721D2A7551} - ServiceStack.Text.MonoTouch - - - - - - - - - \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AdhocModelTests.cs b/tests/ServiceStack.Text.Tests/AdhocModelTests.cs index 213a86127..ca1ae00c9 100644 --- a/tests/ServiceStack.Text.Tests/AdhocModelTests.cs +++ b/tests/ServiceStack.Text.Tests/AdhocModelTests.cs @@ -2,571 +2,800 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; -using System.Globalization; using System.Runtime.Serialization; -using System.Threading; +using System.Xml; using NUnit.Framework; -using ServiceStack.Common.Extensions; +using ServiceStack.Text.Common; using ServiceStack.Text.Jsv; namespace ServiceStack.Text.Tests { - [TestFixture] - public class AdhocModelTests - : TestBase - { - public enum FlowPostType - { - Content, - Text, - Promo, - } - - public class FlowPostTransient - { - public FlowPostTransient() - { - this.TrackUrns = new List(); - } - - public long Id { get; set; } - - public string Urn { get; set; } - - public Guid UserId { get; set; } - - public DateTime DateAdded { get; set; } - - public DateTime DateModified { get; set; } - - public Guid? TargetUserId { get; set; } - - public long? ForwardedPostId { get; set; } - - public Guid OriginUserId { get; set; } - - public string OriginUserName { get; set; } - - public Guid SourceUserId { get; set; } - - public string SourceUserName { get; set; } - - public string SubjectUrn { get; set; } - - public string ContentUrn { get; set; } - - public IList TrackUrns { get; set; } - - public string Caption { get; set; } - - public Guid CaptionUserId { get; set; } - - public string CaptionSourceName { get; set; } - - public string ForwardedPostUrn { get; set; } - - public FlowPostType PostType { get; set; } - - public Guid? OnBehalfOfUserId { get; set; } - - public static FlowPostTransient Create() - { - return new FlowPostTransient { - Caption = "Caption", - CaptionSourceName = "CaptionSourceName", - CaptionUserId = Guid.NewGuid(), - ContentUrn = "ContentUrn", - DateAdded = DateTime.Now, - DateModified = DateTime.Now, - ForwardedPostId = 1, - ForwardedPostUrn = "ForwardedPostUrn", - Id = 1, - OnBehalfOfUserId = Guid.NewGuid(), - OriginUserId = Guid.NewGuid(), - OriginUserName = "OriginUserName", - PostType = FlowPostType.Content, - SourceUserId = Guid.NewGuid(), - SourceUserName = "SourceUserName", - SubjectUrn = "SubjectUrn ", - TargetUserId = Guid.NewGuid(), - TrackUrns = new List { "track1", "track2" }, - Urn = "Urn ", - UserId = Guid.NewGuid(), - }; - } - - public bool Equals(FlowPostTransient other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return other.Id == Id && Equals(other.Urn, Urn) && other.UserId.Equals(UserId) && other.DateAdded.RoundToMs().Equals(DateAdded.RoundToMs()) && other.DateModified.RoundToMs().Equals(DateModified.RoundToMs()) && other.TargetUserId.Equals(TargetUserId) && other.ForwardedPostId.Equals(ForwardedPostId) && other.OriginUserId.Equals(OriginUserId) && Equals(other.OriginUserName, OriginUserName) && other.SourceUserId.Equals(SourceUserId) && Equals(other.SourceUserName, SourceUserName) && Equals(other.SubjectUrn, SubjectUrn) && Equals(other.ContentUrn, ContentUrn) && TrackUrns.EquivalentTo(other.TrackUrns) && Equals(other.Caption, Caption) && other.CaptionUserId.Equals(CaptionUserId) && Equals(other.CaptionSourceName, CaptionSourceName) && Equals(other.ForwardedPostUrn, ForwardedPostUrn) && Equals(other.PostType, PostType) && other.OnBehalfOfUserId.Equals(OnBehalfOfUserId); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(FlowPostTransient)) return false; - return Equals((FlowPostTransient)obj); - } - - public override int GetHashCode() - { - unchecked - { - int result = Id.GetHashCode(); - result = (result * 397) ^ (Urn != null ? Urn.GetHashCode() : 0); - result = (result * 397) ^ UserId.GetHashCode(); - result = (result * 397) ^ DateAdded.GetHashCode(); - result = (result * 397) ^ DateModified.GetHashCode(); - result = (result * 397) ^ (TargetUserId.HasValue ? TargetUserId.Value.GetHashCode() : 0); - result = (result * 397) ^ (ForwardedPostId.HasValue ? ForwardedPostId.Value.GetHashCode() : 0); - result = (result * 397) ^ OriginUserId.GetHashCode(); - result = (result * 397) ^ (OriginUserName != null ? OriginUserName.GetHashCode() : 0); - result = (result * 397) ^ SourceUserId.GetHashCode(); - result = (result * 397) ^ (SourceUserName != null ? SourceUserName.GetHashCode() : 0); - result = (result * 397) ^ (SubjectUrn != null ? SubjectUrn.GetHashCode() : 0); - result = (result * 397) ^ (ContentUrn != null ? ContentUrn.GetHashCode() : 0); - result = (result * 397) ^ (TrackUrns != null ? TrackUrns.GetHashCode() : 0); - result = (result * 397) ^ (Caption != null ? Caption.GetHashCode() : 0); - result = (result * 397) ^ CaptionUserId.GetHashCode(); - result = (result * 397) ^ (CaptionSourceName != null ? CaptionSourceName.GetHashCode() : 0); - result = (result * 397) ^ (ForwardedPostUrn != null ? ForwardedPostUrn.GetHashCode() : 0); - result = (result * 397) ^ PostType.GetHashCode(); - result = (result * 397) ^ (OnBehalfOfUserId.HasValue ? OnBehalfOfUserId.Value.GetHashCode() : 0); - return result; - } - } - } - - [SetUp] - public void SetUp() - { - JsConfig.Reset(); - } - - [Test] - public void Can_Deserialize_text() - { - var dtoString = "[{Id:1,Urn:urn:post:3a944f18-920c-498a-832d-cf38fed3d0d7/1,UserId:3a944f18920c498a832dcf38fed3d0d7,DateAdded:2010-02-17T12:04:45.2845615Z,DateModified:2010-02-17T12:04:45.2845615Z,OriginUserId:3a944f18920c498a832dcf38fed3d0d7,OriginUserName:testuser1,SourceUserId:3a944f18920c498a832dcf38fed3d0d7,SourceUserName:testuser1,SubjectUrn:urn:track:1,ContentUrn:urn:track:1,TrackUrns:[],CaptionUserId:3a944f18920c498a832dcf38fed3d0d7,CaptionSourceName:testuser1,PostType:Content}]"; - var fromString = TypeSerializer.DeserializeFromString>(dtoString); - } - - [Test] - public void Can_Serialize_single_FlowPostTransient() - { - var dto = FlowPostTransient.Create(); - SerializeAndCompare(dto); - } - - [Test] - public void Can_serialize_jsv_dates() - { - var now = DateTime.Now; - - var jsvDate = TypeSerializer.SerializeToString(now); - var fromJsvDate = TypeSerializer.DeserializeFromString(jsvDate); - Assert.That(fromJsvDate, Is.EqualTo(now)); - } - - [Test] - public void Can_serialize_json_dates() - { - var now = DateTime.Now; - - var jsonDate = JsonSerializer.SerializeToString(now); - var fromJsonDate = JsonSerializer.DeserializeFromString(jsonDate); - - Assert.That(fromJsonDate.RoundToMs(), Is.EqualTo(now.RoundToMs())); - } - - [Test] - public void Can_Serialize_multiple_FlowPostTransient() - { - var dtos = new List { - FlowPostTransient.Create(), - FlowPostTransient.Create() - }; - Serialize(dtos); - } - - [DataContract] - public class TestObject - { - [DataMember] - public string Value { get; set; } - public TranslatedString ValueNoMember { get; set; } - - public bool Equals(TestObject other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other.Value, Value); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(TestObject)) return false; - return Equals((TestObject)obj); - } - - public override int GetHashCode() - { - return (Value != null ? Value.GetHashCode() : 0); - } - } - - public class Test - { - public string Val { get; set; } - } - - public class TestResponse - { - public TestObject Result { get; set; } - } - - public class TranslatedString : ListDictionary - { - public string CurrentLanguage { get; set; } - - public string Value - { - get - { - if (this.Contains(CurrentLanguage)) - return this[CurrentLanguage] as string; - return null; - } - set - { - if (this.Contains(CurrentLanguage)) - this[CurrentLanguage] = value; - else - Add(CurrentLanguage, value); - } - } - - public TranslatedString() - { - CurrentLanguage = "en"; - } - - public static void SetLanguageOnStrings(string lang, params TranslatedString[] strings) - { - foreach (TranslatedString str in strings) - str.CurrentLanguage = lang; - } - } - - [Test] - public void Should_ignore_non_DataMember_TranslatedString() - { - var dto = new TestObject { - Value = "value", - ValueNoMember = new TranslatedString - { - {"key1", "val1"}, - {"key2", "val2"}, - } - }; - SerializeAndCompare(dto); - } - - public interface IParent - { - int Id { get; set; } - string ParentName { get; set; } - } - - public class Parent : IParent - { - public int Id { get; set; } - public string ParentName { get; set; } - public Child Child { get; set; } - } - - public class Child - { - public int Id { get; set; } - public string ChildName { get; set; } - public IParent Parent { get; set; } - } - - [Test] - public void Can_Serialize_Cyclical_Dependency_via_interface() - { - JsConfig.PreferInterfaces = true; - - var dto = new Parent { - Id = 1, - ParentName = "Parent", - Child = new Child { Id = 2, ChildName = "Child" } - }; - dto.Child.Parent = dto; + [TestFixture] + public class AdhocModelTests + : TestBase + { + public enum FlowPostType + { + Content, + Text, + Promo, + } + + public class FlowPostTransient + { + public FlowPostTransient() + { + this.TrackUrns = new List(); + } + + public long Id { get; set; } + + public string Urn { get; set; } + + public Guid UserId { get; set; } - var fromDto = Serialize(dto, includeXml: false); + public DateTime DateAdded { get; set; } - var parent = (IParent)fromDto.Child.Parent; - Assert.That(parent.Id, Is.EqualTo(dto.Id)); - Assert.That(parent.ParentName, Is.EqualTo(dto.ParentName)); - } + public DateTime DateModified { get; set; } - public class Exclude - { - public int Id { get; set; } - public string Key { get; set; } - } + public Guid? TargetUserId { get; set; } - [Test] - public void Can_exclude_properties() - { - JsConfig.ExcludePropertyNames = new[] { "Id" }; + public long? ForwardedPostId { get; set; } - var dto = new Exclude { Id = 1, Key = "Value" }; + public Guid OriginUserId { get; set; } - Assert.That(dto.ToJson(), Is.EqualTo("{\"Key\":\"Value\"}")); - Assert.That(dto.ToJsv(), Is.EqualTo("{Key:Value}")); - } + public string OriginUserName { get; set; } - public class HasIndex - { - public int Id { get; set; } + public Guid SourceUserId { get; set; } - public int this[int id] - { - get { return Id; } - set { Id = value; } - } - } + public string SourceUserName { get; set; } - [Test] + public string SubjectUrn { get; set; } + + public string ContentUrn { get; set; } + + public IList TrackUrns { get; set; } + + public string Caption { get; set; } + + public Guid CaptionUserId { get; set; } + + public string CaptionSourceName { get; set; } + + public string ForwardedPostUrn { get; set; } + + public FlowPostType PostType { get; set; } + + public Guid? OnBehalfOfUserId { get; set; } + + public static FlowPostTransient Create() + { + return new FlowPostTransient + { + Caption = "Caption", + CaptionSourceName = "CaptionSourceName", + CaptionUserId = Guid.NewGuid(), + ContentUrn = "ContentUrn", + DateAdded = DateTime.Now, + DateModified = DateTime.Now, + ForwardedPostId = 1, + ForwardedPostUrn = "ForwardedPostUrn", + Id = 1, + OnBehalfOfUserId = Guid.NewGuid(), + OriginUserId = Guid.NewGuid(), + OriginUserName = "OriginUserName", + PostType = FlowPostType.Content, + SourceUserId = Guid.NewGuid(), + SourceUserName = "SourceUserName", + SubjectUrn = "SubjectUrn ", + TargetUserId = Guid.NewGuid(), + TrackUrns = new List { "track1", "track2" }, + Urn = "Urn ", + UserId = Guid.NewGuid(), + }; + } + + public bool Equals(FlowPostTransient other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other.Id == Id + && Equals(other.Urn, Urn) + && other.UserId.Equals(UserId) + && other.DateAdded.RoundToMs().Equals(DateAdded.RoundToMs()) + && other.DateModified.RoundToMs().Equals(DateModified.RoundToMs()) + && other.TargetUserId.Equals(TargetUserId) + && other.ForwardedPostId.Equals(ForwardedPostId) + && other.OriginUserId.Equals(OriginUserId) + && Equals(other.OriginUserName, OriginUserName) + && other.SourceUserId.Equals(SourceUserId) + && Equals(other.SourceUserName, SourceUserName) + && Equals(other.SubjectUrn, SubjectUrn) + && Equals(other.ContentUrn, ContentUrn) + && TrackUrns.EquivalentTo(other.TrackUrns) + && Equals(other.Caption, Caption) + && other.CaptionUserId.Equals(CaptionUserId) + && Equals(other.CaptionSourceName, CaptionSourceName) + && Equals(other.ForwardedPostUrn, ForwardedPostUrn) + && Equals(other.PostType, PostType) + && other.OnBehalfOfUserId.Equals(OnBehalfOfUserId); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(FlowPostTransient)) return false; + return Equals((FlowPostTransient)obj); + } + + public override int GetHashCode() + { + unchecked + { + int result = Id.GetHashCode(); + result = (result * 397) ^ (Urn != null ? Urn.GetHashCode() : 0); + result = (result * 397) ^ UserId.GetHashCode(); + result = (result * 397) ^ DateAdded.GetHashCode(); + result = (result * 397) ^ DateModified.GetHashCode(); + result = (result * 397) ^ (TargetUserId.HasValue ? TargetUserId.Value.GetHashCode() : 0); + result = (result * 397) ^ (ForwardedPostId.HasValue ? ForwardedPostId.Value.GetHashCode() : 0); + result = (result * 397) ^ OriginUserId.GetHashCode(); + result = (result * 397) ^ (OriginUserName != null ? OriginUserName.GetHashCode() : 0); + result = (result * 397) ^ SourceUserId.GetHashCode(); + result = (result * 397) ^ (SourceUserName != null ? SourceUserName.GetHashCode() : 0); + result = (result * 397) ^ (SubjectUrn != null ? SubjectUrn.GetHashCode() : 0); + result = (result * 397) ^ (ContentUrn != null ? ContentUrn.GetHashCode() : 0); + result = (result * 397) ^ (TrackUrns != null ? TrackUrns.GetHashCode() : 0); + result = (result * 397) ^ (Caption != null ? Caption.GetHashCode() : 0); + result = (result * 397) ^ CaptionUserId.GetHashCode(); + result = (result * 397) ^ (CaptionSourceName != null ? CaptionSourceName.GetHashCode() : 0); + result = (result * 397) ^ (ForwardedPostUrn != null ? ForwardedPostUrn.GetHashCode() : 0); + result = (result * 397) ^ PostType.GetHashCode(); + result = (result * 397) ^ (OnBehalfOfUserId.HasValue ? OnBehalfOfUserId.Value.GetHashCode() : 0); + return result; + } + } + } + + [SetUp] + public void SetUp() + { + JsConfig.Reset(); + } + + [Test] + public void Can_Deserialize_text() + { + var dtoString = "[{Id:1,Urn:urn:post:3a944f18-920c-498a-832d-cf38fed3d0d7/1,UserId:3a944f18920c498a832dcf38fed3d0d7,DateAdded:2010-02-17T12:04:45.2845615Z,DateModified:2010-02-17T12:04:45.2845615Z,OriginUserId:3a944f18920c498a832dcf38fed3d0d7,OriginUserName:testuser1,SourceUserId:3a944f18920c498a832dcf38fed3d0d7,SourceUserName:testuser1,SubjectUrn:urn:track:1,ContentUrn:urn:track:1,TrackUrns:[],CaptionUserId:3a944f18920c498a832dcf38fed3d0d7,CaptionSourceName:testuser1,PostType:Content}]"; + var fromString = TypeSerializer.DeserializeFromString>(dtoString); + } + + [Test] + public void Can_Serialize_single_FlowPostTransient() + { + var dto = FlowPostTransient.Create(); + SerializeAndCompare(dto); + } + + [Test] + public void Can_serialize_jsv_dates() + { + var now = DateTime.Now; + + var jsvDate = TypeSerializer.SerializeToString(now); + var fromJsvDate = TypeSerializer.DeserializeFromString(jsvDate); + Assert.That(fromJsvDate, Is.EqualTo(now)); + } + + [Test] + public void Can_serialize_json_dates() + { + var now = DateTime.Now; + + var jsonDate = JsonSerializer.SerializeToString(now); + var fromJsonDate = JsonSerializer.DeserializeFromString(jsonDate); + + Assert.That(fromJsonDate.RoundToMs(), Is.EqualTo(now.RoundToMs())); + } + + [Test] + public void Can_Serialize_multiple_FlowPostTransient() + { + var dtos = new List { + FlowPostTransient.Create(), + FlowPostTransient.Create() + }; + Serialize(dtos); + } + + [DataContract] + public class TestObject + { + [DataMember] + public string Value { get; set; } + public TranslatedString ValueNoMember { get; set; } + + public bool Equals(TestObject other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(other.Value, Value); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(TestObject)) return false; + return Equals((TestObject)obj); + } + + public override int GetHashCode() + { + return (Value != null ? Value.GetHashCode() : 0); + } + } + + public class Test + { + public string Val { get; set; } + } + + public class TestResponse + { + public TestObject Result { get; set; } + } + + public class TranslatedString : ListDictionary + { + public string CurrentLanguage { get; set; } + + public string Value + { + get + { + if (this.Contains(CurrentLanguage)) + return this[CurrentLanguage] as string; + return null; + } + set + { + if (this.Contains(CurrentLanguage)) + this[CurrentLanguage] = value; + else + Add(CurrentLanguage, value); + } + } + + public TranslatedString() + { + CurrentLanguage = "en"; + } + + public static void SetLanguageOnStrings(string lang, params TranslatedString[] strings) + { + foreach (TranslatedString str in strings) + str.CurrentLanguage = lang; + } + } + + [Test] + public void Should_ignore_non_DataMember_TranslatedString() + { + var dto = new TestObject + { + Value = "value", + ValueNoMember = new TranslatedString + { + {"key1", "val1"}, + {"key2", "val2"}, + } + }; + SerializeAndCompare(dto); + } + + public interface IParent + { + int Id { get; set; } + string ParentName { get; set; } + } + + public class Parent : IParent + { + public int Id { get; set; } + public string ParentName { get; set; } + public Child Child { get; set; } + } + + public class Child + { + public int Id { get; set; } + public string ChildName { get; set; } + public IParent Parent { get; set; } + } + + [Test] + public void Can_Serialize_Cyclical_Dependency_via_interface() + { + JsConfig.PreferInterfaces = true; + + var dto = new Parent + { + Id = 1, + ParentName = "Parent", + Child = new Child { Id = 2, ChildName = "Child" } + }; + dto.Child.Parent = dto; + + var fromDto = Serialize(dto, includeXml: false); + + var parent = (IParent)fromDto.Child.Parent; + Assert.That(parent.Id, Is.EqualTo(dto.Id)); + Assert.That(parent.ParentName, Is.EqualTo(dto.ParentName)); + } + + public class Exclude + { + public int Id { get; set; } + public string Key { get; set; } + } + + [Test] + public void Can_exclude_properties() + { + JsConfig.ExcludePropertyNames = new[] { "Id" }; + + var dto = new Exclude { Id = 1, Key = "Value" }; + + Assert.That(dto.ToJson(), Is.EqualTo("{\"Key\":\"Value\"}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Key:Value}")); + } + + [Test] + public void Can_exclude_properties_scoped() + { + var dto = new Exclude { Id = 1, Key = "Value" }; + using (var config = JsConfig.BeginScope()) + { + config.ExcludePropertyReferences = new[] { "Exclude.Id" }; + Assert.That(dto.ToJson(), Is.EqualTo("{\"Key\":\"Value\"}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Key:Value}")); + } + + using (JsConfig.With(new Config { ExcludePropertyReferences = new[] { "Exclude.Id" }})) + { + Assert.That(dto.ToJson(), Is.EqualTo("{\"Key\":\"Value\"}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Key:Value}")); + } + } + + public class IncludeExclude + { + public int Id { get; set; } + public string Name { get; set; } + public Exclude Obj { get; set; } + } + + [Test] + public void Can_include_nested_only() + { + var dto = new IncludeExclude + { + Id = 1234, + Name = "TEST", + Obj = new Exclude + { + Id = 1, + Key = "Value" + } + }; + + using (var config = JsConfig.BeginScope()) + { + config.ExcludePropertyReferences = new[] { "Exclude.Id", "IncludeExclude.Id", "IncludeExclude.Name" }; + Assert.That(dto.ToJson(), Is.EqualTo("{\"Obj\":{\"Key\":\"Value\"}}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Obj:{Key:Value}}")); + } + Assert.That(JsConfig.ExcludePropertyReferences, Is.EqualTo(null)); + + } + + [Test] + public void Exclude_all_nested() + { + var dto = new IncludeExclude + { + Id = 1234, + Name = "TEST", + Obj = new Exclude + { + Id = 1, + Key = "Value" + } + }; + + using (var config = JsConfig.BeginScope()) + { + config.ExcludePropertyReferences = new[] { "Exclude.Id", "Exclude.Key" }; + Assert.AreEqual(2, config.ExcludePropertyReferences.Length); + + var actual = dto.ToJson(); + Assert.That(actual, Is.EqualTo("{\"Id\":1234,\"Name\":\"TEST\",\"Obj\":{}}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Id:1234,Name:TEST,Obj:{}}")); + } + } + + public class ExcludeList + { + public int Id { get; set; } + public List Excludes { get; set; } + } + + [Test] + public void Exclude_List_Scope() + { + var dto = new ExcludeList + { + Id = 1234, + Excludes = new List() { + new Exclude { + Id = 2345, + Key = "Value" + }, + new Exclude { + Id = 3456, + Key = "Value" + } + } + }; + using (var config = JsConfig.BeginScope()) + { + config.ExcludePropertyReferences = new[] { "ExcludeList.Id", "Exclude.Id" }; + Assert.That(dto.ToJson(), Is.EqualTo("{\"Excludes\":[{\"Key\":\"Value\"},{\"Key\":\"Value\"}]}")); + Assert.That(dto.ToJsv(), Is.EqualTo("{Excludes:[{Key:Value},{Key:Value}]}")); + } + } + + public class HasIndex + { + public int Id { get; set; } + + public int this[int id] + { + get { return Id; } + set { Id = value; } + } + } + + [Test] public void Can_serialize_type_with_indexer() - { - var dto = new HasIndex { Id = 1 }; - Serialize(dto); - } - - public struct Size - { - public Size(string value) - { - var parts = value.Split(','); - this.Width = parts[0]; - this.Height = parts[1]; - } - - public Size(string width, string height) - { - Width = width; - Height = height; - } - - public string Width; - public string Height; - - public override string ToString() - { - return this.Width + "," + this.Height; - } - } - - [Test] - public void Can_serialize_struct_in_list() - { - var structs = new[] { + { + var dto = new HasIndex { Id = 1 }; + Serialize(dto); + } + + public struct Size + { + public Size(string value) + { + var parts = value.Split(','); + this.Width = parts[0]; + this.Height = parts[1]; + } + + public Size(string width, string height) + { + Width = width; + Height = height; + } + + public string Width; + public string Height; + + public override string ToString() + { + return this.Width + "," + this.Height; + } + } + + [Test] + public void Can_serialize_struct_in_list() + { + var structs = new[] { new Size("10px", "10px"), new Size("20px", "20px"), }; - Serialize(structs); - } - - [Test] - public void Can_serialize_list_of_bools() - { - Serialize(new List { true, false, true }); - Serialize(new[] { true, false, true }); - } - - public class PolarValues - { - public int Int { get; set; } - public long Long { get; set; } - public float Float { get; set; } - public double Double { get; set; } - public decimal Decimal { get; set; } - - public bool Equals(PolarValues other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return other.Int == Int - && other.Long == Long - && other.Float.Equals(Float) - && other.Double.Equals(Double) - && other.Decimal == Decimal; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(PolarValues)) return false; - return Equals((PolarValues)obj); - } - - public override int GetHashCode() - { - unchecked - { - int result = Int; - result = (result * 397) ^ Long.GetHashCode(); - result = (result * 397) ^ Float.GetHashCode(); - result = (result * 397) ^ Double.GetHashCode(); - result = (result * 397) ^ Decimal.GetHashCode(); - return result; - } - } - } - - [Test] - public void Can_serialize_max_values() - { - var dto = new PolarValues { - Int = int.MaxValue, - Long = long.MaxValue, - Float = float.MaxValue, - Double = double.MaxValue, - Decimal = decimal.MaxValue, - }; - var to = Serialize(dto); - Assert.That(to, Is.EqualTo(dto)); - } - - [Test] - public void Can_serialize_max_values_less_1() - { - var dto = new PolarValues { - Int = int.MaxValue - 1, - Long = long.MaxValue - 1, - Float = float.MaxValue - 1, - Double = double.MaxValue - 1, - Decimal = decimal.MaxValue - 1, - }; - var to = Serialize(dto); - Assert.That(to, Is.EqualTo(dto)); - } - - [Test] - public void Can_serialize_min_values() - { - var dto = new PolarValues { - Int = int.MinValue, - Long = long.MinValue, - Float = float.MinValue, - Double = double.MinValue, - Decimal = decimal.MinValue, - }; - var to = Serialize(dto); - Assert.That(to, Is.EqualTo(dto)); - } - - public class TestClass - { - public string Description { get; set; } - public TestClass Inner { get; set; } - } - - [Test] - public void Can_serialize_1_level_cyclical_dto() - { - var dto = new TestClass { - Description = "desc", - Inner = new TestClass { Description = "inner" } - }; - - var from = Serialize(dto, includeXml:false); - - Assert.That(from.Description, Is.EqualTo(dto.Description)); - Assert.That(from.Inner.Description, Is.EqualTo(dto.Inner.Description)); - Console.WriteLine(from.Dump()); - } - - public enum EnumValues - { - Enum1, - Enum2, - Enum3, - } - - [Test] - public void Can_Deserialize() - { - var items = TypeSerializer.DeserializeFromString>( - "/CustomPath35/api,/CustomPath40/api,/RootPath35,/RootPath40,:82,:83,:5001/api,:5002/api,:5003,:5004"); - - Console.WriteLine(items.Dump()); - } - - [Test] - public void Can_Serialize_Array_of_enums() - { - var enumArr = new[] { EnumValues.Enum1, EnumValues.Enum2, EnumValues.Enum3, }; - var json = JsonSerializer.SerializeToString(enumArr); - Assert.That(json, Is.EqualTo("[\"Enum1\",\"Enum2\",\"Enum3\"]")); - } - - [Test] - public void Can_Serialize_Array_of_chars() - { - var enumArr = new[] { 'A', 'B', 'C', }; - var json = JsonSerializer.SerializeToString(enumArr); - Assert.That(json, Is.EqualTo("[\"A\",\"B\",\"C\"]")); - } - - [Test] - public void Can_Serialize_Array_with_nulls() - { - var t = new { - Name = "MyName", - Number = (int?)null, - Data = new object[] { 5, null, "text" } - }; - - ServiceStack.Text.JsConfig.IncludeNullValues = true; - var json = ServiceStack.Text.JsonSerializer.SerializeToString(t); - Assert.That(json, Is.EqualTo("{\"Name\":\"MyName\",\"Number\":null,\"Data\":[5,null,\"text\"]}")); - JsConfig.Reset(); - } - - class A - { - public string Value { get; set; } - } - - [Test] - public void DumpFail() - { - var arrayOfA = new[] { new A { Value = "a" }, null, new A { Value = "b" } }; - Console.WriteLine(arrayOfA.Dump()); - } - - [Test] - public void Deserialize_array_with_null_elements() - { - var json = "[{\"Value\": \"a\"},null,{\"Value\": \"b\"}]"; - var o = JsonSerializer.DeserializeFromString(json); - } - - [Test] - public void Can_serialize_StringCollection() - { - var sc = new StringCollection {"one", "two", "three"}; - var from = Serialize(sc, includeXml:false); - Console.WriteLine(from.Dump()); - } - } + Serialize(structs); + } + + [Test] + public void Can_serialize_list_of_bools() + { + Serialize(new List { true, false, true }); + Serialize(new[] { true, false, true }); + } + + public class PolarValues + { + public int Int { get; set; } + public long Long { get; set; } + public float Float { get; set; } + public double Double { get; set; } + public decimal Decimal { get; set; } + + public bool Equals(PolarValues other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other.Int == Int + && other.Long == Long + && other.Float.Equals(Float) + && other.Double.Equals(Double) + && other.Decimal == Decimal; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(PolarValues)) return false; + return Equals((PolarValues)obj); + } + + public override int GetHashCode() + { + unchecked + { + int result = Int; + result = (result * 397) ^ Long.GetHashCode(); + result = (result * 397) ^ Float.GetHashCode(); + result = (result * 397) ^ Double.GetHashCode(); + result = (result * 397) ^ Decimal.GetHashCode(); + return result; + } + } + } + + [Test] + public void Can_serialize_max_values() + { + var dto = new PolarValues + { + Int = int.MaxValue, + Long = long.MaxValue, + Float = float.MaxValue, + Double = double.MaxValue, + Decimal = decimal.MaxValue, + }; + var to = Serialize(dto); + Assert.That(to, Is.EqualTo(dto)); + } + + [Test] + public void Can_serialize_max_values_less_1() + { + var dto = new PolarValues + { + Int = int.MaxValue - 1, + Long = long.MaxValue - 1, + Float = float.MaxValue - 1, + Double = double.MaxValue - 1, + Decimal = decimal.MaxValue - 1, + }; + var to = Serialize(dto); + Assert.That(to, Is.EqualTo(dto)); + } + + [Test] + public void Can_serialize_min_values() + { + var dto = new PolarValues + { + Int = int.MinValue, + Long = long.MinValue, + Float = float.MinValue, + Double = double.MinValue, + Decimal = decimal.MinValue, + }; + var to = Serialize(dto); + Assert.That(to, Is.EqualTo(dto)); + } + + public class TestClass + { + public string Description { get; set; } + public TestClass Inner { get; set; } + } + + [Test] + public void Can_serialize_1_level_cyclical_dto() + { + var dto = new TestClass + { + Description = "desc", + Inner = new TestClass { Description = "inner" } + }; + + var from = Serialize(dto, includeXml: false); + + Assert.That(from.Description, Is.EqualTo(dto.Description)); + Assert.That(from.Inner.Description, Is.EqualTo(dto.Inner.Description)); + Console.WriteLine(from.Dump()); + } + + public enum EnumValues + { + Enum1, + Enum2, + Enum3, + } + + [Test] + public void Can_Deserialize() + { + var items = TypeSerializer.DeserializeFromString>( + "/CustomPath35/api,/CustomPath40/api,/RootPath35,/RootPath40,:82,:83,:5001/api,:5002/api,:5003,:5004"); + + Console.WriteLine(items.Dump()); + } + + [Test] + public void Can_Serialize_Array_of_enums() + { + var enumArr = new[] { EnumValues.Enum1, EnumValues.Enum2, EnumValues.Enum3, }; + var json = JsonSerializer.SerializeToString(enumArr); + Assert.That(json, Is.EqualTo("[\"Enum1\",\"Enum2\",\"Enum3\"]")); + } + + public class DictionaryEnumType + { + public Dictionary DictEnumType { get; set; } + } + + [Test] + public void Can_Serialize_Dictionary_With_Enums() + { + Dictionary dictEnumType = + new Dictionary + { + { + EnumValues.Enum1, new Test { Val = "A Value" } + } + }; + + var item = new DictionaryEnumType + { + DictEnumType = dictEnumType + }; + const string expected = "{\"DictEnumType\":{\"Enum1\":{\"Val\":\"A Value\"}}}"; + + var jsonItem = JsonSerializer.SerializeToString(item); + //Log(jsonItem); + Assert.That(jsonItem, Is.EqualTo(expected)); + + var deserializedItem = JsonSerializer.DeserializeFromString(jsonItem); + Assert.That(deserializedItem, Is.TypeOf()); + } + + [Test] + public void Can_Serialize_Array_of_chars() + { + var enumArr = new[] { 'A', 'B', 'C', }; + var json = JsonSerializer.SerializeToString(enumArr); + Assert.That(json, Is.EqualTo("[\"A\",\"B\",\"C\"]")); + } + + [Test] + public void Can_Serialize_Array_with_nulls() + { + var t = new + { + Name = "MyName", + Number = (int?)null, + Data = new object[] { 5, null, "text" } + }; + + ServiceStack.Text.JsConfig.IncludeNullValues = true; + var json = ServiceStack.Text.JsonSerializer.SerializeToString(t); + Assert.That(json, Is.EqualTo("{\"Name\":\"MyName\",\"Number\":null,\"Data\":[5,null,\"text\"]}")); + JsConfig.Reset(); + } + + class A + { + public string Value { get; set; } + } + + [Test] + public void DumpFail() + { + var arrayOfA = new[] { new A { Value = "a" }, null, new A { Value = "b" } }; + Console.WriteLine(arrayOfA.Dump()); + } + + [Test] + public void Can_deserialize_case_insensitive_names() + { + var dto = "{\"vALUE\":\"B\"}".FromJson(); + Assert.That(dto.Value, Is.EqualTo("B")); + + dto = "{vALUE:B}".FromJsv(); + Assert.That(dto.Value, Is.EqualTo("B")); + } + + [Test] + public void Deserialize_array_with_null_elements() + { + var json = "[{\"Value\": \"a\"},null,{\"Value\": \"b\"}]"; + var o = JsonSerializer.DeserializeFromString(json); + } + + [Test] + public void Can_serialize_StringCollection() + { + var sc = new StringCollection { "one", "two", "three" }; + var from = Serialize(sc, includeXml: false); + Console.WriteLine(from.Dump()); + } + + public class Breaker + { + public IEnumerable Blah { get; set; } + } + + [Test] + public void Can_serialize_IEnumerable() + { + var dto = new Breaker + { + Blah = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9 } + }; + + var from = Serialize(dto, includeXml: false); + Assert.IsNotNull(from.Blah); + from.PrintDump(); + } + + public class BreakerCollection + { + public ICollection Blah { get; set; } + } + + [Test] + public void Can_serialize_ICollection() + { + var dto = new BreakerCollection + { + Blah = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9 } + }; + + var from = Serialize(dto, includeXml: false); + Assert.IsNotNull(from.Blah); + Assert.AreEqual(dto.Blah.Count, from.Blah.Count); + from.PrintDump(); + } + + [Test] + public void Can_parse_different_3_part_date_formats() + { + Assert.That("28/06/2015".FromJsv(), + Is.EqualTo(new DateTime(2015, 6, 28))); + + Assert.That("6/28/2015".FromJsv(), + Is.EqualTo(new DateTime(2015, 6, 28))); + + DateTimeSerializer.OnParseErrorFn = (s, ex) => + { + var parts = s.Split('/'); + return new DateTime(int.Parse(parts[2]), int.Parse(parts[0]), int.Parse(parts[1])); + }; + + Assert.That("06/28/2015".FromJsv(), + Is.EqualTo(new DateTime(2015, 6, 28))); + + DateTimeSerializer.OnParseErrorFn = null; + } + } } diff --git a/tests/ServiceStack.Text.Tests/AdhocModelTests.cs.orig b/tests/ServiceStack.Text.Tests/AdhocModelTests.cs.orig deleted file mode 100644 index d067b2947..000000000 --- a/tests/ServiceStack.Text.Tests/AdhocModelTests.cs.orig +++ /dev/null @@ -1,443 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Runtime.Serialization; -using NUnit.Framework; -using ServiceStack.Common.Extensions; -using ServiceStack.Text.Jsv; - -namespace ServiceStack.Text.Tests -{ -<<<<<<< HEAD - [TestFixture] - public class AdhocModelTests - : TestBase - { - public enum FlowPostType - { - Content, - Text, - Promo, - } - - public class FlowPostTransient - { - public FlowPostTransient() - { - this.TrackUrns = new List(); - } - - public long Id { get; set; } - - public string Urn { get; set; } - - public Guid UserId { get; set; } - - public DateTime DateAdded { get; set; } - - public DateTime DateModified { get; set; } - - public Guid? TargetUserId { get; set; } - - public long? ForwardedPostId { get; set; } - - public Guid OriginUserId { get; set; } - - public string OriginUserName { get; set; } - - public Guid SourceUserId { get; set; } - - public string SourceUserName { get; set; } - - public string SubjectUrn { get; set; } - - public string ContentUrn { get; set; } - - public IList TrackUrns { get; set; } - - public string Caption { get; set; } - - public Guid CaptionUserId { get; set; } - - public string CaptionSourceName { get; set; } - - public string ForwardedPostUrn { get; set; } - - public FlowPostType PostType { get; set; } - - public Guid? OnBehalfOfUserId { get; set; } - - public static FlowPostTransient Create() - { - return new FlowPostTransient - { - Caption = "Caption", - CaptionSourceName = "CaptionSourceName", - CaptionUserId = Guid.NewGuid(), - ContentUrn = "ContentUrn", - DateAdded = DateTime.Now, - DateModified = DateTime.Now, - ForwardedPostId = 1, - ForwardedPostUrn = "ForwardedPostUrn", - Id = 1, - OnBehalfOfUserId = Guid.NewGuid(), - OriginUserId = Guid.NewGuid(), - OriginUserName = "OriginUserName", - PostType = FlowPostType.Content, - SourceUserId = Guid.NewGuid(), - SourceUserName = "SourceUserName", - SubjectUrn = "SubjectUrn ", - TargetUserId = Guid.NewGuid(), - TrackUrns = new List { "track1", "track2" }, - Urn = "Urn ", - UserId = Guid.NewGuid(), - }; - } - - public bool Equals(FlowPostTransient other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return other.Id == Id && Equals(other.Urn, Urn) && other.UserId.Equals(UserId) && other.DateAdded.IsEqualToTheSecond(DateAdded) && other.DateModified.IsEqualToTheSecond(DateModified) && other.TargetUserId.Equals(TargetUserId) && other.ForwardedPostId.Equals(ForwardedPostId) && other.OriginUserId.Equals(OriginUserId) && Equals(other.OriginUserName, OriginUserName) && other.SourceUserId.Equals(SourceUserId) && Equals(other.SourceUserName, SourceUserName) && Equals(other.SubjectUrn, SubjectUrn) && Equals(other.ContentUrn, ContentUrn) && TrackUrns.EquivalentTo(other.TrackUrns) && Equals(other.Caption, Caption) && other.CaptionUserId.Equals(CaptionUserId) && Equals(other.CaptionSourceName, CaptionSourceName) && Equals(other.ForwardedPostUrn, ForwardedPostUrn) && Equals(other.PostType, PostType) && other.OnBehalfOfUserId.Equals(OnBehalfOfUserId); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(FlowPostTransient)) return false; - return Equals((FlowPostTransient)obj); - } - - public override int GetHashCode() - { - unchecked - { - int result = Id.GetHashCode(); - result = (result * 397) ^ (Urn != null ? Urn.GetHashCode() : 0); - result = (result * 397) ^ UserId.GetHashCode(); - result = (result * 397) ^ DateAdded.GetHashCode(); - result = (result * 397) ^ DateModified.GetHashCode(); - result = (result * 397) ^ (TargetUserId.HasValue ? TargetUserId.Value.GetHashCode() : 0); - result = (result * 397) ^ (ForwardedPostId.HasValue ? ForwardedPostId.Value.GetHashCode() : 0); - result = (result * 397) ^ OriginUserId.GetHashCode(); - result = (result * 397) ^ (OriginUserName != null ? OriginUserName.GetHashCode() : 0); - result = (result * 397) ^ SourceUserId.GetHashCode(); - result = (result * 397) ^ (SourceUserName != null ? SourceUserName.GetHashCode() : 0); - result = (result * 397) ^ (SubjectUrn != null ? SubjectUrn.GetHashCode() : 0); - result = (result * 397) ^ (ContentUrn != null ? ContentUrn.GetHashCode() : 0); - result = (result * 397) ^ (TrackUrns != null ? TrackUrns.GetHashCode() : 0); - result = (result * 397) ^ (Caption != null ? Caption.GetHashCode() : 0); - result = (result * 397) ^ CaptionUserId.GetHashCode(); - result = (result * 397) ^ (CaptionSourceName != null ? CaptionSourceName.GetHashCode() : 0); - result = (result * 397) ^ (ForwardedPostUrn != null ? ForwardedPostUrn.GetHashCode() : 0); - result = (result * 397) ^ PostType.GetHashCode(); - result = (result * 397) ^ (OnBehalfOfUserId.HasValue ? OnBehalfOfUserId.Value.GetHashCode() : 0); - return result; - } - } - } - - [Test] - public void Can_Deserialize_text() - { - var dtoString = "[{Id:1,Urn:urn:post:3a944f18-920c-498a-832d-cf38fed3d0d7/1,UserId:3a944f18920c498a832dcf38fed3d0d7,DateAdded:2010-02-17T12:04:45.2845615Z,DateModified:2010-02-17T12:04:45.2845615Z,OriginUserId:3a944f18920c498a832dcf38fed3d0d7,OriginUserName:testuser1,SourceUserId:3a944f18920c498a832dcf38fed3d0d7,SourceUserName:testuser1,SubjectUrn:urn:track:1,ContentUrn:urn:track:1,TrackUrns:[],CaptionUserId:3a944f18920c498a832dcf38fed3d0d7,CaptionSourceName:testuser1,PostType:Content}]"; - var fromString = TypeSerializer.DeserializeFromString>(dtoString); - } - - [Test] - public void Can_Serialize_single_FlowPostTransient() - { - var dto = FlowPostTransient.Create(); - SerializeAndCompare(dto); - } - - [Test] - public void Can_serialize_jsv_dates() - { - var now = DateTime.Now; - - var jsvDate = TypeSerializer.SerializeToString(now); - var fromJsvDate = TypeSerializer.DeserializeFromString(jsvDate); - Assert.That(fromJsvDate, Is.EqualTo(now)); - } - - [Test] - public void Can_serialize_json_dates() - { - var now = DateTime.Now; - - var jsonDate = JsonSerializer.SerializeToString(now); - var fromJsonDate = JsonSerializer.DeserializeFromString(jsonDate); - - Assert.That(fromJsonDate.RoundToSecond(), Is.EqualTo(now.RoundToSecond())); - } - - [Test] - public void Can_Serialize_multiple_FlowPostTransient() - { - var dtos = new List { -======= - [TestFixture] - public class AdhocModelTests - : TestBase - { - public enum FlowPostType - { - Content, - Text, - Promo, - } - - public class FlowPostTransient - { - public FlowPostTransient() - { - this.TrackUrns = new List(); - } - - public long Id { get; set; } - - public string Urn { get; set; } - - public Guid UserId { get; set; } - - public DateTime DateAdded { get; set; } - - public DateTime DateModified { get; set; } - - public Guid? TargetUserId { get; set; } - - public long? ForwardedPostId { get; set; } - - public Guid OriginUserId { get; set; } - - public string OriginUserName { get; set; } - - public Guid SourceUserId { get; set; } - - public string SourceUserName { get; set; } - - public string SubjectUrn { get; set; } - - public string ContentUrn { get; set; } - - public IList TrackUrns { get; set; } - - public string Caption { get; set; } - - public Guid CaptionUserId { get; set; } - - public string CaptionSourceName { get; set; } - - public string ForwardedPostUrn { get; set; } - - public FlowPostType PostType { get; set; } - - public Guid? OnBehalfOfUserId { get; set; } - - public static FlowPostTransient Create() - { - return new FlowPostTransient - { - Caption = "Caption", - CaptionSourceName = "CaptionSourceName", - CaptionUserId = Guid.NewGuid(), - ContentUrn = "ContentUrn", - DateAdded = DateTime.Now, - DateModified = DateTime.Now, - ForwardedPostId = 1, - ForwardedPostUrn = "ForwardedPostUrn", - Id = 1, - OnBehalfOfUserId = Guid.NewGuid(), - OriginUserId = Guid.NewGuid(), - OriginUserName = "OriginUserName", - PostType = FlowPostType.Content, - SourceUserId = Guid.NewGuid(), - SourceUserName = "SourceUserName", - SubjectUrn = "SubjectUrn ", - TargetUserId = Guid.NewGuid(), - TrackUrns = new List { "track1", "track2" }, - Urn = "Urn ", - UserId = Guid.NewGuid(), - }; - } - - public bool Equals(FlowPostTransient other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return other.Id == Id && Equals(other.Urn, Urn) && other.UserId.Equals(UserId) && other.DateAdded.RoundToMs().Equals(DateAdded.RoundToMs()) && other.DateModified.RoundToMs().Equals(DateModified.RoundToMs()) && other.TargetUserId.Equals(TargetUserId) && other.ForwardedPostId.Equals(ForwardedPostId) && other.OriginUserId.Equals(OriginUserId) && Equals(other.OriginUserName, OriginUserName) && other.SourceUserId.Equals(SourceUserId) && Equals(other.SourceUserName, SourceUserName) && Equals(other.SubjectUrn, SubjectUrn) && Equals(other.ContentUrn, ContentUrn) && TrackUrns.EquivalentTo(other.TrackUrns) && Equals(other.Caption, Caption) && other.CaptionUserId.Equals(CaptionUserId) && Equals(other.CaptionSourceName, CaptionSourceName) && Equals(other.ForwardedPostUrn, ForwardedPostUrn) && Equals(other.PostType, PostType) && other.OnBehalfOfUserId.Equals(OnBehalfOfUserId); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof (FlowPostTransient)) return false; - return Equals((FlowPostTransient) obj); - } - - public override int GetHashCode() - { - unchecked - { - int result = Id.GetHashCode(); - result = (result*397) ^ (Urn != null ? Urn.GetHashCode() : 0); - result = (result*397) ^ UserId.GetHashCode(); - result = (result*397) ^ DateAdded.GetHashCode(); - result = (result*397) ^ DateModified.GetHashCode(); - result = (result*397) ^ (TargetUserId.HasValue ? TargetUserId.Value.GetHashCode() : 0); - result = (result*397) ^ (ForwardedPostId.HasValue ? ForwardedPostId.Value.GetHashCode() : 0); - result = (result*397) ^ OriginUserId.GetHashCode(); - result = (result*397) ^ (OriginUserName != null ? OriginUserName.GetHashCode() : 0); - result = (result*397) ^ SourceUserId.GetHashCode(); - result = (result*397) ^ (SourceUserName != null ? SourceUserName.GetHashCode() : 0); - result = (result*397) ^ (SubjectUrn != null ? SubjectUrn.GetHashCode() : 0); - result = (result*397) ^ (ContentUrn != null ? ContentUrn.GetHashCode() : 0); - result = (result*397) ^ (TrackUrns != null ? TrackUrns.GetHashCode() : 0); - result = (result*397) ^ (Caption != null ? Caption.GetHashCode() : 0); - result = (result*397) ^ CaptionUserId.GetHashCode(); - result = (result*397) ^ (CaptionSourceName != null ? CaptionSourceName.GetHashCode() : 0); - result = (result*397) ^ (ForwardedPostUrn != null ? ForwardedPostUrn.GetHashCode() : 0); - result = (result*397) ^ PostType.GetHashCode(); - result = (result*397) ^ (OnBehalfOfUserId.HasValue ? OnBehalfOfUserId.Value.GetHashCode() : 0); - return result; - } - } - } - - [Test] - public void Can_Deserialize_text() - { - var dtoString = "[{Id:1,Urn:urn:post:3a944f18-920c-498a-832d-cf38fed3d0d7/1,UserId:3a944f18920c498a832dcf38fed3d0d7,DateAdded:2010-02-17T12:04:45.2845615Z,DateModified:2010-02-17T12:04:45.2845615Z,OriginUserId:3a944f18920c498a832dcf38fed3d0d7,OriginUserName:testuser1,SourceUserId:3a944f18920c498a832dcf38fed3d0d7,SourceUserName:testuser1,SubjectUrn:urn:track:1,ContentUrn:urn:track:1,TrackUrns:[],CaptionUserId:3a944f18920c498a832dcf38fed3d0d7,CaptionSourceName:testuser1,PostType:Content}]"; - var fromString = TypeSerializer.DeserializeFromString>(dtoString); - } - - [Test] - public void Can_Serialize_single_FlowPostTransient() - { - var dto = FlowPostTransient.Create(); - SerializeAndCompare(dto); - } - - [Test] - public void Can_serialize_jsv_dates() - { - var now = DateTime.Now; - - var jsvDate = TypeSerializer.SerializeToString(now); - var fromJsvDate = TypeSerializer.DeserializeFromString(jsvDate); - Assert.That(fromJsvDate, Is.EqualTo(now)); - } - - [Test] - public void Can_serialize_json_dates() - { - var now = DateTime.Now; - - var jsonDate = JsonSerializer.SerializeToString(now); - var fromJsonDate = JsonSerializer.DeserializeFromString(jsonDate); - - Assert.That(fromJsonDate.RoundToMs(), Is.EqualTo(now.RoundToMs())); - } - - [Test] - public void Can_Serialize_multiple_FlowPostTransient() - { - var dtos = new List { ->>>>>>> 8f9b7fc39dc3944c8586461784db4bd04dd2b9e1 - FlowPostTransient.Create(), - FlowPostTransient.Create() - }; - Serialize(dtos); - } - - [DataContract] - public class TestObject - { - [DataMember] - public string Value { get; set; } - public TranslatedString ValueNoMember { get; set; } - - public bool Equals(TestObject other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other.Value, Value); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof (TestObject)) return false; - return Equals((TestObject) obj); - } - - public override int GetHashCode() - { - return (Value != null ? Value.GetHashCode() : 0); - } - } - - public class Test - { - public string Val { get; set; } - } - - public class TestResponse - { - public TestObject Result { get; set; } - } - - public class TranslatedString : ListDictionary - { - public string CurrentLanguage { get; set; } - - public string Value - { - get - { - if (this.Contains(CurrentLanguage)) - return this[CurrentLanguage] as string; - return null; - } - set - { - if (this.Contains(CurrentLanguage)) - this[CurrentLanguage] = value; - else - Add(CurrentLanguage, value); - } - } - - public TranslatedString() - { - CurrentLanguage = "en"; - } - - public static void SetLanguageOnStrings(string lang, params TranslatedString[] strings) - { - foreach (TranslatedString str in strings) - str.CurrentLanguage = lang; - } - } - - [Test] - public void Should_ignore_non_DataMember_TranslatedString() - { - var dto = new TestObject - { - Value = "value", - ValueNoMember = new TranslatedString - { - {"key1", "val1"}, - {"key2", "val2"}, - } - }; - SerializeAndCompare(dto); - } - } -} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AnonymousTypes.cs b/tests/ServiceStack.Text.Tests/AnonymousTypes.cs index 46e0d0603..0a4007f85 100644 --- a/tests/ServiceStack.Text.Tests/AnonymousTypes.cs +++ b/tests/ServiceStack.Text.Tests/AnonymousTypes.cs @@ -27,6 +27,15 @@ public void Can_serialize_anonymous_type_and_read_as_string_Dictionary() Console.WriteLine("MAP: " + map.Dump()); } + [Test] + public void Can_reset_JsConfig_after_serialization() + { + var t = new { Name="123"}; + + var json = ServiceStack.Text.JsonSerializer.SerializeToString(t); + JsConfig.Reset(); + } + public class TestObj { public string Title1 { get; set; } diff --git a/tests/ServiceStack.Text.Tests/AotTests.cs b/tests/ServiceStack.Text.Tests/AotTests.cs deleted file mode 100644 index ec3364eec..000000000 --- a/tests/ServiceStack.Text.Tests/AotTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; -using ServiceStack.Text.Tests.Support; - -namespace ServiceStack.Text.Tests -{ - [TestFixture] - public class AotTests - { -#if SILVERLIGHT || MONOTOUCH - [Test] - public void Can_Register_AOT() - { - JsConfig.RegisterForAot(); - } -#endif - } -} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/App.config b/tests/ServiceStack.Text.Tests/App.config new file mode 100644 index 000000000..559ff2fa7 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AttributeTests.cs b/tests/ServiceStack.Text.Tests/AttributeTests.cs new file mode 100644 index 000000000..eb2a7ec54 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/AttributeTests.cs @@ -0,0 +1,343 @@ +using System; +using System.Linq; +using NUnit.Framework; +using ServiceStack; +using System.Runtime.Serialization; +using System.Reflection; + +namespace ServiceStack.Text.Tests +{ + using System.Collections.Generic; + + [TestFixture] + public class AttributeTests + { + [Test] + public void Does_get_Single_Default_Attribute() + { + var attrs = typeof(DefaultWithSingleAttribute).AllAttributes(); + Assert.That(attrs[0].ToString(), Is.EqualTo("/path:")); + + var attr = typeof(DefaultWithSingleAttribute).FirstAttribute(); + Assert.That(attr.ToString(), Is.EqualTo("/path:")); + } + + [Test] + public void Does_get_Single_TypeId_Attribute() + { + var attrs = typeof(TypeIdWithSingleAttribute).AllAttributes(); + Assert.That(attrs[0].ToString(), Is.EqualTo("/path:")); + + var attr = typeof(TypeIdWithSingleAttribute).FirstAttribute(); + Assert.That(attr.ToString(), Is.EqualTo("/path:")); + } + + [Test] + public void Does_get_Multiple_RouteDefault_Attributes() + { + // AllAttributes() makes this call to get attrs +#if NETCORE + var referenceGeneric = + typeof(DefaultWithMultipleAttributes).GetTypeInfo().GetCustomAttributes(typeof(RouteDefaultAttribute), true) + .OfType(); +#else + var referenceGeneric = + typeof(DefaultWithMultipleAttributes).GetCustomAttributes(typeof(RouteDefaultAttribute), true) + .OfType(); +#endif + // Attribute inheritance hierarchies (InheritedRouteAttribute) are returned in results + Assert.That(referenceGeneric.Count(), Is.EqualTo(4)); + + // AllAttributes() makes this call to get attrs +#if NETCORE + var reference = + typeof(DefaultWithMultipleAttributes).GetTypeInfo().GetCustomAttributes(typeof(RouteDefaultAttribute), true); +#else + var reference = + typeof(DefaultWithMultipleAttributes).GetCustomAttributes(typeof(RouteDefaultAttribute), true); +#endif + // Attribute inheritance hierarchies (InheritedRouteAttribute) are returned in results + Assert.That(reference.Count(), Is.EqualTo(4)); + + // Loses one of the attrs with inheritence when union + var referenceUnion = referenceGeneric.Union(new List()); + Assert.That(referenceUnion.Count(), Is.EqualTo(4)); + + // Keeps all items when concat + var referenceConcat = referenceGeneric.Concat(new List()); + Assert.That(referenceConcat.Count(), Is.EqualTo(4)); + + var attrsGeneric = typeof(DefaultWithMultipleAttributes).AllAttributes(); + + // Attribute inheritance hierarchies (InheritedRouteAttribute) are NOT ALL returned in results + Assert.That(attrsGeneric.Length, Is.EqualTo(4)); // union loses one + + var attrs = typeof(DefaultWithMultipleAttributes).AllAttributes(typeof(RouteDefaultAttribute)); + + // Attribute inheritance hierarchies (InheritedRouteAttribute) are NOT ALL returned in results + Assert.That(attrs.Length, Is.EqualTo(4)); // union loses one + + var values = attrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + })); + + var objAttrs = typeof(DefaultWithMultipleAttributes).AllAttributes(); + values = objAttrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + })); + + objAttrs = typeof(DefaultWithMultipleAttributes).AllAttributes(typeof(RouteDefaultAttribute)); + values = objAttrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + })); + } + + [Test] + public void Does_get_Multiple_Route_Attributes() + { + var routeAttrs = typeof(DefaultWithMultipleRouteAttributes) + .AllAttributes(); + + Assert.That(routeAttrs.Length, Is.EqualTo(4)); + + var values = routeAttrs.ToList().ConvertAll(x => "{0}:{1}".Fmt(x.Path, x.Verbs)); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + })); + + var inheritedRouteAttrs = typeof(DefaultWithMultipleRouteAttributes) + .AllAttributes(); + + Assert.That(inheritedRouteAttrs.Length, Is.EqualTo(2)); + } + + [Test] + public void Does_get_Multiple_TypeId_Attributes() + { + var attrs = typeof(TypeIdWithMultipleAttributes).AllAttributes(); + Assert.That(attrs.Length, Is.EqualTo(4)); + + var values = attrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + })); + + var objAttrs = typeof(TypeIdWithMultipleAttributes).AllAttributes(); + values = objAttrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + })); + + objAttrs = typeof(TypeIdWithMultipleAttributes).AllAttributes(typeof(RouteTypeIdAttribute)); + values = objAttrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + })); + } + } + + [TestFixture] + public class RuntimeAttributesTests + { + [Test] + public void Can_add_to_Multiple_Default_Attributes() + { + typeof (DefaultWithMultipleAttributes).AddAttributes( + new RouteDefaultAttribute("/path-add"), + new RouteDefaultAttribute("/path-add", "GET")); + + var attrs = typeof(DefaultWithMultipleAttributes).AllAttributes(); + Assert.That(attrs.Length, Is.EqualTo(6)); + + var values = attrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + "/path-add:", "/path-add:GET", + })); + + var objAttrs = typeof(DefaultWithMultipleAttributes).AllAttributes(); + values = objAttrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + "/path-add:", "/path-add:GET", + })); + + objAttrs = typeof(DefaultWithMultipleAttributes).AllAttributes(typeof(RouteDefaultAttribute)); + values = objAttrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + "/path-add:", "/path-add:GET", + })); + } + + [Test] + public void Does_get_Multiple_TypeId_Attributes() + { + typeof(TypeIdWithMultipleAttributes).AddAttributes( + new RouteTypeIdAttribute("/path-add"), + new RouteTypeIdAttribute("/path-add", "GET")); + + var attrs = typeof(TypeIdWithMultipleAttributes).AllAttributes(); + Assert.That(attrs.Length, Is.EqualTo(6)); + + var values = attrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + "/path-add:", "/path-add:GET", + })); + + var objAttrs = typeof(TypeIdWithMultipleAttributes).AllAttributes(); + values = objAttrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + "/path-add:", "/path-add:GET", + })); + + objAttrs = typeof(TypeIdWithMultipleAttributes).AllAttributes(typeof(RouteTypeIdAttribute)); + values = objAttrs.ToList().ConvertAll(x => x.ToString()); + + Assert.That(values, Is.EquivalentTo(new[] { + "/path:", "/path/2:", "/path:GET", "/path:POST", + "/path-add:", "/path-add:GET", + })); + } + } + + [RouteTypeId("/path")] + public class TypeIdWithSingleAttribute { } + + [RouteTypeId("/path")] + [RouteTypeId("/path/2")] + [RouteTypeId("/path", "GET")] + [RouteTypeId("/path", "POST")] + public class TypeIdWithMultipleAttributes { } + + [RouteDefault("/path")] + public class DefaultWithSingleAttribute { } + + [RouteDefault("/path")] + [RouteDefault("/path/2")] + [InheritedRouteDefault("/path", "GET")] + [InheritedRouteDefault("/path", "POST")] + public class DefaultWithMultipleAttributes { } + + [Route("/path")] + [Route("/path/2")] + [InheritedRoute("/path", "GET")] + [InheritedRoute("/path", "POST")] + public class DefaultWithMultipleRouteAttributes { } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class RouteTypeIdAttribute : Attribute + { + public RouteTypeIdAttribute(string path) : this(path, null) {} + public RouteTypeIdAttribute(string path, string verbs) + { + Path = path; + Verbs = verbs; + } + + public string Path { get; set; } + public string Verbs { get; set; } + + public override object TypeId + { + get + { + return (Path ?? "") + + (Verbs ?? ""); + } + } + + public override string ToString() + { + return "{0}:{1}".Fmt(Path, Verbs); + } + } + + public class InheritedRouteDefaultAttribute : RouteDefaultAttribute { + public InheritedRouteDefaultAttribute(string path) + : base(path) + { + } + + public InheritedRouteDefaultAttribute(string path, string verbs) + : base(path, verbs) + { + } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class RouteDefaultAttribute : Attribute + { + public RouteDefaultAttribute(string path) : this(path, null) {} + public RouteDefaultAttribute(string path, string verbs) + { + Path = path; + Verbs = verbs; + } + + public string Path { get; set; } + public string Verbs { get; set; } + + public override string ToString() + { + return "{0}:{1}".Fmt(Path, Verbs); + } + + protected bool Equals(RouteDefaultAttribute other) + { + return base.Equals(other) && string.Equals(Path, other.Path) && string.Equals(Verbs, other.Verbs); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((RouteDefaultAttribute) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = base.GetHashCode(); + hashCode = (hashCode*397) ^ (Path != null ? Path.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (Verbs != null ? Verbs.GetHashCode() : 0); + return hashCode; + } + } + } + + public class InheritedRouteAttribute : RouteAttribute + { + public InheritedRouteAttribute(string path) + : base(path) + { + } + + public InheritedRouteAttribute(string path, string verbs) + : base(path, verbs) + { + } + + public string Custom { get; set; } + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AutoMappingBenchmarks.cs b/tests/ServiceStack.Text.Tests/AutoMappingBenchmarks.cs new file mode 100644 index 000000000..d5927dca1 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/AutoMappingBenchmarks.cs @@ -0,0 +1,183 @@ +using NUnit.Framework; +using ServiceStack.Text; + +namespace ServiceStack.Common.Tests +{ + public class BenchSource + { + public class Int1 + { + public string str1 = "1"; + public string str2 = null; + public int i = 10; + } + + public class Int2 + { + public Int1 i1 = new Int1(); + public Int1 i2 = new Int1(); + public Int1 i3 = new Int1(); + public Int1 i4 = new Int1(); + public Int1 i5 = new Int1(); + public Int1 i6 = new Int1(); + public Int1 i7 = new Int1(); + } + + public Int2 i1 = new Int2(); + public Int2 i2 = new Int2(); + public Int2 i3 = new Int2(); + public Int2 i4 = new Int2(); + public Int2 i5 = new Int2(); + public Int2 i6 = new Int2(); + public Int2 i7 = new Int2(); + public Int2 i8 = new Int2(); + + public int n2; + public long n3; + public byte n4; + public short n5; + public uint n6; + public int n7; + public int n8; + public int n9; + + public string s1 = "1"; + public string s2 = "2"; + public string s3 = "3"; + public string s4 = "4"; + public string s5 = "5"; + public string s6 = "6"; + public string s7 = "7"; + + } + + public class BenchDestination + { + public class Int1 + { + public string str1; + public string str2; + public int i; + } + + public class Int2 + { + public Int1 i1; + public Int1 i2; + public Int1 i3; + public Int1 i4; + public Int1 i5; + public Int1 i6; + public Int1 i7; + } + + public Int2 i1 { get; set; } + public Int2 i2 { get; set; } + public Int2 i3 { get; set; } + public Int2 i4 { get; set; } + public Int2 i5 { get; set; } + public Int2 i6 { get; set; } + public Int2 i7 { get; set; } + public Int2 i8 { get; set; } + + public long n2 = 2; + public long n3 = 3; + public long n4 = 4; + public long n5 = 5; + public long n6 = 6; + public long n7 = 7; + public long n8 = 8; + public long n9 = 9; + + public string s1; + public string s2; + public string s3; + public string s4; + public string s5; + public string s6; + public string s7; + } + + public class HandwrittenMapper + { + public static BenchDestination ToBenchDestination(BenchSource from) + { + return new BenchDestination + { + i1 = ToInt2(from.i1), + i2 = ToInt2(from.i2), + i3 = ToInt2(from.i3), + i4 = ToInt2(from.i4), + i5 = ToInt2(from.i5), + i6 = ToInt2(from.i6), + i7 = ToInt2(from.i7), + i8 = ToInt2(from.i8), + + n2 = from.n2, + n3 = from.n3, + n4 = from.n4, + n5 = from.n5, + n6 = from.n6, + n7 = from.n7, + n8 = from.n8, + n9 = from.n9, + + s1 = from.s1, + s2 = from.s2, + s3 = from.s3, + s4 = from.s4, + s5 = from.s5, + s6 = from.s6, + s7 = from.s7, + }; + } + + public static BenchDestination.Int2 ToInt2(BenchSource.Int2 from) + { + return new BenchDestination.Int2 + { + i1 = ToInt1(from.i1), + i2 = ToInt1(from.i2), + i3 = ToInt1(from.i3), + i4 = ToInt1(from.i4), + i5 = ToInt1(from.i5), + i6 = ToInt1(from.i6), + i7 = ToInt1(from.i7), + }; + } + + public static BenchDestination.Int1 ToInt1(BenchSource.Int1 from) + { + return new BenchDestination.Int1 { i = from.i, str1 = from.str1, str2 = from.str2 }; + } + } + + [Ignore("Perf test"), TestFixture] + public class AutoMappingPerfTests : PerfTestBase + { + [Test] + public void Compare_handwritten_vs_AutoMapping() + { + CompareMultipleRuns( + "Handwritten", + () => HandwrittenMapper.ToBenchDestination(new BenchSource()), + "Auto Mapping", + () => new BenchSource().ConvertTo()); + } + + [Test] + public void Does_Convert_BenchSource() + { + var from = new BenchSource(); + var to = from.ConvertTo(); //warmup + to = from.ConvertTo(); + + using (JsConfig.With(new Config { IncludePublicFields = true })) + { + to.PrintDump(); + from.PrintDump(); + } + } + } +} + diff --git a/tests/ServiceStack.Text.Tests/AutoMappingCustomConverterTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingCustomConverterTests.cs new file mode 100644 index 000000000..6a730d415 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/AutoMappingCustomConverterTests.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace ServiceStack.Text.Tests +{ + public class AutoMappingCustomConverterTests + { + public class PersonWithWrappedDateOfBirth : User + { + public WrappedDateTimeOffset DateOfBirth { get; set; } + } + + public class PersonWithDateOfBirth : User + { + public DateTimeOffset DateOfBirth { get; set; } + } + + public class WrappedDateTimeOffset + { + private readonly DateTimeOffset dateTimeOffset; + + public WrappedDateTimeOffset(DateTimeOffset dateTimeOffset) + { + this.dateTimeOffset = dateTimeOffset; + } + + public DateTimeOffset ToDateTimeOffset() + { + return dateTimeOffset; + } + } + + [Test] + public void Can_convert_prop_with_CustomTypeConverter() + { + AutoMapping.RegisterConverter((WrappedDateTimeOffset from) => from.ToDateTimeOffset()); + + var map = new Dictionary + { + { "FirstName", "Foo" }, + { "LastName", "Bar" }, + { "DateOfBirth", new WrappedDateTimeOffset( + new DateTimeOffset(1971, 3, 23, 4, 30, 0, TimeSpan.Zero)) } + }; + + var personWithDoB = map.FromObjectDictionary(); + + Assert.That(personWithDoB.FirstName, Is.EqualTo("Foo")); + Assert.That(personWithDoB.LastName, Is.EqualTo("Bar")); + Assert.That(personWithDoB.DateOfBirth, Is.Not.Null); + Assert.That(personWithDoB.DateOfBirth.Year, Is.EqualTo(1971)); + Assert.That(personWithDoB.DateOfBirth.Month, Is.EqualTo(3)); + Assert.That(personWithDoB.DateOfBirth.Day, Is.EqualTo(23)); + Assert.That(personWithDoB.DateOfBirth.Hour, Is.EqualTo(4)); + Assert.That(personWithDoB.DateOfBirth.Minute, Is.EqualTo(30)); + Assert.That(personWithDoB.DateOfBirth.Second, Is.EqualTo(0)); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Can_Convert_Props_With_CustomTypeConverter() + { + AutoMapping.RegisterConverter((WrappedDateTimeOffset from) => from.ToDateTimeOffset()); + + var personWithWrappedDateOfBirth = new PersonWithWrappedDateOfBirth + { + FirstName = "Foo", + LastName = "Bar", + DateOfBirth = new WrappedDateTimeOffset( + new DateTimeOffset(1971, 3, 23, 4, 30, 0, TimeSpan.Zero)) + }; + + var personWithDoB = personWithWrappedDateOfBirth.ConvertTo(); + + Assert.That(personWithDoB.FirstName, Is.EqualTo("Foo")); + Assert.That(personWithDoB.LastName, Is.EqualTo("Bar")); + Assert.That(personWithDoB.DateOfBirth, Is.Not.Null); + Assert.That(personWithDoB.DateOfBirth.Year, Is.EqualTo(1971)); + Assert.That(personWithDoB.DateOfBirth.Month, Is.EqualTo(3)); + Assert.That(personWithDoB.DateOfBirth.Day, Is.EqualTo(23)); + Assert.That(personWithDoB.DateOfBirth.Hour, Is.EqualTo(4)); + Assert.That(personWithDoB.DateOfBirth.Minute, Is.EqualTo(30)); + Assert.That(personWithDoB.DateOfBirth.Second, Is.EqualTo(0)); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Can_Convert_Anonymous_Types_With_CustomTypeConverter() + { + AutoMapping.RegisterConverter((DateTimeOffset from) => new WrappedDateTimeOffset(from)); + + var personWithDateOfBirth = new + { + FirstName = "Foo", + LastName = "Bar", + DateOfBirth = new DateTimeOffset(1971, 3, 23, 4, 30, 0, TimeSpan.Zero) + }; + + var personWithWrappedDoB = personWithDateOfBirth.ConvertTo(); + + Assert.That(personWithWrappedDoB.FirstName, Is.EqualTo("Foo")); + Assert.That(personWithWrappedDoB.LastName, Is.EqualTo("Bar")); + Assert.That(personWithWrappedDoB.DateOfBirth, Is.Not.Null); + var dto = personWithWrappedDoB.DateOfBirth.ToDateTimeOffset(); + Assert.That(dto, Is.Not.Null); + Assert.That(dto.Year, Is.EqualTo(1971)); + Assert.That(dto.Month, Is.EqualTo(3)); + Assert.That(dto.Day, Is.EqualTo(23)); + Assert.That(dto.Hour, Is.EqualTo(4)); + Assert.That(dto.Minute, Is.EqualTo(30)); + Assert.That(dto.Second, Is.EqualTo(0)); + + AutoMappingUtils.Reset(); + } + + // TODO: Work out a way we can capture and monitor the Trace log in the test, as exceptions are caught in Populate method + [Test] + public void Should_Not_Throw_Exception_When_Multiple_Same_Type_CustomTypeConverters_Found() + { + AutoMapping.RegisterConverter((DateTimeOffset from) => new WrappedDateTimeOffset(from)); + + var personWithWrappedDateOfBirth = new PersonWithWrappedDateOfBirth + { + DateOfBirth = new WrappedDateTimeOffset( + new DateTimeOffset(1971, 3, 23, 4, 30, 0, TimeSpan.Zero)) + }; + + var personWithDoB = personWithWrappedDateOfBirth.ConvertTo(); + + // Object returned but mapping failed + Assert.That(personWithDoB.FirstName, Is.Null); + Assert.That(personWithDoB.LastName, Is.Null); + Assert.That(personWithDoB.DateOfBirth, Is.EqualTo(DateTimeOffset.MinValue)); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Can_Convert_POCO_collections_with_custom_Converter() + { + AutoMapping.RegisterConverter((User from) => { + var to = from.ConvertTo(skipConverters:true); // avoid infinite recursion + to.FirstName += "!"; + to.LastName += "!"; + return to; + }); + AutoMapping.RegisterConverter((Car from) => $"{from.Name} ({from.Age})"); + + var user = new User { + FirstName = "John", + LastName = "Doe", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + var users = new UsersData { + Id = 1, + User = user, + UsersList = { user }, + UsersMap = { {1,user} } + }; + + var dtoUsers = users.ConvertTo(); + Assert.That(dtoUsers.Id, Is.EqualTo(users.Id)); + + void AssertUser(UserDto userDto) + { + Assert.That(userDto.FirstName, Is.EqualTo(user.FirstName + "!")); + Assert.That(userDto.LastName, Is.EqualTo(user.LastName + "!")); + Assert.That(userDto.Car, Is.EqualTo($"{user.Car.Name} ({user.Car.Age})")); + } + AssertUser(user.ConvertTo()); + AssertUser(dtoUsers.User); + AssertUser(dtoUsers.UsersList[0]); + AssertUser(dtoUsers.UsersMap[1]); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Does_ignore_POCO_mappings() + { + AutoMapping.IgnoreMapping(); + + var user = new User { + FirstName = "John", + LastName = "Doe", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + var users = new UsersData { + Id = 1, + User = user, + UsersList = { user }, + UsersMap = {{1,user}} + }; + + var dtoUsers = users.ConvertTo(); + Assert.That(dtoUsers.Id, Is.EqualTo(users.Id)); + + Assert.That(user.ConvertTo(), Is.Null); + Assert.That(dtoUsers.User, Is.Null); + Assert.That(dtoUsers.UsersList, Is.Empty); + Assert.That(dtoUsers.UsersMap, Is.Empty); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Does_ignore_collection_mappings() + { + AutoMapping.IgnoreMapping, List>(); + AutoMapping.IgnoreMapping, Dictionary>(); + + var users = new UsersData { + Id = 1, + UsersList = new List { + new User { + FirstName = "John", + LastName = "Doe", + Car = new Car { Name = "BMW X6", Age = 3 } + } + } + }; + + var dtoUsers = users.ConvertTo(); + dtoUsers.PrintDump(); + + Assert.That(dtoUsers.Id, Is.EqualTo(users.Id)); + Assert.That(dtoUsers.UsersList, Is.Empty); + Assert.That(dtoUsers.UsersMap, Is.Empty); + + AutoMappingUtils.Reset(); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs new file mode 100644 index 000000000..34a710411 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs @@ -0,0 +1,598 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Northwind.Common.DataModel; +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; + +namespace ServiceStack.Text.Tests +{ + [TestFixture] + public class AutoMappingObjectDictionaryTests + { + [Test] + public void Can_convert_Car_to_ObjectDictionary_WithMapper() + { + var dto = new DtoWithEnum { Name = "Dan", Color = Color.Blue}; + var map = dto.ToObjectDictionary((k,v) => k == nameof(Color) ? v.ToString().ToLower() : v); + + Assert.That(map["Color"], Is.EqualTo(Color.Blue.ToString().ToLower())); + Assert.That(map["Name"], Is.EqualTo(dto.Name)); + } + + [Test] + public void Can_convert_Car_to_ObjectDictionary() + { + var dto = new Car { Age = 10, Name = "ZCar" }; + var map = dto.ToObjectDictionary(); + + Assert.That(map["Age"], Is.EqualTo(dto.Age)); + Assert.That(map["Name"], Is.EqualTo(dto.Name)); + + var fromDict = (Car)map.FromObjectDictionary(typeof(Car)); + Assert.That(fromDict.Age, Is.EqualTo(dto.Age)); + Assert.That(fromDict.Name, Is.EqualTo(dto.Name)); + } + + [Test] + public void Can_convert_Cart_to_ObjectDictionary() + { + var dto = new User + { + FirstName = "First", + LastName = "Last", + Car = new Car { Age = 10, Name = "ZCar" }, + }; + + var map = dto.ToObjectDictionary(); + + Assert.That(map["FirstName"], Is.EqualTo(dto.FirstName)); + Assert.That(map["LastName"], Is.EqualTo(dto.LastName)); + Assert.That(((Car)map["Car"]).Age, Is.EqualTo(dto.Car.Age)); + Assert.That(((Car)map["Car"]).Name, Is.EqualTo(dto.Car.Name)); + + var fromDict = map.FromObjectDictionary(); + Assert.That(fromDict.FirstName, Is.EqualTo(dto.FirstName)); + Assert.That(fromDict.LastName, Is.EqualTo(dto.LastName)); + Assert.That(fromDict.Car.Age, Is.EqualTo(dto.Car.Age)); + Assert.That(fromDict.Car.Name, Is.EqualTo(dto.Car.Name)); + } + + [Test] + public void Can_Convert_from_ObjectDictionary_with_Different_Types() + { + var map = new Dictionary + { + { "FirstName", 1 }, + { "LastName", true }, + { "Car", new SubCar { Age = 10, Name = "SubCar", Custom = "Custom"} }, + }; + + var fromDict = (User)map.FromObjectDictionary(typeof(User)); + Assert.That(fromDict.FirstName, Is.EqualTo("1")); + Assert.That(fromDict.LastName, Is.EqualTo(bool.TrueString)); + Assert.That(fromDict.Car.Age, Is.EqualTo(10)); + Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar")); + } + + [Test] + public void Can_Convert_from_ObjectDictionary_with_Different_Types_with_camelCase_names() + { + var map = new Dictionary + { + { "firstName", 1 }, + { "lastName", true }, + { "car", new SubCar { Age = 10, Name = "SubCar", Custom = "Custom"} }, + }; + + var fromDict = (User)map.FromObjectDictionary(typeof(User)); + Assert.That(fromDict.FirstName, Is.EqualTo("1")); + Assert.That(fromDict.LastName, Is.EqualTo(bool.TrueString)); + Assert.That(fromDict.Car.Age, Is.EqualTo(10)); + Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar")); + } + + [Test] + public void Can_Convert_from_ObjectDictionary_with_Read_Only_Dictionary() + { + var map = new Dictionary + { + { "FirstName", 1 }, + { "LastName", true }, + { "Car", new SubCar { Age = 10, Name = "SubCar", Custom = "Custom"} }, + }; + + var readOnlyMap = new ReadOnlyDictionary(map); + + var fromDict = (User)readOnlyMap.FromObjectDictionary(typeof(User)); + Assert.That(fromDict.FirstName, Is.EqualTo("1")); + Assert.That(fromDict.LastName, Is.EqualTo(bool.TrueString)); + Assert.That(fromDict.Car.Age, Is.EqualTo(10)); + Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar")); + } + + public class QueryCustomers : QueryDb + { + public string CustomerId { get; set; } + public string[] CountryIn { get; set; } + public string[] CityIn { get; set; } + } + + [Test] + public void Can_convert_from_ObjectDictionary_into_AutoQuery_DTO() + { + var map = new Dictionary + { + { "CustomerId", "CustomerId"}, + { "CountryIn", new[]{"UK", "Germany"}}, + { "CityIn", "London,Berlin"}, + { "take", 5 }, + { "Meta", "{foo:bar}" }, + }; + + var request = map.FromObjectDictionary(); + + Assert.That(request.CustomerId, Is.EqualTo("CustomerId")); + Assert.That(request.CountryIn, Is.EquivalentTo(new[]{"UK", "Germany" })); + Assert.That(request.CityIn, Is.EquivalentTo(new[]{ "London", "Berlin" })); + Assert.That(request.Take, Is.EqualTo(5)); + Assert.That(request.Meta, Is.EquivalentTo(new Dictionary {{"foo", "bar"}})); + } + + public class PersonWithIdentities + { + public string Name { get; set; } + public List OtherNames { get;set; } + } + + public class OtherName + { + public string Name { get; set; } + } + + [Test] + public void Can_Convert_from_ObjectDictionary_Containing_Another_Object_Dictionary() + { + var map = new Dictionary + { + { "name", "Foo" }, + { "otherNames", new List + { + new Dictionary { { "name", "Fu" } }, + new Dictionary { { "name", "Fuey" } } + } + } + }; + + var fromDict = map.FromObjectDictionary(); + + Assert.That(fromDict.Name, Is.EqualTo("Foo")); + Assert.That(fromDict.OtherNames.Count, Is.EqualTo(2)); + Assert.That(fromDict.OtherNames.First().Name, Is.EqualTo("Fu")); + Assert.That(fromDict.OtherNames.Last().Name, Is.EqualTo("Fuey")); + + var toDict = fromDict.ToObjectDictionary(); + Assert.That(toDict["Name"], Is.EqualTo("Foo")); + Assert.That(toDict["OtherNames"], Is.EqualTo(fromDict.OtherNames)); + } + + public class Employee + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string DisplayName { get; set; } + } + + [Test] + public void Can_create_new_object_using_MergeIntoObjectDictionary() + { + var customer = new User { FirstName = "John", LastName = "Doe" }; + var map = customer.MergeIntoObjectDictionary(new { Initial = "Z" }); + map["DisplayName"] = map["FirstName"] + " " + map["Initial"] + " " + map["LastName"]; + var employee = map.FromObjectDictionary(); + + Assert.That(employee.DisplayName, Is.EqualTo("John Z Doe")); + } + + [Test] + public void Can_create_new_object_from_merged_objects() + { + var customer = new User { FirstName = "John", LastName = "Doe" }; + var map = MergeObjects(customer, new { Initial = "Z" }); + map["DisplayName"] = map["FirstName"] + " " + map["Initial"] + " " + map["LastName"]; + var employee = map.FromObjectDictionary(); + + Dictionary MergeObjects(params object[] sources) { + var to = new Dictionary(); + sources.Each(x => x.ToObjectDictionary().Each(entry => to[entry.Key] = entry.Value)); + return to; + } + + Assert.That(employee.DisplayName, Is.EqualTo("John Z Doe")); + } + + [Test, TestCaseSource(nameof(TestDataFromObjectDictionaryWithNullableTypes))] + public void Can_Convert_from_ObjectDictionary_with_Nullable_Properties( + Dictionary map, + ModelWithFieldsOfNullableTypes expected) + { + var actual = map.FromObjectDictionary(); + + ModelWithFieldsOfNullableTypes.AssertIsEqual(actual, expected); + } + + private static IEnumerable TestDataFromObjectDictionaryWithNullableTypes + { + get + { + var defaults = ModelWithFieldsOfNullableTypes.CreateConstant(1); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NId", defaults.NId }, + { "NLongId", defaults.NLongId }, + { "NGuid", defaults.NGuid }, + { "NBool", defaults.NBool }, + { "NDateTime", defaults.NDateTime }, + { "NFloat", defaults.NFloat }, + { "NDouble", defaults.NDouble }, + { "NDecimal", defaults.NDecimal }, + { "NTimeSpan", defaults.NTimeSpan } + }, + defaults).SetName("All values populated"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id.ToString() }, + { "NId", defaults.NId.ToString() }, + { "NLongId", defaults.NLongId.ToString() }, + { "NGuid", defaults.NGuid.ToString() }, + { "NBool", defaults.NBool.ToString() }, + { "NDateTime", defaults.NDateTime?.ToString("o") }, + { "NFloat", defaults.NFloat.ToString() }, + { "NDouble", defaults.NDouble.ToString() }, + { "NDecimal", defaults.NDecimal.ToString() }, + { "NTimeSpan", defaults.NTimeSpan.ToString() } + }, + defaults).SetName("All values populated as strings"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NId", null }, + { "NLongId", null }, + { "NGuid", null }, + { "NBool", null }, + { "NDateTime", null }, + { "NFloat", null }, + { "NDouble", null }, + { "NDecimal", null }, + { "NTimeSpan", null } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id + }).SetName("Nullables set to null"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id + }).SetName("Nullables unassigned"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NLongId", 2 }, + { "NFloat", "3.1" }, + { "NDecimal", 4.2d }, + { "NTimeSpan", null } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id, + NLongId = 2, + NFloat = 3.1f, + NDecimal = 4.2m + }).SetName("Mixed properties"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NMadeUp", 99.9 }, + { "NLongId", 2 }, + { "NFloat", "3.1" }, + { "NRandom", "RANDOM" }, + { "NDecimal", 4.2d }, + { "NTimeSpan", null }, + { "NNull", null } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id, + NLongId = 2, + NFloat = 3.1f, + NDecimal = 4.2m + }).SetName("Mixed properties with some foreign key/values"); + } + } + + [Test] + public void Can_Convert_from_ObjectDictionary_with_Nullable_Collection_Properties() + { + var map = new Dictionary + { + { "Id", 1 }, + { "Users", new[] { new User { FirstName = "Foo", LastName = "Bar", Car = new Car { Name = "Jag", Age = 25 }}}}, + { "Cars", new List { new Car { Name = "Toyota", Age = 2 }, new Car { Name = "Lexus", Age = 1 }}}, + { "Colors", null } + }; + + var actual = map.FromObjectDictionary(); + + Assert.That(actual.Id, Is.EqualTo(1)); + Assert.That(actual.Users, Is.Not.Null); + Assert.That(actual.Users.Count(), Is.EqualTo(1)); + var user = actual.Users.Single(); + Assert.That(user.FirstName, Is.EqualTo("Foo")); + Assert.That(user.LastName, Is.EqualTo("Bar")); + Assert.That(user.Car, Is.Not.Null); + Assert.That(user.Car.Name, Is.EqualTo("Jag")); + Assert.That(user.Car.Age, Is.EqualTo(25)); + Assert.That(actual.Cars, Is.Not.Null); + Assert.That(actual.Cars.Count, Is.EqualTo(2)); + var firstCar = actual.Cars.First(); + Assert.That(firstCar.Name, Is.EqualTo("Toyota")); + Assert.That(firstCar.Age, Is.EqualTo(2)); + var secondCar = actual.Cars.Last(); + Assert.That(secondCar.Name, Is.EqualTo("Lexus")); + Assert.That(secondCar.Age, Is.EqualTo(1)); + Assert.That(actual.Colors, Is.Null); + } + + public class ModelWithCollectionsOfNullableTypes + { + public int Id { get; set; } + public IEnumerable Users { get; set; } + public Car[] Cars { get; set; } + public IList Colors { get; set; } + } + + public class ModelWithTimeSpan + { + public TimeSpan Time { get; set; } + } + + public class ModelWithLong + { + public long Time { get; set; } + } + + [Test] + public void FromObjectDictionary_does_Convert_long_to_TimeSpan() + { + var time = new TimeSpan(1,1,1,1); + var map = new Dictionary { + [nameof(ModelWithTimeSpan.Time)] = time.Ticks + }; + + var dto = map.FromObjectDictionary(); + Assert.That(dto.Time, Is.EqualTo(time)); + + map = new Dictionary { + [nameof(ModelWithTimeSpan.Time)] = time + }; + + var dtoLong = map.FromObjectDictionary(); + Assert.That(dtoLong.Time, Is.EqualTo(time.Ticks)); + } + + public enum CefLogSeverity + { + Default, + Verbose, + Debug = Verbose, + Info, + Warning, + Error, + ErrorReport, + Disable = 99, + } + + public class CefSettings + { + public CefLogSeverity LogSeverity { get; set; } + } + + public class CefConfig + { + public string WindowTitle { get; set; } + public string Icon { get; set; } + public string CefPath { get; set; } + public string[] Args { get; set; } + public CefSettings CefSettings { get; set; } + public string StartUrl { get; set; } + public int? X { get; set; } + public int? Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public bool CenterToScreen { get; set; } + public bool HideConsoleWindow { get; set; } + } + + [Test] + public void Can_use_PopulateInstance_to_populate_enum() + { + var map = new Dictionary { + ["LogSeverity"] = "Verbose" + }; + + var config = new CefConfig { CefSettings = new CefSettings { LogSeverity = CefLogSeverity.Info } }; + map.PopulateInstance(config.CefSettings); + + Assert.That(config.CefSettings.LogSeverity, Is.EqualTo(CefLogSeverity.Verbose)); + } + + [Test] + public void Can_convert_Dictionary_FromObjectDictionary() + { + var dict = new Dictionary(); + var to = dict.FromObjectDictionary>(); + Assert.That(to == dict); + } + + [Test] + public void Can_convert_inner_dictionary() + { + var map = new Dictionary + { + { "FirstName", "Foo" }, + { "LastName", "Bar" }, + { "Car", new Dictionary + { + { "Name", "Tesla" }, + { "Age", 2 } + }} + }; + + var user = map.FromObjectDictionary(); + + Assert.That(user.FirstName, Is.EqualTo("Foo")); + Assert.That(user.LastName, Is.EqualTo("Bar")); + Assert.That(user.Car, Is.Not.Null); + Assert.That(user.Car.Name, Is.EqualTo("Tesla")); + Assert.That(user.Car.Age, Is.EqualTo(2)); + } + + [Test] + public void Can_convert_inner_collection_of_dictionaries() + { + var map = new Dictionary + { + { "Name", "Tesla" }, + { "Age", "2" }, + { "Specs", new List> + { + new Dictionary + { + {"Item", "Model"}, + {"Value", "S"} + }, + new Dictionary + { + {"Item", "Engine"}, + {"Value", "Electric"} + }, + new Dictionary + { + {"Item", "Color"}, + {"Value", "Red"} + }, + new Dictionary + { + {"Item", "PowerKW"}, + {"Value", 285} + }, + new Dictionary + { + {"Item", "TorqueNm"}, + {"Value", 430} + }, + }} + }; + + var carWithSpecs = map.FromObjectDictionary(); + + Assert.That(carWithSpecs.Name, Is.EqualTo("Tesla")); + Assert.That(carWithSpecs.Age, Is.EqualTo(2)); + Assert.That(carWithSpecs.Specs.Count, Is.EqualTo(5)); + var model = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Model"); + Assert.That(model, Is.Not.Null); + Assert.That(model.Value, Is.EqualTo("S")); + var engine = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Engine"); + Assert.That(engine, Is.Not.Null); + Assert.That(engine.Value, Is.EqualTo("Electric")); + var color = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Color"); + Assert.That(color, Is.Not.Null); + Assert.That(color.Value, Is.EqualTo("Red")); + var power = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "PowerKW"); + Assert.That(power, Is.Not.Null); + Assert.That(power.Value, Is.EqualTo("285")); + var torque = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "TorqueNm"); + Assert.That(torque, Is.Not.Null); + Assert.That(torque.Value, Is.EqualTo("430")); + } + + [Test] + public void Can_convert_inner_array_of_dictionaries() + { + var map = new Dictionary + { + { "Name", "Tesla" }, + { "Age", "2" }, + { "Specs", new[] + { + new Dictionary + { + {"Item", "Model"}, + {"Value", "S"} + }, + new Dictionary + { + {"Item", "Engine"}, + {"Value", "Electric"} + }, + new Dictionary + { + {"Item", "Color"}, + {"Value", "Red"} + }, + new Dictionary + { + {"Item", "PowerKW"}, + {"Value", 285} + }, + new Dictionary + { + {"Item", "TorqueNm"}, + {"Value", 430} + }, + }} + }; + + var carWithSpecs = map.FromObjectDictionary(); + + Assert.That(carWithSpecs.Name, Is.EqualTo("Tesla")); + Assert.That(carWithSpecs.Age, Is.EqualTo(2)); + Assert.That(carWithSpecs.Specs.Count, Is.EqualTo(5)); + var model = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Model"); + Assert.That(model, Is.Not.Null); + Assert.That(model.Value, Is.EqualTo("S")); + var engine = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Engine"); + Assert.That(engine, Is.Not.Null); + Assert.That(engine.Value, Is.EqualTo("Electric")); + var color = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Color"); + Assert.That(color, Is.Not.Null); + Assert.That(color.Value, Is.EqualTo("Red")); + var power = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "PowerKW"); + Assert.That(power, Is.Not.Null); + Assert.That(power.Value, Is.EqualTo("285")); + var torque = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "TorqueNm"); + Assert.That(torque, Is.Not.Null); + Assert.That(torque.Value, Is.EqualTo("430")); + } + } + + +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AutoMappingPopulatorTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingPopulatorTests.cs new file mode 100644 index 000000000..54aca58dd --- /dev/null +++ b/tests/ServiceStack.Text.Tests/AutoMappingPopulatorTests.cs @@ -0,0 +1,73 @@ +using NUnit.Framework; + +namespace ServiceStack.Text.Tests +{ + public class AutoMappingPopulatorTests + { + public class UserData + { + public string FirstName { get; set; } + public string LastName { get; set; } + public Car Car { get; set; } + } + + private static UserData CreateUser() => + new UserData { + FirstName = "John", + LastName = "Doe", + Car = new Car {Name = "BMW X6", Age = 3} + }; + + [Test] + public void Does_call_populator_for_PopulateWith() + { + AutoMapping.RegisterPopulator((UserDto target, UserData source) => + target.LastName += "?!"); + + var user = CreateUser(); + var dtoUser = new UserDto().PopulateWith(user); + + Assert.That(dtoUser.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(dtoUser.LastName, Is.EqualTo(user.LastName + "?!")); + } + + [Test] + public void Does_call_populator_for_PopulateWithNonDefaultValues() + { + AutoMapping.RegisterPopulator((UserDto target, UserData source) => + target.LastName += "?!"); + + var user = CreateUser(); + var dtoUser = new UserDto().PopulateWithNonDefaultValues(user); + + Assert.That(dtoUser.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(dtoUser.LastName, Is.EqualTo(user.LastName + "?!")); + } + + [Test] + public void Does_call_populator_for_PopulateFromPropertiesWithoutAttribute() + { + AutoMapping.RegisterPopulator((UserDto target, UserData source) => + target.LastName += "?!"); + + var user = CreateUser(); + var dtoUser = new UserDto().PopulateFromPropertiesWithoutAttribute(user, typeof(IgnoreAttribute)); + + Assert.That(dtoUser.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(dtoUser.LastName, Is.EqualTo(user.LastName + "?!")); + } + + [Test] + public void Does_call_populator_for_ConvertTo() + { + AutoMapping.RegisterPopulator((UserDto target, UserData source) => + target.LastName += "?!"); + + var user = CreateUser(); + var dtoUser = user.ConvertTo(); + + Assert.That(dtoUser.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(dtoUser.LastName, Is.EqualTo(user.LastName + "?!")); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AutoMappingTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingTests.cs new file mode 100644 index 000000000..9701c7478 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/AutoMappingTests.cs @@ -0,0 +1,1602 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.Serialization; +using System.Xml.Linq; +#if !NETCORE +using System.Web.Script.Serialization; +#endif +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.DataAnnotations; +using ServiceStack.Text.Tests.DynamicModels; +using ServiceStack.Web; + +namespace ServiceStack.Text.Tests +{ + public class User + { + public string FirstName { get; set; } + [DataMember] + public string LastName { get; set; } + public Car Car { get; set; } + } + + public class UserFields + { + public string FirstName; + public string LastName; + public Car Car; + } + + public class SubUser : User { } + public class SubUserFields : UserFields { } + + public class Car + { + public string Name { get; set; } + public int Age { get; set; } + } + + public class SubCar : Car + { + public string Custom { get; set; } + } + + public class CarWithSpecs : Car + { + public List Specs { get; set; } + } + + public class CarSpec + { + public string Item { get; set; } + public string Value { get; set; } + } + + public class UserDto + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string Car { get; set; } + } + + public class UsersData + { + public int Id { get; set; } + + public User User { get; set; } + public List UsersList { get; set; } = new List(); + public Dictionary UsersMap { get; set; } = new Dictionary(); + } + public class UsersDto + { + public int Id { get; set; } + + public UserDto User { get; set; } + public List UsersList { get; set; } = new List(); + public Dictionary UsersMap { get; set; } = new Dictionary(); + } + + public enum Color + { + Red, + Green, + Blue + } + + public enum OtherColor + { + Red, + Green, + Blue + } + + + public class IntNullableId + { + public int? Id { get; set; } + } + + public class IntId + { + public int Id { get; set; } + } + + public class BclTypes + { + public int Int { get; set; } + public long Long { get; set; } + public double Double { get; set; } + public decimal Decimal { get; set; } + } + + public class BclTypeStrings + { + public string Int { get; set; } + public string Long { get; set; } + public string Double { get; set; } + public string Decimal { get; set; } + } + + public class NullableConversion + { + public decimal Amount { get; set; } + } + + public class NullableConversionDto + { + public decimal? Amount { get; set; } + } + + public class DtoWithEnum + { + public string Name { get; set; } + public Color Color { get; set; } + } + + public class NullableEnumConversion + { + public Color Color { get; set; } + } + + public class ReallyNullableEnumConversion + { + public Color? Color { get; set; } + } + + public class EnumConversion + { + public Color Color { get; set; } + public Color? NullableColor { get; set; } + } + + public class NullableEnumConversionDto + { + public OtherColor? Color { get; set; } + } + + public class EnumConversionDto + { + public OtherColor Color { get; set; } + } + + public class EnumConversionString + { + public string Color { get; set; } + public string NullableColor { get; set; } + } + + public class EnumConversionInt + { + public int Color { get; set; } + public int? NullableColor { get; set; } + } + + public class ModelWithEnumerable + { + public IEnumerable Collection { get; set; } + } + + public class ModelWithList + { + public List Collection { get; set; } + } + + public class ModelWithArray + { + public User[] Collection { get; set; } + } + + public class ModelWithHashSet + { + public HashSet Collection { get; set; } + } + + public class ModelWithIgnoredFields + { + public int Id { get; set; } + public string Name { get; set; } + + [ReadOnly] + public int Ignored { get; set; } + } + + public class ReadOnlyAttribute : AttributeBase { } + + [EnumAsInt] + public enum MyEnumAsInt + { + ZeroValue = 0, + OneValue = 1, + DefaultValue = 2, + }; + + class MyEnumAsIntSource + { + public MyEnumAsInt MyEnumAsInt { get; set; } = MyEnumAsInt.DefaultValue; + } + + class MyEnumAsIntTarget + { + public MyEnumAsInt MyEnumAsInt { get; set; } = MyEnumAsInt.DefaultValue; + } + + [TestFixture] + public class AutoMappingTests + { + [Test] + public void Can_convert_Default_Enum_Values() + { + var from = new MyEnumAsIntSource { MyEnumAsInt = MyEnumAsInt.ZeroValue }; + var to = from.ConvertTo(); + + Assert.That(to.MyEnumAsInt, Is.EqualTo(from.MyEnumAsInt)); + } + + [Test] + public void Does_populate() + { + var user = new User { + FirstName = "Demis", + LastName = "Bellot", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + + var userDto = new UserDto().PopulateWith(user); + + Assert.That(userDto.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(userDto.LastName, Is.EqualTo(user.LastName)); + Assert.That(userDto.Car, Is.EqualTo("{Name:BMW X6,Age:3}")); + } + + [Test] + public void Does_translate() + { + var user = new User + { + FirstName = "Demis", + LastName = "Bellot", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + + var userDto = user.ConvertTo(); + + Assert.That(userDto.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(userDto.LastName, Is.EqualTo(user.LastName)); + Assert.That(userDto.Car, Is.EqualTo("{Name:BMW X6,Age:3}")); + } + + [Test] + public void Does_enumstringconversion_translate() + { + var conversion = new EnumConversion { Color = Color.Blue }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo("Blue")); + } + + [Test] + public void Does_convert_to_EnumConversionInt() + { + var conversion = new EnumConversion + { + Color = Color.Green, + NullableColor = Color.Green, + }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo(1)); + Assert.That(conversionDto.NullableColor, Is.EqualTo(1)); + } + + [Test] + public void Does_convert_from_EnumConversionInt() + { + var conversion = new EnumConversionInt + { + Color = 1, + NullableColor = 1, + }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo(Color.Green)); + Assert.That(conversionDto.NullableColor, Is.EqualTo(Color.Green)); + } + + [Test] + public void Does_convert_to_EnumConversionString() + { + var conversion = new EnumConversion + { + Color = Color.Green, + NullableColor = Color.Green, + }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo("Green")); + Assert.That(conversionDto.NullableColor, Is.EqualTo("Green")); + } + + [Test] + public void Does_convert_from_EnumConversionString() + { + var conversion = new EnumConversionString + { + Color = "Green", + NullableColor = "Green", + }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo(Color.Green)); + Assert.That(conversionDto.NullableColor, Is.EqualTo(Color.Green)); + } + + [Test] + public void Does_nullableconversion_translate() + { + var conversion = new NullableConversion { Amount = 123.45m }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Amount, Is.EqualTo(123.45m)); + } + + [Test] + public void Does_Enumnullableconversion_translate() + { + var conversion = new NullableEnumConversion { Color = Color.Green }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo(OtherColor.Green)); + } + + [Test] + public void Does_Enumconversion_translate() + { + var conversion = new NullableEnumConversion { Color = Color.Green }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo(OtherColor.Green)); + } + + [Test] + public void Does_ReallyEnumnullableconversion_translate() + { + var conversion = new ReallyNullableEnumConversion { Color = Color.Green }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo(OtherColor.Green)); + } + + [Test] + public void Does_RealyEnumconversion_translate() + { + var conversion = new ReallyNullableEnumConversion { Color = Color.Green }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo(OtherColor.Green)); + } + + [Test] + public void Does_Enumconversion_translateFromNull() + { + var conversion = new ReallyNullableEnumConversion { Color = null }; + var conversionDto = conversion.ConvertTo(); + + Assert.That(conversionDto.Color, Is.EqualTo(default(OtherColor))); + } + + [Test] + public void Does_translate_nullableInt_to_and_from() + { + var nullable = new IntNullableId(); + + var nonNullable = nullable.ConvertTo(); + + nonNullable.Id = 10; + + var expectedNullable = nonNullable.ConvertTo(); + + Assert.That(expectedNullable.Id.Value, Is.EqualTo(nonNullable.Id)); + } + + [Test] + public void Does_translate_from_properties_to_fields() + { + var user = new User + { + FirstName = "Demis", + LastName = "Bellot", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + + var to = user.ConvertTo(); + Assert.That(to.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(to.LastName, Is.EqualTo(user.LastName)); + Assert.That(to.Car.Name, Is.EqualTo(user.Car.Name)); + Assert.That(to.Car.Age, Is.EqualTo(user.Car.Age)); + } + + [Test] + public void Does_translate_from_fields_to_properties() + { + var user = new UserFields + { + FirstName = "Demis", + LastName = "Bellot", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + + var to = user.ConvertTo(); + Assert.That(to.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(to.LastName, Is.EqualTo(user.LastName)); + Assert.That(to.Car.Name, Is.EqualTo(user.Car.Name)); + Assert.That(to.Car.Age, Is.EqualTo(user.Car.Age)); + } + + [Test] + public void Does_translate_from_inherited_propeties() + { + var user = new SubUser + { + FirstName = "Demis", + LastName = "Bellot", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + + var to = user.ConvertTo(); + Assert.That(to.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(to.LastName, Is.EqualTo(user.LastName)); + Assert.That(to.Car.Name, Is.EqualTo(user.Car.Name)); + Assert.That(to.Car.Age, Is.EqualTo(user.Car.Age)); + } + + [Test] + public void Does_translate_to_inherited_propeties() + { + var user = new User + { + FirstName = "Demis", + LastName = "Bellot", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + + var to = user.ConvertTo(); + Assert.That(to.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(to.LastName, Is.EqualTo(user.LastName)); + Assert.That(to.Car.Name, Is.EqualTo(user.Car.Name)); + Assert.That(to.Car.Age, Is.EqualTo(user.Car.Age)); + } + + [Test] + public void Can_convert_BclTypes() + { + Assert.That("from".ConvertTo(), Is.EqualTo("from")); + Assert.That(1.ConvertTo(), Is.EqualTo(1L)); + Assert.That(2L.ConvertTo(), Is.EqualTo(2)); + Assert.That(3.3d.ConvertTo(), Is.EqualTo(3.3f)); + Assert.That(4.4d.ConvertTo(), Is.EqualTo(4.4m)); + } + + [Test] + public void Does_coerce_from_BclTypes_to_strings() + { + var from = new BclTypes + { + Int = 1, + Long = 2, + Double = 3.3, + Decimal = 4.4m, + }; + + var to = from.ConvertTo(); + Assert.That(to.Int, Is.EqualTo("1")); + Assert.That(to.Long, Is.EqualTo("2")); + Assert.That(to.Double, Is.EqualTo("3.3")); + Assert.That(to.Decimal, Is.EqualTo("4.4")); + } + + [Test] + public void Does_coerce_from_strings_to_BclTypes() + { + var from = new BclTypeStrings + { + Int = "1", + Long = "2", + Double = "3.3", + Decimal = "4.4", + }; + + var to = from.ConvertTo(); + Assert.That(to.Int, Is.EqualTo(1)); + Assert.That(to.Long, Is.EqualTo(2)); + Assert.That(to.Double, Is.EqualTo(3.3d)); + Assert.That(to.Decimal, Is.EqualTo(4.4m)); + } + + [Test] + public void Does_map_only_properties_with_specified_Attribute() + { + var user = new User + { + FirstName = "Demis", + LastName = "Bellot", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + + var to = new User(); + to.PopulateFromPropertiesWithAttribute(user, typeof(DataMemberAttribute)); + + Assert.That(to.LastName, Is.EqualTo(user.LastName)); + Assert.That(to.FirstName, Is.Null); + Assert.That(to.Car, Is.Null); + } + + [Test] + public void Does_convert_ModelWithAllTypes() + { + var to = ModelWithAllTypes.Create(1); + var from = to.ConvertTo(); + + Assert.That(to.Equals(from)); + } + + public bool MatchesUsers(IEnumerable u1s, IEnumerable u2s) + { + if (u1s == null || u2s == null) + return false; + + var u1sList = u1s.ToList(); + var u2sList = u2s.ToList(); + + if (u1sList.Count != u2sList.Count) + return false; + + for (var i = 0; i < u1sList.Count; i++) + { + var u1 = u1sList[i]; + var u2 = u2sList[i]; + + if (u1.FirstName != u2.FirstName) + return false; + if (u1.LastName != u2.LastName) + return false; + if (u1.Car.Name != u2.Car.Name) + return false; + if (u1.Car.Age != u2.Car.Age) + return false; + } + + return true; + } + + [Test] + public void Does_convert_models_with_collections() + { + var from = new ModelWithEnumerable + { + Collection = new[] { + new User { FirstName = "First1", LastName = "Last1", Car = new Car { Name = "Car1", Age = 1} }, + new User { FirstName = "First2", LastName = "Last2", Car = new Car { Name = "Car2", Age = 2} }, + } + }; + + Assert.That(MatchesUsers(from.Collection, from.ConvertTo().Collection)); + Assert.That(MatchesUsers(from.Collection, from.ConvertTo().Collection)); + Assert.That(MatchesUsers(from.Collection, from.ConvertTo().Collection)); + Assert.That(MatchesUsers(from.Collection, from.ConvertTo().Collection)); + + Assert.That(MatchesUsers(from.Collection, from.Collection.ConvertTo>())); + Assert.That(MatchesUsers(from.Collection, from.Collection.ConvertTo>())); + Assert.That(MatchesUsers(from.Collection, from.Collection.ConvertTo())); + Assert.That(MatchesUsers(from.Collection, from.Collection.ConvertTo>())); + + var array = from.Collection.ToArray(); + Assert.That(MatchesUsers(array, from.Collection.ConvertTo>())); + Assert.That(MatchesUsers(array, from.Collection.ConvertTo>())); + Assert.That(MatchesUsers(array, from.Collection.ConvertTo())); + Assert.That(MatchesUsers(array, from.Collection.ConvertTo>())); + + var hashset = from.Collection.ToSet(); + Assert.That(MatchesUsers(hashset, from.Collection.ConvertTo>())); + Assert.That(MatchesUsers(hashset, from.Collection.ConvertTo>())); + Assert.That(MatchesUsers(hashset, from.Collection.ConvertTo())); + Assert.That(MatchesUsers(hashset, from.Collection.ConvertTo>())); + } + + public class Customer + { + public CompanyInfo CompanyInfo { get; set; } + // other properties + } + + public class CustomerDto + { + public CompanyInfoDto CompanyInfo { get; set; } + } + + public class CompanyInfo + { + public int Id { get; set; } + public string ITN { get; set; } + } + + public class CompanyInfoDto + { + public int Id { get; set; } + public string ITN { get; set; } + } + + [Test] + public void Does_retain_null_properties() + { + var user = new User { FirstName = "Foo" }; + var userDto = user.ConvertTo(); + + Assert.That(userDto.FirstName, Is.EqualTo("Foo")); + Assert.That(userDto.LastName, Is.Null); + Assert.That(userDto.Car, Is.Null); + + var customer = new Customer { CompanyInfo = null }; + var dto = customer.ConvertTo(); + + Assert.That(dto.CompanyInfo, Is.Null); + } + + [Test] + public void Does_ignore_properties_without_attributes() + { + var model = new ModelWithIgnoredFields + { + Id = 1, + Name = "Foo", + Ignored = 2 + }; + + var dto = new ModelWithIgnoredFields { Ignored = 10 } + .PopulateFromPropertiesWithoutAttribute(model, typeof(ReadOnlyAttribute)); + + Assert.That(dto.Id, Is.EqualTo(model.Id)); + Assert.That(dto.Name, Is.EqualTo(model.Name)); + Assert.That(dto.Ignored, Is.EqualTo(10)); + } + +#if !NETCORE + public class IgnoredModel + { + public int Id { get; set; } + + [IgnoreDataMember] + public int DataMemberIgnoreId { get; set; } + + [JsonIgnore] + public int JsonIgnoreId { get; set; } + + [ScriptIgnore] + public int ScriptIgnoreId { get; set; } + } + + //Matches JSON.NET's [JsonIgnore] by name + public class JsonIgnoreAttribute : AttributeBase { } + + [Test] + public void Can_change_ignored_properties() + { + JsConfig.IgnoreAttributesNamed = JsConfig.IgnoreAttributesNamed.NewArray( + with: typeof(ScriptIgnoreAttribute).Name, + without: typeof(JsonIgnoreAttribute).Name); + + var dto = new IgnoredModel { JsonIgnoreId = 1, ScriptIgnoreId = 2 }; + + Assert.That(dto.ToJson(), Is.EqualTo("{\"Id\":0,\"JsonIgnoreId\":1}")); + } +#endif + + [Test] + public void Does_convert_to_ValueType() + { + Assert.That("1".ConvertTo(typeof(int)), Is.EqualTo(1)); + Assert.That("1".ConvertTo(typeof(long)), Is.EqualTo(1L)); + Assert.That("1.1".ConvertTo(typeof(float)), Is.EqualTo(1.1f)); + Assert.That("1.1".ConvertTo(typeof(double)), Is.EqualTo(1.1d)); + Assert.That("1.1".ConvertTo(typeof(decimal)), Is.EqualTo(1.1M)); + + Assert.That("2001-01-01".ConvertTo(), Is.EqualTo(new DateTime(2001, 01, 01))); + Assert.That("98ece8400be4452eb6ad7c3a4404f119".ConvertTo(), Is.EqualTo(new Guid("98ece8400be4452eb6ad7c3a4404f119"))); + } + + [Test] + public void Does_convert_from_ValueType_to_strings() + { + Assert.That(1.ConvertTo(typeof(string)), Is.EqualTo("1")); + Assert.That(1L.ConvertTo(typeof(string)), Is.EqualTo("1")); + Assert.That(1.1f.ConvertTo(typeof(string)), Is.EqualTo("1.1")); + Assert.That(1.1d.ConvertTo(typeof(string)), Is.EqualTo("1.1")); + Assert.That(1.1M.ConvertTo(typeof(string)), Is.EqualTo("1.1")); + + Assert.That(new DateTime(2001, 01, 01).ConvertTo(), Is.EqualTo("2001-01-01")); + Assert.That(new Guid("98ECE840-0BE4-452E-B6AD-7C3A4404F119").ConvertTo(), Is.EqualTo("98ece8400be4452eb6ad7c3a4404f119")); + } + + [Test] + public void Can_convert_from_List_object() + { + var from = 3.Times(i => (object)new Car { Age = i, Name = "Name" + i }); + var to = (List)TranslateListWithElements.TryTranslateCollections( + typeof(List), typeof(List), from); + + Assert.That(to.Count, Is.EqualTo(3)); + Assert.That(to[0].Age, Is.EqualTo(0)); + Assert.That(to[0].Name, Is.EqualTo("Name0")); + } + + [Test] + public void Can_convert_from_List_SubType() + { + var from = 3.Times(i => new SubCar { Age = i, Name = "Name" + i }); + var to = (List)TranslateListWithElements.TryTranslateCollections( + typeof(List), typeof(List), from); + + Assert.That(to.Count, Is.EqualTo(3)); + Assert.That(to[0].Age, Is.EqualTo(0)); + Assert.That(to[0].Name, Is.EqualTo("Name0")); + } + + [Test] + public void Can_create_Dictionary_default_value() + { + var obj = (Dictionary)AutoMappingUtils.CreateDefaultValue(typeof(Dictionary), new Dictionary()); + Assert.That(obj, Is.Not.Null); + } + + [Test] + public void Can_get_generic_types_from_Dictionary_or_KVPs() + { + typeof(Dictionary).GetKeyValuePairsTypes(out var keyType, out var valueType); + Assert.That(keyType, Is.EqualTo(typeof(string))); + Assert.That(valueType, Is.EqualTo(typeof(object))); + + typeof(IEnumerable>).GetKeyValuePairsTypes(out keyType, out valueType); + Assert.That(keyType, Is.EqualTo(typeof(string))); + Assert.That(valueType, Is.EqualTo(typeof(object))); + + typeof(List>).GetKeyValuePairsTypes(out keyType, out valueType); + Assert.That(keyType, Is.EqualTo(typeof(string))); + Assert.That(valueType, Is.EqualTo(typeof(object))); + } + + [Test] + public void Can_convert_between_different_types_of_Dictionaries_and_KVP_values() + { + var genericMap = new Dictionary { + { "a", 1 } + }; + var stringMap = new Dictionary { + {"a", "1"} + }; + var intMap = new Dictionary { + {"a", 1} + }; + var kvps = new List> { + new KeyValuePair("a", 1) + }; + var stringKvps = new List> { + new KeyValuePair("a", "1") + }; + var objDict = new ObjectDictionary { + { "a", 1 } + }; + + var genericMapStringValue = new Dictionary { + { "a", "1" } + }; + var objDictStringValue = new ObjectDictionary { + { "a", "1" } + }; + var kvpsStringValue = new List> { + new KeyValuePair("a", "1") + }; + + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(genericMap.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(genericMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(genericMap.ConvertTo(), Is.EquivalentTo(objDict)); + + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(genericMapStringValue)); + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(stringMap.ConvertTo>>(), Is.EquivalentTo(kvpsStringValue)); + Assert.That(stringMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(stringMap.ConvertTo(), Is.EquivalentTo(objDictStringValue)); + + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(intMap.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(intMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(intMap.ConvertTo(), Is.EquivalentTo(objDict)); + + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(kvps.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(kvps.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(kvps.ConvertTo(), Is.EquivalentTo(objDict)); + + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(genericMapStringValue)); + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(stringKvps.ConvertTo>>(), Is.EquivalentTo(kvpsStringValue)); + Assert.That(stringKvps.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(stringKvps.ConvertTo(), Is.EquivalentTo(objDictStringValue)); + } + + [Test] + public void Can_convert_between_different_types_of_Dictionaries_and_KVP_keys() + { + var genericMap = new Dictionary { + { 1, "a" } + }; + var stringMap = new Dictionary { + {"1","a"} + }; + var intMap = new Dictionary { + {1,"a"} + }; + var kvps = new List> { + new KeyValuePair(1, "a") + }; + var stringKvps = new List> { + new KeyValuePair("1","a") + }; + + var genericMapStringValue = new Dictionary { + { "1","a" } + }; + var kvpsStringValue = new List> { + new KeyValuePair("1","a") + }; + + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(genericMap.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(genericMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(genericMapStringValue)); + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(stringMap.ConvertTo>>(), Is.EquivalentTo(kvpsStringValue)); + Assert.That(stringMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(intMap.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(intMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(kvps.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(kvps.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(genericMapStringValue)); + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(stringKvps.ConvertTo>>(), Is.EquivalentTo(kvpsStringValue)); + Assert.That(stringKvps.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + } + + class HasObject + { + public object Value { get; set; } + } + + [Test] + public void Can_convert_dictionary_to_HasObject() + { + var objDict = new ObjectDictionary { + ["value"] = new ObjectDictionary { + ["a"] = 1 + } + }; + + var to = objDict.FromObjectDictionary(); + Assert.That(to.Value, Is.EqualTo(objDict["value"])); + + var kvps = new ObjectDictionary { + ["value"] = new List> { + new KeyValuePair("a",1) + } + }; + + to = objDict.FromObjectDictionary(); + Assert.That(to.Value, Is.EqualTo(objDict["value"])); + } + + } + + public enum ClassWithEnumType + { + One = 1, + Two = 2, + Three = 3 + } + + public class ClassWithEnum + { + public ClassWithEnumType Type { get; set; } + } + + public class Test + { + public string Name { get; set; } + } + + public class PropertyExpressionTests + { + [Test] + public void Can_call_typed_setter_Expressions() + { + var nameProperty = typeof(Test).GetProperty("Name"); + var setMethod = nameProperty.GetSetMethod(); + + var instance = Expression.Parameter(typeof(Test), "i"); + var argument = Expression.Parameter(typeof(string), "a"); + + var setterCall = Expression.Call(instance, setMethod, argument); + var fn = Expression.Lambda>(setterCall, instance, argument).Compile(); + + var test = new Test(); + fn(test, "Foo"); + Assert.That(test.Name, Is.EqualTo("Foo")); + } + + [Test] + public void Can_call_object_setter_Expressions() + { + var nameProperty = typeof(Test).GetProperty("Name"); + + var instance = Expression.Parameter(typeof(object), "i"); + var argument = Expression.Parameter(typeof(object), "a"); + + var instanceParam = Expression.Convert(instance, nameProperty.ReflectedType); + var valueParam = Expression.Convert(argument, nameProperty.PropertyType); + + var setterCall = Expression.Call(instanceParam, nameProperty.GetSetMethod(nonPublic:true), valueParam); + + var fn = Expression.Lambda>(setterCall, instance, argument).Compile(); + + var test = new Test(); + fn(test, "Foo"); + Assert.That(test.Name, Is.EqualTo("Foo")); + } + + public class RawRequest : IRequiresRequestStream + { + public Stream RequestStream { get; set; } + } + + [Test] + public void Can_create_DTO_with_Stream() + { + var o = typeof(RawRequest).CreateInstance(); + var requestObj = AutoMappingUtils.PopulateWith(o); + + Assert.That(requestObj, Is.Not.Null); + } + + public class ModelWithUri + { + public Uri Uri { get; set; } + } + public class ModelWithUriString + { + public string Uri { get; set; } + } + + [Test] + public void Does_map_Uri() + { + var dto = new ModelWithUri { Uri = new Uri("http://a.com") }; + + var to = new ModelWithUri().PopulateWithNonDefaultValues(dto); + Assert.That(to.Uri, Is.EqualTo(dto.Uri)); + + var toString = new ModelWithUriString().PopulateWithNonDefaultValues(dto); + Assert.That(toString.Uri, Is.EqualTo(dto.Uri.AbsoluteUri)); + } + + public class ViewModel + { + public string Public { get; set; } + + [IgnoreOnSelect] + public string Private { get; set; } + } + + [Test] + public void Does_convert_same_model_without_Attributes() + { + var vm = new ViewModel().PopulateFromPropertiesWithoutAttribute( + new ViewModel { Public = "Public", Private = "Private" }, + typeof(IgnoreOnSelectAttribute)); + + Assert.That(vm.Public, Is.EqualTo("Public")); + Assert.That(vm.Private, Is.Null); + } + + [Test] + public void Can_use_ToObjectDictionary_to_Remove_Property_with_Attribute() + { + var vm = new ViewModel {Public = "Public", Private = "Private"}; + var map = vm.ToObjectDictionary(); + vm.GetType().GetProperties() + .Where(x => x.HasAttribute()) + .Each(x => map.Remove(x.Name)); + + vm = map.FromObjectDictionary(); + Assert.That(vm.Public, Is.EqualTo("Public")); + Assert.That(vm.Private, Is.Null); + } + + class DictionaryTest + { + public string A { get; set; } + public int B { get; set; } + public bool C { get; set; } + public double D { get; set; } + } + + [Test] + public void Can_convert_anonymous_object_to_ObjectDictionary() + { + var newObj = new { A = "a", B = 1, C = true, D = 2.0 }; + var type = new DictionaryTest { A = "a", B = 1, C = true, D = 2.0 }; + var expected = new Dictionary { + ["A"] = "a", + ["B"] = 1, + ["C"] = true, + ["D"] = 2.0 + }; + + var to = newObj.ToObjectDictionary(); + Assert.That(to, Is.EquivalentTo(expected)); + to = type.ToObjectDictionary(); + Assert.That(to, Is.EquivalentTo(expected)); + to = expected.ToObjectDictionary(); + Assert.That(to, Is.EquivalentTo(expected)); + } + + [Test] + public void Can_convert_string_to_collection() + { + Assert.That("a,b,c".ConvertTo(), Is.EquivalentTo(new[]{ "a", "b", "c" })); + Assert.That("1,2,3".ConvertTo(), Is.EquivalentTo(new[]{ 1, 2, 3 })); + } + + [Test] + public void Translate_Between_Models_of_different_types_and_nullables() + { + var fromObj = ModelWithFieldsOfDifferentTypes.CreateConstant(1); + + var toObj = fromObj.ConvertTo(); + + Console.WriteLine(toObj.Dump()); + + ModelWithFieldsOfDifferentTypesAsNullables.AssertIsEqual(fromObj, toObj); + } + + [Test] + public void Translate_Between_Models_of_nullables_and_different_types() + { + var fromObj = ModelWithFieldsOfDifferentTypesAsNullables.CreateConstant(1); + + var toObj = fromObj.ConvertTo(); + + Console.WriteLine(toObj.Dump()); + + ModelWithFieldsOfDifferentTypesAsNullables.AssertIsEqual(toObj, fromObj); + } + + [Test] + public void Can_convert_to_array_to_string() + { + Assert.That(new []{ new ViewModel { Public = "A"} }.ConvertTo(), Is.Not.Empty); + Assert.That(new []{ DayOfWeek.Monday }.ConvertTo(), Is.Not.Empty); + } + + [Test] + public void To_works_with_ValueTypes() + { + Assert.That(1.ToString().ConvertTo(), Is.EqualTo(1)); + } + + [Test] + public void To_on_null_or_empty_string_returns_default_value_supplied() + { + const string nullString = null; + Assert.That("".ConvertTo(1), Is.EqualTo(1)); + Assert.That("".ConvertTo(default(int)), Is.EqualTo(default(int))); + Assert.That(nullString.ConvertTo(1), Is.EqualTo(1)); + } + + [Test] + public void To_ValueType_on_null_or_empty_string_returns_default_value() + { + Assert.That("".ConvertTo(), Is.EqualTo(default(int))); + } + + struct A + { + public int Id { get; } + public A(int id) => Id = id; + public static implicit operator B(A from) => new B(from.Id); + } + + struct B + { + public int Id { get; } + public B(int id) => Id = id; + public static implicit operator A(B from) => new A(from.Id); + } + + [Test] + public void Can_convert_between_structs_with_implicit_casts() + { + var a = new A(1); + var b = a; + Assert.That(b.Id, Is.EqualTo(a.Id)); + + b = a.ConvertTo(); + Assert.That(b.Id, Is.EqualTo(a.Id)); + } + + [Test] + public void Can_convert_from_class_implicit_Casts() + { + var to = "test".ConvertTo(); + Assert.That(to.LocalName, Is.EqualTo("test")); + } + + struct C + { + public int Id { get; } + public C(int id) => Id = id; + public static explicit operator C(D from) => new C(from.Id); + } + + struct D + { + public int Id { get; } + public D(int id) => Id = id; + public static explicit operator D(C from) => new D(from.Id); + } + + [Test] + public void Can_convert_between_structs_with_explicit_casts() + { + var c = new C(1); + var d = (D)c; + Assert.That(d.Id, Is.EqualTo(c.Id)); + + d = c.ConvertTo(); + Assert.That(d.Id, Is.EqualTo(c.Id)); + } + + [Test] + public void Can_Convert_from_ObjectDictionary_Containing_Another_Object_Dictionary() + { + var map = new Dictionary + { + { "name", "Foo" }, + { "otherNames", new List + { + new Dictionary { { "name", "Fu" } }, + new Dictionary { { "name", "Fuey" } } + } + } + }; + + var fromDict = map.ConvertTo(); + + Assert.That(fromDict.Name, Is.EqualTo("Foo")); + Assert.That(fromDict.OtherNames.Count, Is.EqualTo(2)); + Assert.That(fromDict.OtherNames.First().Name, Is.EqualTo("Fu")); + Assert.That(fromDict.OtherNames.Last().Name, Is.EqualTo("Fuey")); + + var toDict = fromDict.ConvertTo>(); + Assert.That(toDict["Name"], Is.EqualTo("Foo")); + Assert.That(toDict["OtherNames"], Is.EqualTo(fromDict.OtherNames)); + + var toObjDict = fromDict.ConvertTo(); + Assert.That(toObjDict["Name"], Is.EqualTo("Foo")); + Assert.That(toObjDict["OtherNames"], Is.EqualTo(fromDict.OtherNames)); + } + + class Person + { + public int Id { get; set; } + public string Name { get; set; } + + protected bool Equals(Person other) => Id == other.Id && string.Equals(Name, other.Name); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == this.GetType() && Equals((Person) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Id * 397) ^ (Name != null ? Name.GetHashCode() : 0); + } + } + } + + [Test] + public void Can_convert_between_Poco_and_ObjectDictionary() + { + var person = new Person { Id = 1, Name = "foo" }; + + Assert.That(person.ConvertTo>(), Is.EqualTo(new Dictionary { + {"Id", 1}, + {"Name", "foo"}, + })); + Assert.That(new Dictionary { + {"Id", 1}, + {"Name", "foo"}, + }.ConvertTo(), Is.EqualTo(person)); + + Assert.That(person.ConvertTo(), Is.EqualTo(new ObjectDictionary { + {"Id", 1}, + {"Name", "foo"}, + })); + Assert.That(new ObjectDictionary { + {"Id", 1}, + {"Name", "foo"}, + }.ConvertTo(), Is.EqualTo(person)); + + Assert.That(person.ConvertTo>(), Is.EqualTo(new Dictionary { + {"Id", "1"}, + {"Name", "foo"}, + })); + + Assert.That(new Dictionary { + {"Id", "1"}, + {"Name", "foo"}, + }.ConvertTo(), Is.EqualTo(person)); + + Assert.That(person.ConvertTo(), Is.EqualTo(new StringDictionary { + {"Id", "1"}, + {"Name", "foo"}, + })); + Assert.That(new StringDictionary { + {"Id", "1"}, + {"Name", "foo"}, + }.ConvertTo(), Is.EqualTo(person)); + } + + [Test] + public void Can_ToObjectDictionary_KVPs() + { + var objKvp = new KeyValuePair("A", 1); + var strKvp = new KeyValuePair("A", "1"); + var intKvp = new KeyValuePair("A", 1); + + Assert.That(objKvp.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", 1 } } )); + Assert.That(strKvp.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", "1"} } )); + Assert.That(intKvp.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", 1 } } )); + + var objKvp2 = new KeyValuePair("B", 2); + var strKvp2 = new KeyValuePair("B", "2"); + var intKvp2 = new KeyValuePair("B", 2); + + Assert.That(new[] { objKvp, objKvp2 }.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"A", 1}, {"B", 2} })); + Assert.That(new[] { strKvp, strKvp2 }.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"A", "1"}, {"B", "2"} })); + Assert.That(new[] { intKvp, intKvp2 }.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"A", 1}, {"B", 2} })); + } + + [Test] + public void Can_convert_KVPs_to_ObjectDictionary() + { + var objKvp = new KeyValuePair("A", 1); + var strKvp = new KeyValuePair("A", "1"); + var intKvp = new KeyValuePair("A", 1); + + Assert.That(objKvp.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", 1 } } )); + Assert.That(strKvp.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", "1"} } )); + Assert.That(intKvp.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", 1 } } )); + + var objKvp2 = new KeyValuePair("B", 2); + var strKvp2 = new KeyValuePair("B", "2"); + var intKvp2 = new KeyValuePair("B", 2); + + Assert.That(new[] { objKvp, objKvp2 }.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"A", 1}, {"B", 2} })); + Assert.That(new[] { strKvp, strKvp2 }.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"A", "1"}, {"B", "2"} })); + Assert.That(new[] { intKvp, intKvp2 }.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"A", 1}, {"B", 2} })); + } + + [Test] + public void Can_convert_between_KVP_Objects_and_IEnumerable() + { + var kvps = new List> { + new KeyValuePair("A", 1), + new KeyValuePair("B", 2), + new KeyValuePair("C", 3), + }; + + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo(), Is.EqualTo(kvps)); + + List listObjs = kvps.Cast().ToList(); + Assert.That(listObjs.ConvertTo>>(), Is.EquivalentTo(listObjs)); + object[] arrayObjs = kvps.Cast().ToArray(); + Assert.That(arrayObjs.ConvertTo>>(), Is.EquivalentTo(arrayObjs)); + } + + [Test] + public void Can_convert_between_KVP_Strings_and_IEnumerable() + { + var kvps = new List> { + new KeyValuePair("A", "1"), + new KeyValuePair("B", "2"), + new KeyValuePair("C", "3"), + }; + + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo(), Is.EqualTo(kvps)); + + List listObjs = kvps.Cast().ToList(); + Assert.That(listObjs.ConvertTo>>(), Is.EquivalentTo(listObjs)); + object[] arrayObjs = kvps.Cast().ToArray(); + Assert.That(arrayObjs.ConvertTo>>(), Is.EquivalentTo(arrayObjs)); + } + + [Test] + public void Can_convert_between_KVP_ints_and_IEnumerable() + { + var kvps = new List> { + new KeyValuePair("A", 1), + new KeyValuePair("B", 2), + new KeyValuePair("C", 3), + }; + + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo(), Is.EqualTo(kvps)); + + List listObjs = kvps.Cast().ToList(); + Assert.That(listObjs.ConvertTo>>(), Is.EquivalentTo(listObjs)); + object[] arrayObjs = kvps.Cast().ToArray(); + Assert.That(arrayObjs.ConvertTo>>(), Is.EquivalentTo(arrayObjs)); + } + + public enum TestEnum { A, B, C } + + [Test] + public void Can_convert_between_list_of_value_types() + { + var enumArrays = new[]{ TestEnum.A, TestEnum.B, TestEnum.C }; + var strArrays = new List {"A","B","C"}; + Assert.That(enumArrays.ConvertTo>(), Is.EquivalentTo(enumArrays.ToList())); + Assert.That(enumArrays.ConvertTo>(), Is.EquivalentTo(strArrays)); + + var strNums = new List {"1","2","3"}; + var intNums = new List {1,2,3}; + Assert.That(strNums.ConvertTo>(), Is.EquivalentTo(intNums)); + Assert.That(intNums.ConvertTo>(), Is.EquivalentTo(strNums)); + } + + public class TestClassA + { + public IList IListStrings { get; set; } + public ArrayOfString ListStrings { get; set; } + public IList ListEnums { get; set; } + } + public class TestClassB + { + public ArrayOfString IListStrings { get; set; } + public IList ListStrings { get; set; } + public ArrayOfString ListEnums { get; set; } + } + public class TestClassC + { + public IList ListStrings { get; protected set; } + } + + [Test] + public void Can_translate_generic_lists() + { + + var values = new[] { "A", "B", "C" }; + var testA = new TestClassA + { + IListStrings = new List(values), + ListStrings = new ArrayOfString(values), + ListEnums = new List { TestEnum.A, TestEnum.B, TestEnum.C }, + }; + + var fromTestA = testA.ConvertTo(); + + AssertAreEqual(testA, fromTestA); + + var userFileTypeValues = testA.ListEnums.Map(x => x.ToString()); + var testB = new TestClassB + { + IListStrings = new ArrayOfString(values), + ListStrings = new List(values), + ListEnums = new ArrayOfString(userFileTypeValues), + }; + + var fromTestB = testB.ConvertTo(); + AssertAreEqual(fromTestB, testB); + } + + private static void AssertAreEqual(TestClassA testA, TestClassB testB) + { + Assert.That(testA, Is.Not.Null); + Assert.That(testB, Is.Not.Null); + + Assert.That(testA.IListStrings, Is.Not.Null); + Assert.That(testB.IListStrings, Is.Not.Null); + Assert.That(testA.IListStrings, + Is.EquivalentTo(new List(testB.IListStrings))); + + Assert.That(testA.ListStrings, Is.Not.Null); + Assert.That(testB.ListStrings, Is.Not.Null); + Assert.That(testA.ListStrings, Is.EquivalentTo(testB.ListStrings)); + + Assert.That(testA.ListEnums, Is.Not.Null); + Assert.That(testB.ListEnums, Is.Not.Null); + Assert.That(testA.ListEnums, + Is.EquivalentTo(testB.ListEnums.ConvertAll(x => x.ToEnum()))); + } + + class Company + { + public string Name { get; set; } + public Address Address { get; set; } + } + + class Address + { + public string City { get; set; } + public string PostCode { get; set; } + } + + [Test] + public void Can_register_converters_to_PopulateWithNonDefaultValues() + { + Company Create() + { + return new Company { + Name = "Compnay1", + Address = new Address { + City = "NY", + PostCode = "1234", + } + }; + } + + var defaults = Create(); + var original = Create(); + + original.PopulateWithNonDefaultValues(new Company { + Address = new Address { + PostCode = "4321" + } + }); + + Assert.That(original.Name, Is.EqualTo(defaults.Name)); + Assert.That(original.Address.City, Is.Null); + Assert.That(original.Address.PostCode, Is.EqualTo("4321")); + + original = Create(); + + var updated = new Company { + Address = new Address { + PostCode = "4321" + } + }; + original.Address.PopulateWithNonDefaultValues(updated.Address); + updated.Address = null; + original.PopulateWithNonDefaultValues(updated); + + Assert.That(original.Name, Is.EqualTo(defaults.Name)); + Assert.That(original.Address.City, Is.EqualTo(defaults.Address.City)); + Assert.That(original.Address.PostCode, Is.EqualTo("4321")); + } + + public class NavItem : IMeta + { + public string Label { get; set; } + public string Path { get; set; } + + /// + /// Whether Active class should only be added when paths are exact match + /// otherwise checks if ActivePath starts with Path + /// + public bool Exact { get; set; } + + public string Id { get; set; } // Emit id="{Id}" + public string Class { get; set; } // Override class="{Class}" + + public List Children { get; set; } = new List(); + + public Dictionary Meta { get; set; } = new Dictionary(); + } + + [Test] + public void Does_convert_nested_collections() + { + var to = new[] { + new Dictionary { + ["id"] = "A1", + ["children"] = new List + { + new Dictionary + { + ["id"] = "B1", + }, + new Dictionary + { + ["id"] = "B2", + ["children"] = new List { + new Dictionary + { + ["id"] = "C1", + }, + } + }, + } + } + }; + + var navItems = to.ConvertTo>(); + Assert.That(navItems[0].Id, Is.EqualTo("A1")); + Assert.That(navItems[0].Children[0].Id, Is.EqualTo("B1")); + Assert.That(navItems[0].Children[1].Id, Is.EqualTo("B2")); + Assert.That(navItems[0].Children[1].Children[0].Id, Is.EqualTo("C1")); + } + + public class TestMethod + { + public void MyMethod() + { + MyProp = nameof(MyProp); + } + public string MyProp { get; set; } + } + + public class TestMethod2 + { + public void MyMethod(string myProp) + { + MyProp = nameof(MyMethod); + } + public string MyProp { get; set; } + } + + [Test] + public void Does_not_try_to_map_methods() + { + var from = new TestMethod(); + var to = new TestMethod2(); + from.MyProp = "Test"; + to.PopulateFromPropertiesWithoutAttribute(from, typeof(IgnoreDataMemberAttribute)); + Assert.That(from.MyProp, Is.EqualTo(to.MyProp)); + } + + } +} diff --git a/tests/ServiceStack.Text.Tests/BasicStringSerializerTests.cs b/tests/ServiceStack.Text.Tests/BasicStringSerializerTests.cs index aec553530..a88c1ecd2 100644 --- a/tests/ServiceStack.Text.Tests/BasicStringSerializerTests.cs +++ b/tests/ServiceStack.Text.Tests/BasicStringSerializerTests.cs @@ -3,40 +3,39 @@ using System.Collections.ObjectModel; using System.Linq; using NUnit.Framework; -#if !MONOTOUCH +#if !IOS using System.ComponentModel.DataAnnotations; using Northwind.Common.ComplexModel; -using ServiceStack.Common.Extensions; using ServiceStack.Common.Tests.Models; #endif using ServiceStack.Text.Common; namespace ServiceStack.Text.Tests { - [TestFixture] - public class BasicStringSerializerTests - { - readonly char[] allCharsUsed = new[] { - JsWriter.QuoteChar, JsWriter.ItemSeperator, - JsWriter.MapStartChar, JsWriter.MapKeySeperator, JsWriter.MapEndChar, - JsWriter.ListEndChar, JsWriter.ListEndChar, - }; - - readonly string fieldWithInvalidChars = string.Format("all {0} {1} {2} {3} {4} {5} {6} invalid chars", - JsWriter.QuoteChar, JsWriter.ItemSeperator, - JsWriter.MapStartChar, JsWriter.MapKeySeperator, JsWriter.MapEndChar, - JsWriter.ListEndChar, JsWriter.ListEndChar); - - readonly int[] intValues = new[] { 1, 2, 3, 4, 5 }; - readonly double[] doubleValues = new[] { 1.0d, 2.0d, 3.0d, 4.0d, 5.0d }; - readonly string[] stringValues = new[] { "One", "Two", "Three", "Four", "Five" }; - readonly string[] stringValuesWithIllegalChar = new[] { "One", ",", "Three", "Four", "Five" }; - - public enum TestEnum - { - EnumValue1, - EnumValue2, - } + [TestFixture] + public class BasicStringSerializerTests + { + readonly char[] allCharsUsed = new[] { + JsWriter.QuoteChar, JsWriter.ItemSeperator, + JsWriter.MapStartChar, JsWriter.MapKeySeperator, JsWriter.MapEndChar, + JsWriter.ListEndChar, JsWriter.ListEndChar, + }; + + readonly string fieldWithInvalidChars = string.Format("all {0} {1} {2} {3} {4} {5} {6} invalid chars", + JsWriter.QuoteChar, JsWriter.ItemSeperator, + JsWriter.MapStartChar, JsWriter.MapKeySeperator, JsWriter.MapEndChar, + JsWriter.ListEndChar, JsWriter.ListEndChar); + + readonly int[] intValues = new[] { 1, 2, 3, 4, 5 }; + readonly double[] doubleValues = new[] { 1.0d, 2.0d, 3.0d, 4.0d, 5.0d }; + readonly string[] stringValues = new[] { "One", "Two", "Three", "Four", "Five" }; + readonly string[] stringValuesWithIllegalChar = new[] { "One", ",", "Three", "Four", "Five" }; + + public enum TestEnum + { + EnumValue1, + EnumValue2, + } [Flags] public enum UnsignedFlags : uint @@ -45,106 +44,106 @@ public enum UnsignedFlags : uint EnumValue2 = 1, } - [Test] - public void Can_convert_comma_delimited_string_to_List_String() - { - Assert.That(TypeSerializer.CanCreateFromString(typeof(List)), Is.True); - - var stringValueList = "[" + string.Join(",", stringValues) + "]"; - - var convertedJsvValues = TypeSerializer.DeserializeFromString>(stringValueList); - Assert.That(convertedJsvValues, Is.EquivalentTo(stringValues)); - - var convertedJsonValues = JsonSerializer.DeserializeFromString>(stringValueList); - Assert.That(convertedJsonValues, Is.EquivalentTo(stringValues)); - } - - [Test] - public void Null_or_Empty_string_returns_null() - { - var convertedJsvValues = TypeSerializer.DeserializeFromString>(null); - Assert.That(convertedJsvValues, Is.EqualTo(null)); - - convertedJsvValues = TypeSerializer.DeserializeFromString>(string.Empty); - Assert.That(convertedJsvValues, Is.EqualTo(null)); - } - - [Test] - public void Empty_list_string_returns_empty_List() - { - var convertedStringValues = TypeSerializer.DeserializeFromString>("[]"); - Assert.That(convertedStringValues, Is.EqualTo(new List())); - } - - [Test] - public void Null_or_Empty_string_returns_null_Map() - { - var convertedStringValues = TypeSerializer.DeserializeFromString>(null); - Assert.That(convertedStringValues, Is.EqualTo(null)); - - convertedStringValues = TypeSerializer.DeserializeFromString>(string.Empty); - Assert.That(convertedStringValues, Is.EqualTo(null)); - } - - [Test] - public void Empty_map_string_returns_empty_List() - { - var convertedStringValues = TypeSerializer.DeserializeFromString>("{}"); - Assert.That(convertedStringValues, Is.EqualTo(new Dictionary())); - } - - [Test] - public void Can_convert_string_collection() - { - Assert.That(TypeSerializer.CanCreateFromString(typeof(string[])), Is.True); - - var stringValue = TypeSerializer.SerializeToString(stringValues); - var expectedString = "[" + string.Join(",", stringValues.ToArray()) + "]"; - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_enum() - { - var enumValue = TestEnum.EnumValue1; - var stringValue = TypeSerializer.SerializeToString(enumValue); - var expectedString = TestEnum.EnumValue1.ToString(); - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_nullable_enum() - { - TestEnum? enumValue = TestEnum.EnumValue1; - var stringValue = TypeSerializer.SerializeToString(enumValue); - var expectedString = TestEnum.EnumValue1.ToString(); - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_to_nullable_enum() - { - Assert.That(TypeSerializer.CanCreateFromString(typeof(TestEnum?)), Is.True); - - TestEnum? enumValue = TestEnum.EnumValue1; - var actualValue = TypeSerializer.DeserializeFromString(enumValue.ToString()); - Assert.That(actualValue, Is.EqualTo(enumValue)); - } - - [Test] - public void Can_convert_to_nullable_enum_with_null_value() - { - var enumValue = TypeSerializer.DeserializeFromString(null); - Assert.That(enumValue, Is.Null); - } - - [Test] - public void Can_convert_nullable_enum_with_null_value() - { - TestEnum? enumValue = null; - var stringValue = TypeSerializer.SerializeToString(enumValue); - Assert.That(stringValue, Is.Null); - } + [Test] + public void Can_convert_comma_delimited_string_to_List_String() + { + Assert.That(TypeSerializer.CanCreateFromString(typeof(List)), Is.True); + + var stringValueList = "[" + string.Join(",", stringValues) + "]"; + + var convertedJsvValues = TypeSerializer.DeserializeFromString>(stringValueList); + Assert.That(convertedJsvValues, Is.EquivalentTo(stringValues)); + + var convertedJsonValues = JsonSerializer.DeserializeFromString>(stringValueList); + Assert.That(convertedJsonValues, Is.EquivalentTo(stringValues)); + } + + [Test] + public void Null_or_Empty_string_returns_null() + { + var convertedJsvValues = TypeSerializer.DeserializeFromString>((string)null); + Assert.That(convertedJsvValues, Is.EqualTo(null)); + + convertedJsvValues = TypeSerializer.DeserializeFromString>(string.Empty); + Assert.That(convertedJsvValues, Is.EqualTo(null)); + } + + [Test] + public void Empty_list_string_returns_empty_List() + { + var convertedStringValues = TypeSerializer.DeserializeFromString>("[]"); + Assert.That(convertedStringValues, Is.EqualTo(new List())); + } + + [Test] + public void Null_or_Empty_string_returns_null_Map() + { + var convertedStringValues = TypeSerializer.DeserializeFromString>((string)null); + Assert.That(convertedStringValues, Is.EqualTo(null)); + + convertedStringValues = TypeSerializer.DeserializeFromString>(string.Empty); + Assert.That(convertedStringValues, Is.EqualTo(null)); + } + + [Test] + public void Empty_map_string_returns_empty_List() + { + var convertedStringValues = TypeSerializer.DeserializeFromString>("{}"); + Assert.That(convertedStringValues, Is.EqualTo(new Dictionary())); + } + + [Test] + public void Can_convert_string_collection() + { + Assert.That(TypeSerializer.CanCreateFromString(typeof(string[])), Is.True); + + var stringValue = TypeSerializer.SerializeToString(stringValues); + var expectedString = "[" + string.Join(",", stringValues.ToArray()) + "]"; + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_enum() + { + var enumValue = TestEnum.EnumValue1; + var stringValue = TypeSerializer.SerializeToString(enumValue); + var expectedString = TestEnum.EnumValue1.ToString(); + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_nullable_enum() + { + TestEnum? enumValue = TestEnum.EnumValue1; + var stringValue = TypeSerializer.SerializeToString(enumValue); + var expectedString = TestEnum.EnumValue1.ToString(); + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_to_nullable_enum() + { + Assert.That(TypeSerializer.CanCreateFromString(typeof(TestEnum?)), Is.True); + + TestEnum? enumValue = TestEnum.EnumValue1; + var actualValue = TypeSerializer.DeserializeFromString(enumValue.ToString()); + Assert.That(actualValue, Is.EqualTo(enumValue)); + } + + [Test] + public void Can_convert_to_nullable_enum_with_null_value() + { + var enumValue = TypeSerializer.DeserializeFromString((string)null); + Assert.That(enumValue, Is.Null); + } + + [Test] + public void Can_convert_nullable_enum_with_null_value() + { + TestEnum? enumValue = null; + var stringValue = TypeSerializer.SerializeToString(enumValue); + Assert.That(stringValue, Is.Null); + } [Test] public void Can_convert_unsigned_flags_enum() @@ -155,86 +154,86 @@ public void Can_convert_unsigned_flags_enum() Assert.That(stringValue, Is.EqualTo(expectedString)); } - [Test] - public void Can_convert_Guid() - { - Assert.That(TypeSerializer.CanCreateFromString(typeof(Guid)), Is.True); - - var guidValue = Guid.NewGuid(); - var stringValue = TypeSerializer.SerializeToString(guidValue); - var expectedString = guidValue.ToString("N"); - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_datetime() - { - var dateValue = new DateTime(1979, 5, 9); - var stringValue = TypeSerializer.SerializeToString(dateValue); - var expectedString = "1979-05-09"; - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_to_datetime() - { - Assert.That(TypeSerializer.CanCreateFromString(typeof(DateTime)), Is.True); - - var dateValue = new DateTime(1979, 5, 9); - var actualValue = TypeSerializer.DeserializeFromString("1979-05-09"); - Assert.That(actualValue, Is.EqualTo(dateValue)); - } - - [Test] - public void Can_convert_nullable_datetime() - { - DateTime? dateValue = new DateTime(1979, 5, 9); - var stringValue = TypeSerializer.SerializeToString(dateValue); - var expectedString = "1979-05-09"; - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_to_nullable_datetime() - { - Assert.That(TypeSerializer.CanCreateFromString(typeof(DateTime?)), Is.True); - - DateTime? dateValue = new DateTime(1979, 5, 9); - var actualValue = TypeSerializer.DeserializeFromString("1979-05-09"); - Assert.That(actualValue, Is.EqualTo(dateValue)); - } - - [Test] - public void Can_convert_string_List() - { - var stringValue = TypeSerializer.SerializeToString(stringValues.ToList()); - var expectedString = "[" + string.Join(",", stringValues.ToArray()) + "]"; - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_string_array() - { - var stringValue = TypeSerializer.SerializeToString(stringValues.ToArray()); - var expectedString = "[" + string.Join(",", stringValues.ToArray()) + "]"; - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_string_List_as_object() - { - var stringValue = TypeSerializer.SerializeToString((object)stringValues.ToList()); - var expectedString = "[" + string.Join(",", stringValues.ToArray()) + "]"; - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_empty_List() - { - var stringValue = TypeSerializer.SerializeToString(new List()); - var expectedString = "[]"; - Assert.That(stringValue, Is.EqualTo(expectedString)); - } + [Test] + public void Can_convert_Guid() + { + Assert.That(TypeSerializer.CanCreateFromString(typeof(Guid)), Is.True); + + var guidValue = Guid.NewGuid(); + var stringValue = TypeSerializer.SerializeToString(guidValue); + var expectedString = guidValue.ToString("N"); + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_datetime() + { + var dateValue = new DateTime(1979, 5, 9); + var stringValue = TypeSerializer.SerializeToString(dateValue); + var expectedString = "1979-05-09"; + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_to_datetime() + { + Assert.That(TypeSerializer.CanCreateFromString(typeof(DateTime)), Is.True); + + var dateValue = new DateTime(1979, 5, 9); + var actualValue = TypeSerializer.DeserializeFromString("1979-05-09"); + Assert.That(actualValue, Is.EqualTo(dateValue)); + } + + [Test] + public void Can_convert_nullable_datetime() + { + DateTime? dateValue = new DateTime(1979, 5, 9); + var stringValue = TypeSerializer.SerializeToString(dateValue); + var expectedString = "1979-05-09"; + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_to_nullable_datetime() + { + Assert.That(TypeSerializer.CanCreateFromString(typeof(DateTime?)), Is.True); + + DateTime? dateValue = new DateTime(1979, 5, 9); + var actualValue = TypeSerializer.DeserializeFromString("1979-05-09"); + Assert.That(actualValue, Is.EqualTo(dateValue)); + } + + [Test] + public void Can_convert_string_List() + { + var stringValue = TypeSerializer.SerializeToString(stringValues.ToList()); + var expectedString = "[" + string.Join(",", stringValues.ToArray()) + "]"; + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_string_array() + { + var stringValue = TypeSerializer.SerializeToString(stringValues.ToArray()); + var expectedString = "[" + string.Join(",", stringValues.ToArray()) + "]"; + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_string_List_as_object() + { + var stringValue = TypeSerializer.SerializeToString((object)stringValues.ToList()); + var expectedString = "[" + string.Join(",", stringValues.ToArray()) + "]"; + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_empty_List() + { + var stringValue = TypeSerializer.SerializeToString(new List()); + var expectedString = "[]"; + Assert.That(stringValue, Is.EqualTo(expectedString)); + } [Test] public void Can_convert_multidimensional_array() @@ -250,370 +249,398 @@ public void Can_convert_multidimensional_array() Assert.That(result, Is.EqualTo("[[[1,0],[1,0]],[[0,1],[0,1]]]")); } - [Test] - public void Can_convert_empty_List_as_object() - { - var stringValue = TypeSerializer.SerializeToString((object)new List()); - var expectedString = "[]"; - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_string_dictionary() - { - var stringDictionary = new Dictionary - { - { "One", "1st" }, { "Two", "2nd" }, { "Three", "3rd" } - }; - var expectedString = "{One:1st,Two:2nd,Three:3rd}"; - var stringValue = TypeSerializer.SerializeToString(stringDictionary); - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_parse_string_dictionary() - { - var stringDictionary = new Dictionary - { - { "One", "1st" }, { "Two", "2nd" }, { "Three", "3rd" } - }; - const string mapValues = "{One:1st,Two:2nd,Three:3rd}"; - var parsedDictionary = TypeSerializer.DeserializeFromString(mapValues, stringDictionary.GetType()); - Assert.That(parsedDictionary, Is.EquivalentTo(stringDictionary)); - } - - [Test] - public void Can_convert_string_dictionary_as_object() - { - var stringDictionary = new Dictionary { - { "One", "1st" }, { "Two", "2nd" }, { "Three", "3rd" } - }; - var expectedString = "{One:1st,Two:2nd,Three:3rd}"; - var stringValue = TypeSerializer.SerializeToString((object)stringDictionary); - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_string_dictionary_with_special_chars_as_object() - { - var stringDictionary = new Dictionary - { - { "One", "\"1st" }, { "Two", "2:nd" }, { "Three", "3r,d" }, { "Four", "four%" } - }; - var expectedString = "{One:\"\"\"1st\",Two:\"2:nd\",Three:\"3r,d\",Four:four%}"; - var stringValue = TypeSerializer.SerializeToString(stringDictionary); - Assert.That(stringValue, Is.EqualTo(expectedString)); - - Serialize(stringDictionary); - } - - [Test] - public void Can_parse_string_dictionary_with_special_chars_as_object() - { - var stringDictionary = new Dictionary - { - { "One", "\"1st" }, { "Two", "2:nd" }, { "Three", "3r,d" } - }; - const string mapValues = "{One:\"\"\"1st\",Two:2:nd,Three:\"3r,d\"}"; - var parsedDictionary = TypeSerializer.DeserializeFromString(mapValues, stringDictionary.GetType()); - Assert.That(parsedDictionary, Is.EquivalentTo(stringDictionary)); - - Serialize(stringDictionary); - } - - [Test] - public void Can_convert_string_list_with_special_chars_as_object() - { - var stringList = new List - { - "\"1st", "2:nd", "3r,d", "four%" - }; - var expectedString = "[\"\"\"1st\",\"2:nd\",\"3r,d\",four%]"; - var stringValue = TypeSerializer.SerializeToString(stringList); - Assert.That(stringValue, Is.EqualTo(expectedString)); + [Test] + public void Can_convert_empty_List_as_object() + { + var stringValue = TypeSerializer.SerializeToString((object)new List()); + var expectedString = "[]"; + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_string_dictionary() + { + var stringDictionary = new Dictionary + { + { "One", "1st" }, { "Two", "2nd" }, { "Three", "3rd" } + }; + var expectedString = "{One:1st,Two:2nd,Three:3rd}"; + var stringValue = TypeSerializer.SerializeToString(stringDictionary); + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_parse_string_dictionary() + { + var stringDictionary = new Dictionary + { + { "One", "1st" }, { "Two", "2nd" }, { "Three", "3rd" } + }; + const string mapValues = "{One:1st,Two:2nd,Three:3rd}"; + var parsedDictionary = TypeSerializer.DeserializeFromString(mapValues, stringDictionary.GetType()); + Assert.That(parsedDictionary, Is.EquivalentTo(stringDictionary)); + } + + [Test] + public void Can_convert_string_dictionary_as_object() + { + var stringDictionary = new Dictionary { + { "One", "1st" }, { "Two", "2nd" }, { "Three", "3rd" } + }; + var expectedString = "{One:1st,Two:2nd,Three:3rd}"; + var stringValue = TypeSerializer.SerializeToString((object)stringDictionary); + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_string_dictionary_with_special_chars_as_object() + { + var stringDictionary = new Dictionary + { + { "One", "\"1st" }, { "Two", "2:nd" }, { "Three", "3r,d" }, { "Four", "four%" } + }; + var expectedString = "{One:\"\"\"1st\",Two:\"2:nd\",Three:\"3r,d\",Four:four%}"; + var stringValue = TypeSerializer.SerializeToString(stringDictionary); + Assert.That(stringValue, Is.EqualTo(expectedString)); + + Serialize(stringDictionary); + } + + [Test] + public void Can_parse_string_dictionary_with_special_chars_as_object() + { + var stringDictionary = new Dictionary + { + { "One", "\"1st" }, { "Two", "2:nd" }, { "Three", "3r,d" } + }; + const string mapValues = "{One:\"\"\"1st\",Two:2:nd,Three:\"3r,d\"}"; + var parsedDictionary = TypeSerializer.DeserializeFromString(mapValues, stringDictionary.GetType()); + Assert.That(parsedDictionary, Is.EquivalentTo(stringDictionary)); + + Serialize(stringDictionary); + } + + [Test] + public void Can_convert_string_list_with_special_chars_as_object() + { + var stringList = new List + { + "\"1st", "2:nd", "3r,d", "four%" + }; + var expectedString = "[\"\"\"1st\",\"2:nd\",\"3r,d\",four%]"; + var stringValue = TypeSerializer.SerializeToString(stringList); + Assert.That(stringValue, Is.EqualTo(expectedString)); Serialize(stringList); } - [Test] - public void Can_parse_string_list_with_special_chars_as_object() - { - var stringList = new List - { - "\"1st", "2:nd", "3r,d", "four%" - }; - const string listValues = "[\"\"\"1st\",2:nd,\"3r,d\",four%]"; - var parsedList = TypeSerializer.DeserializeFromString(listValues, stringList.GetType()); - Assert.That(parsedList, Is.EquivalentTo(stringList)); - - Serialize(stringList); - } - - [Test] - public void Can_convert_Byte_array_with_JsonSerializer() - { - var byteArrayValue = new byte[] { 0, 65, 97, 255, }; - var stringValue = JsonSerializer.SerializeToString(byteArrayValue); - var expectedString = Convert.ToBase64String(byteArrayValue); - Assert.That(stringValue, Is.EqualTo('"' + expectedString + '"')); - } - - [Test] - public void Can_convert_Byte_array() - { - var byteArrayValue = new byte[] { 0, 65, 97, 255, }; - var stringValue = TypeSerializer.SerializeToString(byteArrayValue); - var expectedString = Convert.ToBase64String(byteArrayValue); - Assert.That(stringValue, Is.EqualTo(expectedString)); - } - - [Test] - public void Can_convert_to_Byte_array() - { - Assert.That(TypeSerializer.CanCreateFromString(typeof(byte[])), Is.True); - - var byteArrayValue = new byte[] { 0, 65, 97, 255, }; - var byteArrayString = TypeSerializer.SerializeToString(byteArrayValue); - var actualValue = TypeSerializer.DeserializeFromString(byteArrayString); - Assert.That(actualValue, Is.EqualTo(byteArrayValue)); - } - - - public T Serialize(T model) - { - var jsvModel = TypeSerializer.SerializeToString(model); - Console.WriteLine("Len: " + jsvModel.Length + ", " + jsvModel); - var fromJsvModel = TypeSerializer.DeserializeFromString(jsvModel); - - var jsonModel = JsonSerializer.SerializeToString(model); - Console.WriteLine("Len: " + jsonModel.Length + ", " + jsonModel); - var fromJsonModel = JsonSerializer.DeserializeFromString(jsonModel); - - return fromJsonModel; - } - -#if !MONOTOUCH - public class TestClass - { - [Required] - public string Member1 { get; set; } - - public string Member2 { get; set; } - - [Required] - public string Member3 { get; set; } - - [StringLength(1)] - public string Member4 { get; set; } - } - - [Test] - public void Can_convert_string_to_List() - { - var fromHashSet = stringValues; - var toHashSet = Serialize(fromHashSet); - - Assert.That(toHashSet.EquivalentTo(fromHashSet), Is.True); - } - - [Test] - public void Can_convert_string_to_string_HashSet() - { - var fromHashSet = new HashSet(stringValues); - var toHashSet = Serialize(fromHashSet); - - Assert.That(toHashSet.EquivalentTo(fromHashSet), Is.True); - } - - [Test] - public void Can_convert_string_to_int_HashSet() - { - var fromHashSet = new HashSet(intValues); - var toHashSet = Serialize(fromHashSet); - - Assert.That(toHashSet.EquivalentTo(fromHashSet), Is.True); - } - - [Test] - public void Can_convert_string_to_double_HashSet() - { - var fromHashSet = new HashSet(doubleValues); - var toHashSet = Serialize(fromHashSet); - - Assert.That(toHashSet.EquivalentTo(fromHashSet), Is.True); - } - - [Test] - public void Can_convert_string_to_string_ReadOnlyCollection() - { - var fromCollection = new ReadOnlyCollection(stringValues); - var toCollection = Serialize(fromCollection); - - Assert.That(toCollection.EquivalentTo(fromCollection), Is.True); - } - - [Test] - public void Can_convert_string_to_int_ReadOnlyCollection() - { - var fromCollection = new ReadOnlyCollection(intValues); - var toCollection = Serialize(fromCollection); - - Assert.That(toCollection.EquivalentTo(fromCollection), Is.True); - } - - [Test] - public void Can_convert_string_to_double_ReadOnlyCollection() - { - var fromCollection = new ReadOnlyCollection(doubleValues); - var toCollection = Serialize(fromCollection); - - Assert.That(toCollection.EquivalentTo(fromCollection), Is.True); - } - - [Test] - public void Can_convert_ModelWithFieldsOfDifferentTypes() - { - var model = ModelWithFieldsOfDifferentTypes.Create(1); - var toModel = Serialize(model); - - ModelWithFieldsOfDifferentTypes.AssertIsEqual(toModel, model); - } - - [Test] - public void Can_convert_ModelWithFieldsOfNullableTypes() - { - var model = ModelWithFieldsOfNullableTypes.Create(1); - var toModel = Serialize(model); - - ModelWithFieldsOfNullableTypes.AssertIsEqual(toModel, model); - } - - [Test] - public void Can_convert_ModelWithFieldsOfNullableTypes_of_nullables() - { - var model = new ModelWithFieldsOfNullableTypes(); - var toModel = Serialize(model); - - ModelWithFieldsOfNullableTypes.AssertIsEqual(toModel, model); - } - - [Ignore("Causing infinite recursion in TypeToString")] - [Test] - public void Can_convert_ModelWithComplexTypes() - { - var model = ModelWithComplexTypes.Create(1); - var toModel = Serialize(model); - - ModelWithComplexTypes.AssertIsEqual(toModel, model); - } - - [Test] - public void Can_convert_model_with_TypeChar() - { - var model = new ModelWithIdAndName { Id = 1, Name = "in } valid" }; - var toModel = Serialize(model); - - ModelWithIdAndName.AssertIsEqual(toModel, model); - } - - [Test] - public void Can_convert_model_with_ListChar() - { - var model = new ModelWithIdAndName { Id = 1, Name = "in [ valid" }; - var toModel = Serialize(model); - ModelWithIdAndName.AssertIsEqual(toModel, model); - - var model2 = new ModelWithIdAndName { Id = 1, Name = "in valid]" }; - var toModel2 = Serialize(model2); - ModelWithIdAndName.AssertIsEqual(toModel2, model2); - } - - [Test] - public void Can_convert_ModelWithMapAndList_with_ListChar() - { - var model = new ModelWithMapAndList { - Id = 1, - Name = "in [ valid", - List = new List { - new ModelWithIdAndName{Id = 1, Name = "field [in valid] has stuff"}, - new ModelWithIdAndName{Id = 1, Name = "field [in valid] has stuff"}, - }, - }; - var toModel = Serialize(model); - //ModelWithMapAndList.AssertIsEqual(toModel, model); - } - - [Test] - public void Can_convert_ArrayDtoWithOrders() - { - var model = DtoFactory.ArrayDtoWithOrders; - var toModel = Serialize(model); - - Assert.That(model.Equals(toModel), Is.True); - } - - [Test] - public void Can_convert_Field_Map_or_List_with_invalid_chars() - { - var instance = new ModelWithMapAndList { - Id = 1, - Name = fieldWithInvalidChars, - List = new List { fieldWithInvalidChars, fieldWithInvalidChars }, - Map = new Dictionary { { fieldWithInvalidChars, fieldWithInvalidChars } }, - }; - - Serialize(instance); - } - - [Test] - public void Can_convert_Field_Map_or_List_with_single_invalid_char() - { - foreach (var invalidChar in allCharsUsed) - { - var singleInvalidChar = string.Format("a {0} b", invalidChar); - - var instance = new ModelWithMapAndList { - Id = 1, - Name = singleInvalidChar, - List = new List { singleInvalidChar, singleInvalidChar }, - Map = new Dictionary { { singleInvalidChar, singleInvalidChar } }, - }; - - Serialize(instance); - } - } - - [Test] - public void Can_convert_CustomerDto() - { - var model = DtoFactory.CustomerDto; - var toModel = Serialize(model); - - Assert.That(model.Equals(toModel), Is.True); - } - - [Test] - public void Can_convert_CustomerOrderListDto() - { - var model = DtoFactory.CustomerOrderListDto; - var toModel = Serialize(model); - - Assert.That(model.Equals(toModel), Is.True); - } - - [Test] - public void Can_convert_List_Guid() - { - var model = new List { - Guid.NewGuid(), - Guid.NewGuid(), - Guid.NewGuid(), - }; - - var toModel = Serialize(model); - - Assert.That(toModel, Is.EquivalentTo(model)); - } -#endif - } + [Test] + public void Can_parse_string_list_with_special_chars_as_object() + { + var stringList = new List + { + "\"1st", "2:nd", "3r,d", "four%" + }; + const string listValues = "[\"\"\"1st\",2:nd,\"3r,d\",four%]"; + var parsedList = TypeSerializer.DeserializeFromString(listValues, stringList.GetType()); + Assert.That(parsedList, Is.EquivalentTo(stringList)); + + Serialize(stringList); + } + + [Test] + public void Can_convert_Byte_array_with_JsonSerializer() + { + var byteArrayValue = new byte[] { 0, 65, 97, 255, }; + var stringValue = JsonSerializer.SerializeToString(byteArrayValue); + var expectedString = Convert.ToBase64String(byteArrayValue); + Assert.That(stringValue, Is.EqualTo('"' + expectedString + '"')); + } + + [Test] + public void Can_convert_Byte_array() + { + var byteArrayValue = new byte[] { 0, 65, 97, 255, }; + var stringValue = TypeSerializer.SerializeToString(byteArrayValue); + var expectedString = Convert.ToBase64String(byteArrayValue); + Assert.That(stringValue, Is.EqualTo(expectedString)); + } + + [Test] + public void Can_convert_to_Byte_array() + { + Assert.That(TypeSerializer.CanCreateFromString(typeof(byte[])), Is.True); + + var byteArrayValue = new byte[] { 0, 65, 97, 255, }; + var byteArrayString = TypeSerializer.SerializeToString(byteArrayValue); + var actualValue = TypeSerializer.DeserializeFromString(byteArrayString); + Assert.That(actualValue, Is.EqualTo(byteArrayValue)); + } + + + public T Serialize(T model) + { + var jsvModel = TypeSerializer.SerializeToString(model); + Console.WriteLine("Len: " + jsvModel.Length + ", " + jsvModel); + var fromJsvModel = TypeSerializer.DeserializeFromString(jsvModel); + + var jsonModel = JsonSerializer.SerializeToString(model); + Console.WriteLine("Len: " + jsonModel.Length + ", " + jsonModel); + var fromJsonModel = JsonSerializer.DeserializeFromString(jsonModel); + + return fromJsonModel; + } + + public class TestClass + { + [Required] + public string Member1 { get; set; } + + public string Member2 { get; set; } + + [Required] + public string Member3 { get; set; } + + [StringLength(1)] + public string Member4 { get; set; } + } + + [Test] + public void Can_convert_string_to_List() + { + var fromHashSet = stringValues; + var toHashSet = Serialize(fromHashSet); + + Assert.That(toHashSet.EquivalentTo(fromHashSet), Is.True); + } + + [Test] + public void Can_convert_string_to_string_HashSet() + { + var fromHashSet = new HashSet(stringValues); + var toHashSet = Serialize(fromHashSet); + + Assert.That(toHashSet.EquivalentTo(fromHashSet), Is.True); + } + + [Test] + public void Can_convert_string_to_int_HashSet() + { + var fromHashSet = new HashSet(intValues); + var toHashSet = Serialize(fromHashSet); + + Assert.That(toHashSet.EquivalentTo(fromHashSet), Is.True); + } + + [Test] + public void Can_convert_string_to_double_HashSet() + { + var fromHashSet = new HashSet(doubleValues); + var toHashSet = Serialize(fromHashSet); + + Assert.That(toHashSet.EquivalentTo(fromHashSet), Is.True); + } + + [Test] + public void Can_convert_string_to_string_ReadOnlyCollection() + { + var fromCollection = new ReadOnlyCollection(stringValues); + var toCollection = Serialize(fromCollection); + + Assert.That(toCollection.EquivalentTo(fromCollection), Is.True); + } + + [Test] + public void Can_convert_string_to_int_ReadOnlyCollection() + { + var fromCollection = new ReadOnlyCollection(intValues); + var toCollection = Serialize(fromCollection); + + Assert.That(toCollection.EquivalentTo(fromCollection), Is.True); + } + + [Test] + public void Can_convert_string_to_double_ReadOnlyCollection() + { + var fromCollection = new ReadOnlyCollection(doubleValues); + var toCollection = Serialize(fromCollection); + + Assert.That(toCollection.EquivalentTo(fromCollection), Is.True); + } + + [Test] + public void Can_convert_ModelWithFieldsOfDifferentTypes() + { + var model = ModelWithFieldsOfDifferentTypes.Create(1); + var toModel = Serialize(model); + + ModelWithFieldsOfDifferentTypes.AssertIsEqual(toModel, model); + } + + [Test] + public void Can_convert_ModelWithFieldsOfNullableTypes() + { + var model = ModelWithFieldsOfNullableTypes.Create(1); + var toModel = Serialize(model); + + ModelWithFieldsOfNullableTypes.AssertIsEqual(toModel, model); + } + + [Test] + public void Can_convert_ModelWithFieldsOfNullableTypes_of_nullables() + { + var model = new ModelWithFieldsOfNullableTypes(); + var toModel = Serialize(model); + + ModelWithFieldsOfNullableTypes.AssertIsEqual(toModel, model); + } + + [Ignore("Causing infinite recursion in TypeToString")] + [Test] + public void Can_convert_ModelWithComplexTypes() + { + var model = ModelWithComplexTypes.Create(1); + var toModel = Serialize(model); + + ModelWithComplexTypes.AssertIsEqual(toModel, model); + } + + [Test] + public void Can_convert_model_with_TypeChar() + { + var model = new ModelWithIdAndName { Id = 1, Name = "in } valid" }; + var toModel = Serialize(model); + + ModelWithIdAndName.AssertIsEqual(toModel, model); + } + + [Test] + public void Can_convert_model_with_ListChar() + { + var model = new ModelWithIdAndName { Id = 1, Name = "in [ valid" }; + var toModel = Serialize(model); + ModelWithIdAndName.AssertIsEqual(toModel, model); + + var model2 = new ModelWithIdAndName { Id = 1, Name = "in valid]" }; + var toModel2 = Serialize(model2); + ModelWithIdAndName.AssertIsEqual(toModel2, model2); + } + + [Test] + public void Can_convert_ModelWithMapAndList_with_ListChar() + { + var model = new ModelWithMapAndList + { + Id = 1, + Name = "in [ valid", + List = new List { + new ModelWithIdAndName{Id = 1, Name = "field [in valid] has stuff"}, + new ModelWithIdAndName{Id = 1, Name = "field [in valid] has stuff"}, + }, + }; + var toModel = Serialize(model); + //ModelWithMapAndList.AssertIsEqual(toModel, model); + } + + [Test] + public void Can_convert_ArrayDtoWithOrders() + { + var model = DtoFactory.ArrayDtoWithOrders; + var toModel = Serialize(model); + + Assert.That(model.Equals(toModel), Is.True); + } + + [Test] + public void Can_convert_Field_Map_or_List_with_invalid_chars() + { + var instance = new ModelWithMapAndList + { + Id = 1, + Name = fieldWithInvalidChars, + List = new List { fieldWithInvalidChars, fieldWithInvalidChars }, + Map = new Dictionary { { fieldWithInvalidChars, fieldWithInvalidChars } }, + }; + + Serialize(instance); + } + + [Test] + public void Can_convert_Field_Map_or_List_with_single_invalid_char() + { + foreach (var invalidChar in allCharsUsed) + { + var singleInvalidChar = $"a {invalidChar} b"; + + var instance = new ModelWithMapAndList + { + Id = 1, + Name = singleInvalidChar, + List = new List { singleInvalidChar, singleInvalidChar }, + Map = new Dictionary { { singleInvalidChar, singleInvalidChar } }, + }; + + Serialize(instance); + } + } + + [Test] + public void Can_convert_CustomerDto() + { + var model = DtoFactory.CustomerDto; + var toModel = Serialize(model); + + Assert.That(model.Equals(toModel), Is.True); + } + + [Test] + public void Can_convert_CustomerOrderListDto() + { + var model = DtoFactory.CustomerOrderListDto; + var toModel = Serialize(model); + + Assert.That(model.Equals(toModel), Is.True); + } + + [Test] + public void Can_convert_List_Guid() + { + var model = new List { + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + }; + + var toModel = Serialize(model); + + Assert.That(toModel, Is.EquivalentTo(model)); + } + + [Test] + public void Can_deserialize_int_with_leading_zeros() + { + Assert.That("01".FromJson(), Is.EqualTo(1)); + Assert.That("01".FromJson(), Is.EqualTo(1)); + Assert.That("01".FromJson(), Is.EqualTo(1)); + } + + public class EmptyCollections + { + public string[] Strings { get; set; } + public int[] Ints { get; set; } + public List IntList { get; set; } + } + + [Test] + public void Can_deserialize_empty_array() + { + Assert.That("[]".FromJson(), Is.EquivalentTo(new string[0])); + Assert.That("[]".FromJson(), Is.EquivalentTo(new int[0])); + Assert.That("[]".FromJson>(), Is.EquivalentTo(new List())); + + Assert.That("{\"Strings\":[]}".FromJson().Strings, Is.EquivalentTo(new string[0])); + Assert.That("{\"Ints\":[]}".FromJson().Ints, Is.EquivalentTo(new int[0])); + Assert.That("{\"IntList\":[]}".FromJson().IntList, Is.EquivalentTo(new List())); + } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/BclStructTests.cs b/tests/ServiceStack.Text.Tests/BclStructTests.cs index 4fb8b9d12..631d1c384 100644 --- a/tests/ServiceStack.Text.Tests/BclStructTests.cs +++ b/tests/ServiceStack.Text.Tests/BclStructTests.cs @@ -1,45 +1,46 @@ using System; using System.Drawing; +using System.Runtime.Serialization; using NUnit.Framework; -using ServiceStack.Common; namespace ServiceStack.Text.Tests { - public class BclStructTests : TestBase - { - static BclStructTests() - { - JsConfig.SerializeFn = c => c.ToString().Replace("Color ", "").Replace("[", "").Replace("]", ""); - JsConfig.DeSerializeFn = Color.FromName; - } - - [Test] - public void Can_serialize_Color() - { - var color = Color.Red; - - var fromColor = Serialize(color); - - Assert.That(fromColor, Is.EqualTo(color)); - } - - public enum MyEnum - { - Enum1, - Enum2, - Enum3, - } - - [Test] - public void Can_serialize_arrays_of_enums() - { - var enums = new[] { MyEnum.Enum1, MyEnum.Enum2, MyEnum.Enum3 }; - var fromEnums = Serialize(enums); - - Assert.That(fromEnums[0], Is.EqualTo(MyEnum.Enum1)); - Assert.That(fromEnums[1], Is.EqualTo(MyEnum.Enum2)); - Assert.That(fromEnums[2], Is.EqualTo(MyEnum.Enum3)); - } + public class BclStructTests : TestBase + { +#if !NETCORE + static BclStructTests() + { + JsConfig.SerializeFn = c => c.ToString().Replace("Color ", "").Replace("[", "").Replace("]", ""); + JsConfig.DeSerializeFn = System.Drawing.Color.FromName; + } + + [Test] + public void Can_serialize_Color() + { + var color = Color.Red; + + var fromColor = Serialize(color); + + Assert.That(fromColor, Is.EqualTo(color)); + } +#endif + public enum MyEnum + { + Enum1, + Enum2, + Enum3, + } + + [Test] + public void Can_serialize_arrays_of_enums() + { + var enums = new[] { MyEnum.Enum1, MyEnum.Enum2, MyEnum.Enum3 }; + var fromEnums = Serialize(enums); + + Assert.That(fromEnums[0], Is.EqualTo(MyEnum.Enum1)); + Assert.That(fromEnums[1], Is.EqualTo(MyEnum.Enum2)); + Assert.That(fromEnums[2], Is.EqualTo(MyEnum.Enum3)); + } [Flags] public enum ExampleEnum @@ -76,5 +77,45 @@ public void Can_serialize_dto_with_enum_flags() Assert.That(deserialized.Enum, Is.EqualTo(ExampleEnum.One | ExampleEnum.Four)); } - } + + [DataContract] + public class Item + { + [DataMember(Name = "favorite")] + public bool IsFavorite { get; set; } + } + + [Test] + public void Can_customize_bool_deserialization() + { + var dto1 = "{\"favorite\":1}".FromJson(); + Assert.That(dto1.IsFavorite, Is.True); + + var dto0 = "{\"favorite\":0}".FromJson(); + Assert.That(dto0.IsFavorite, Is.False); + + var dtoTrue = "{\"favorite\":true}".FromJson(); + Assert.That(dtoTrue.IsFavorite, Is.True); + + var dtoFalse = "{\"favorite\":false}".FromJson(); + Assert.That(dtoFalse.IsFavorite, Is.False); + } + + [Test] + public void GetUnderlyingTypeCode_tests() + { + //without explicit putting namespace 'System' before TypeCode test fails on .NET Core + Assert.That(Type.GetTypeCode(typeof(int)), Is.EqualTo(System.TypeCode.Int32)); + Assert.That(Type.GetTypeCode(typeof(int?)), Is.EqualTo(System.TypeCode.Object)); + Assert.That(Type.GetTypeCode(typeof(string)), Is.EqualTo(System.TypeCode.String)); + Assert.That(Type.GetTypeCode(typeof(TypeCode)), Is.EqualTo(System.TypeCode.Int32)); //enum + + Assert.That(typeof(int).GetUnderlyingTypeCode(), Is.EqualTo(TypeCode.Int32)); + Assert.That(typeof(int?).GetUnderlyingTypeCode(), Is.EqualTo(TypeCode.Int32)); + Assert.That(typeof(float?).GetUnderlyingTypeCode(), Is.EqualTo(TypeCode.Single)); + Assert.That(typeof(double?).GetUnderlyingTypeCode(), Is.EqualTo(TypeCode.Double)); + Assert.That(typeof(decimal?).GetUnderlyingTypeCode(), Is.EqualTo(TypeCode.Decimal)); + Assert.That(typeof(DateTime?).GetUnderlyingTypeCode(), Is.EqualTo(TypeCode.DateTime)); + } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Benchmarks/JsonDeserializationBenchmarks.cs b/tests/ServiceStack.Text.Tests/Benchmarks/JsonDeserializationBenchmarks.cs new file mode 100644 index 000000000..d86e5dcf6 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Benchmarks/JsonDeserializationBenchmarks.cs @@ -0,0 +1,38 @@ +using System.IO; +using NUnit.Framework; +using ServiceStack.Text.Tests.DynamicModels; + +namespace ServiceStack.Text.Tests.Benchmarks +{ + public class JsonDeserializationBenchmarks + { + static ModelWithAllTypes allTypesModel = ModelWithAllTypes.Create(3); + static ModelWithCommonTypes commonTypesModel = ModelWithCommonTypes.Create(3); + static MemoryStream stream = new MemoryStream(32768); + const string serializedString = "this is the test string"; + readonly string serializedString256 = new string('t', 256); + readonly string serializedString512 = new string('t', 512); + readonly string serializedString4096 = new string('t', 4096); + + static string commonTypesModelJson; + static string stringTypeJson; + + static JsonDeserializationBenchmarks() + { + commonTypesModelJson = JsonSerializer.SerializeToString(commonTypesModel); + stringTypeJson = JsonSerializer.SerializeToString(StringType.Create()); + } + + [Test] + public void DeserializeJsonCommonTypes() + { + var result = JsonSerializer.DeserializeFromString(commonTypesModelJson); + } + + [Test] + public void DeserializeStringType() + { + var result = JsonSerializer.DeserializeFromString(stringTypeJson); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Benchmarks/JsonSerializationBenchmarks.cs b/tests/ServiceStack.Text.Tests/Benchmarks/JsonSerializationBenchmarks.cs new file mode 100644 index 000000000..44e56c12e --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Benchmarks/JsonSerializationBenchmarks.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; +using System.Text; +using NUnit.Framework; +using ServiceStack.Text.Json; +using ServiceStack.Text.Tests.DynamicModels; + +namespace ServiceStack.Text.Tests.Benchmarks +{ + public class JsonSerializationBenchmarks + { + static ModelWithAllTypes allTypesModel = ModelWithAllTypes.Create(3); + static ModelWithCommonTypes commonTypesModel = ModelWithCommonTypes.Create(3); + static MemoryStream stream = new MemoryStream(32768); + const string serializedString = "this is the test string"; + readonly string serializedString256 = new string('t', 256); + readonly string serializedString512 = new string('t', 512); + readonly string serializedString4096 = new string('t', 4096); + + [Test] + public void SerializeJsonAllTypes() + { + string result = JsonSerializer.SerializeToString(allTypesModel); + } + + [Test] + public void SerializeJsonCommonTypes() + { + string result = JsonSerializer.SerializeToString(commonTypesModel); + } + + [Test] + public void SerializeJsonString() + { + string result = JsonSerializer.SerializeToString(serializedString); + } + + [Test] + public void SerializeJsonStringToStream() + { + stream.Position = 0; + JsonSerializer.SerializeToStream(serializedString, stream); + } + + [Test] + public void SerializeJsonString256ToStream() + { + stream.Position = 0; + JsonSerializer.SerializeToStream(serializedString256, stream); + } + + [Test] + public void SerializeJsonString512ToStream() + { + stream.Position = 0; + JsonSerializer.SerializeToStream(serializedString512, stream); + } + + [Test] + public void SerializeJsonString4096ToStream() + { + stream.Position = 0; + JsonSerializer.SerializeToStream(serializedString4096, stream); + } + + [Test] + public void SerializeJsonStringToStreamDirectly() + { + stream.Position = 0; + string tmp = JsonSerializer.SerializeToString(serializedString); + byte[] arr = Encoding.UTF8.GetBytes(tmp); + stream.Write(arr, 0, arr.Length); + } + + + [Test] + public void SerializeJsonAllTypesToStream() + { + stream.Position = 0; + JsonSerializer.SerializeToStream(allTypesModel, stream); + } + + [Test] + public void SerializeJsonCommonTypesToStream() + { + stream.Position = 0; + JsonSerializer.SerializeToStream(commonTypesModel, stream); + } + + [Test] + public void SerializeJsonStringToStreamUsingDirectStreamWriter() + { + stream.Position = 0; + var writer = new DirectStreamWriter(stream, JsConfig.UTF8Encoding); + JsonWriter.WriteRootObject(writer, serializedString); + writer.Flush(); + } + + [Test] + public void SerializeJsonString256ToStreamUsingDirectStreamWriter() + { + stream.Position = 0; + var writer = new DirectStreamWriter(stream, JsConfig.UTF8Encoding); + JsonWriter.WriteRootObject(writer, serializedString256); + writer.Flush(); + } + + [Test] + public void SerializeJsonString512ToStreamUsingDirectStreamWriter() + { + stream.Position = 0; + var writer = new DirectStreamWriter(stream, JsConfig.UTF8Encoding); + JsonWriter.WriteRootObject(writer, serializedString512); + writer.Flush(); + } + + [Test] + public void SerializeJsonString4096ToStreamUsingDirectStreamWriter() + { + stream.Position = 0; + var writer = new DirectStreamWriter(stream, JsConfig.UTF8Encoding); + JsonWriter.WriteRootObject(writer, serializedString4096); + writer.Flush(); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Benchmarks/ModelWithCommonTypes.cs b/tests/ServiceStack.Text.Tests/Benchmarks/ModelWithCommonTypes.cs new file mode 100644 index 000000000..8b82f0a46 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Benchmarks/ModelWithCommonTypes.cs @@ -0,0 +1,77 @@ +using System; + +namespace ServiceStack.Text.Tests.Benchmarks +{ + public class ModelWithCommonTypes + { + public char CharValue { get; set; } + + public byte ByteValue { get; set; } + + public sbyte SByteValue { get; set; } + + public short ShortValue { get; set; } + + public ushort UShortValue { get; set; } + + public int IntValue { get; set; } + + public uint UIntValue { get; set; } + + public long LongValue { get; set; } + + public ulong ULongValue { get; set; } + + public float FloatValue { get; set; } + + public double DoubleValue { get; set; } + + public decimal DecimalValue { get; set; } + + public DateTime DateTimeValue { get; set; } + + public TimeSpan TimeSpanValue { get; set; } + + public Guid GuidValue { get; set; } + + public static ModelWithCommonTypes Create(byte i) + { + return new ModelWithCommonTypes + { + ByteValue = i, + CharValue = (char)i, + DateTimeValue = new DateTime(2000, 1, 1 + i), + DecimalValue = i, + DoubleValue = i, + FloatValue = i, + IntValue = i, + LongValue = i, + SByteValue = (sbyte)i, + ShortValue = i, + TimeSpanValue = new TimeSpan(i), + UIntValue = i, + ULongValue = i, + UShortValue = i, + GuidValue = Guid.NewGuid(), + }; + } + } + + public class StringType + { + public string Value1 { get; set; } + public string Value2 { get; set; } + public string Value3 { get; set; } + public string Value4 { get; set; } + public string Value5 { get; set; } + public string Value6 { get; set; } + public string Value7 { get; set; } + + public static StringType Create() + { + var st = new StringType(); + st.Value1 = st.Value2 = st.Value3 = st.Value4 = st.Value5 = st.Value6 = st.Value7 = "Hello, world"; + return st; + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CombinePathTests.cs b/tests/ServiceStack.Text.Tests/CombinePathTests.cs new file mode 100644 index 000000000..9fffb7b1c --- /dev/null +++ b/tests/ServiceStack.Text.Tests/CombinePathTests.cs @@ -0,0 +1,70 @@ +using NUnit.Framework; + +namespace ServiceStack.Text.Tests +{ + public class CombinePathTests + { + [Test] + public void Does_combine_paths() + { + Assert.That("/a".CombineWith("b"), Is.EqualTo("/a/b")); + Assert.That("a".CombineWith("b"), Is.EqualTo("a/b")); + Assert.That("/a/b".CombineWith("c"), Is.EqualTo("/a/b/c")); + Assert.That("a/b".CombineWith("c"), Is.EqualTo("a/b/c")); + Assert.That("/a/b".CombineWith("c/d"), Is.EqualTo("/a/b/c/d")); + Assert.That("/a/b".CombineWith("c", "d"), Is.EqualTo("/a/b/c/d")); + + Assert.That("http://example.org/a/b".CombineWith("c", "d"), Is.EqualTo("http://example.org/a/b/c/d")); + } + + [Test] + public void Does_combine_paths_with_trailing_slashes() + { + Assert.That("/a/".CombineWith("b"), Is.EqualTo("/a/b")); + Assert.That("/a/".CombineWith("b/"), Is.EqualTo("/a/b/")); + Assert.That("a/".CombineWith("/b"), Is.EqualTo("a/b")); + Assert.That("/a/b/".CombineWith("/c/"), Is.EqualTo("/a/b/c/")); + Assert.That("a/b/".CombineWith("c"), Is.EqualTo("a/b/c")); + Assert.That("/a/b/".CombineWith("/c/d"), Is.EqualTo("/a/b/c/d")); + Assert.That("/a/b/".CombineWith("/c", "/d"), Is.EqualTo("/a/b/c/d")); + + Assert.That("http://example.org/a/b/".CombineWith("/c/", "/d"), Is.EqualTo("http://example.org/a/b/c/d")); + } + + [Test] + public void Can_resolve_paths() + { + Assert.That("/a/b/../".ResolvePaths(), Is.EqualTo("/a/")); + Assert.That("/a/b/..".ResolvePaths(), Is.EqualTo("/a")); + Assert.That("a/b/..".ResolvePaths(), Is.EqualTo("a")); + + Assert.That("a/../b".ResolvePaths(), Is.EqualTo("b")); + Assert.That("a/../b/./c".ResolvePaths(), Is.EqualTo("b/c")); + Assert.That("a/b/c/d/../..".ResolvePaths(), Is.EqualTo("a/b")); + Assert.That("a/b/../../c/d".ResolvePaths(), Is.EqualTo("c/d")); + + Assert.That("a/..".ResolvePaths(), Is.EqualTo("")); + Assert.That("a/../..".ResolvePaths(), Is.EqualTo("..")); + Assert.That("a/../../".ResolvePaths(), Is.EqualTo("../")); + Assert.That("a/../../b".ResolvePaths(), Is.EqualTo("../b")); + } + + [Test] + public void Can_resolve_paths_with_urls() + { + Assert.That("http://example.org/a/b/../".ResolvePaths(), Is.EqualTo("http://example.org/a/")); + Assert.That("http://example.org/a/b/..".ResolvePaths(), Is.EqualTo("http://example.org/a")); + + Assert.That("http://example.org/a/../b".ResolvePaths(), Is.EqualTo("http://example.org/b")); + Assert.That("http://example.org/a/../b/./c".ResolvePaths(), Is.EqualTo("http://example.org/b/c")); + Assert.That("http://example.org/a/b/c/d/../..".ResolvePaths(), Is.EqualTo("http://example.org/a/b")); + Assert.That("http://example.org/a/b/../../c/d".ResolvePaths(), Is.EqualTo("http://example.org/c/d")); + + Assert.That("http://example.org/a/..".ResolvePaths(), Is.EqualTo("http://example.org")); + Assert.That("http://example.org/a/../..".ResolvePaths(), Is.EqualTo("http://")); + Assert.That("http://example.org/a/../../".ResolvePaths(), Is.EqualTo("http://")); + Assert.That("http://example.org/a/../../b".ResolvePaths(), Is.EqualTo("http://b")); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/ConversionTests.cs b/tests/ServiceStack.Text.Tests/ConversionTests.cs new file mode 100644 index 000000000..be6647f12 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/ConversionTests.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using NUnit.Framework; + +namespace ServiceStack.Text.Tests +{ + public class ConversionTests + { + [Test] + public void Converting_ObjectDictionary_ToStringDictionary_converts_collection_to_jsv() + { + var objDictionary = new Dictionary + { + {"string", "foo,bar" }, + {"intArray", new[] {1, 2} }, + {"stringArray", new[] {"foo", "bar"} }, + {"stringEscapeChars", "a, 'b" }, + {"stringArrayEscapeChars", new[] { "a, b", "c 'd"} }, + }; + + var strDictionary = objDictionary.ToStringDictionary(); + + Assert.That(strDictionary["string"], Is.EqualTo("foo,bar")); + Assert.That(strDictionary["intArray"], Is.EqualTo("[1,2]")); + Assert.That(strDictionary["stringArray"], Is.EqualTo("[foo,bar]")); + Assert.That(strDictionary["stringEscapeChars"], Is.EqualTo("a, 'b")); + Assert.That(strDictionary["stringArrayEscapeChars"], Is.EqualTo("[\"a, b\",c 'd]")); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CsvSerializerConfigTests.cs b/tests/ServiceStack.Text.Tests/CsvSerializerConfigTests.cs new file mode 100644 index 000000000..99ef974d0 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/CsvSerializerConfigTests.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Text.Common; + +namespace ServiceStack.Text.Tests +{ + [TestFixture] + public class CsvSerializerConfigTests + { + [Test] + public void Does_use_CsvConfig() + { + CsvConfig.ItemSeperatorString = "|"; + CsvConfig.ItemDelimiterString = "`"; + CsvConfig.RowSeparatorString = "\n\n"; + + var dtos = new[] { + new ModelWithIdAndName { Id = 1, Name = "Value" }, + new ModelWithIdAndName { Id = 2, Name = "Value|Escaped" }, + }; + + var csv = dtos.ToCsv(); + Assert.That(csv, Is.EqualTo("Id|Name\n\n1|Value\n\n2|`Value|Escaped`\n\n")); + + var maps = new List>() + { + new Dictionary { {"Id", "1"}, {"Name", "Value"} }, + new Dictionary { {"Id", "2"}, {"Name", "Value|Escaped"} }, + }; + + csv = maps.ToCsv(); + Assert.That(csv, Is.EqualTo("Id|Name\n\n1|Value\n\n2|`Value|Escaped`\n\n")); + + CsvConfig.Reset(); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CsvSerializerTests.cs b/tests/ServiceStack.Text.Tests/CsvSerializerTests.cs index ad2199b14..0ac783e0a 100644 --- a/tests/ServiceStack.Text.Tests/CsvSerializerTests.cs +++ b/tests/ServiceStack.Text.Tests/CsvSerializerTests.cs @@ -1,186 +1,572 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; using Northwind.Common.DataModel; using NUnit.Framework; using ServiceStack.Text.Tests.Support; namespace ServiceStack.Text.Tests { - [TestFixture] - public class CsvSerializerTests - { - static CsvSerializerTests() - { - NorthwindData.LoadData(false); - } - - public void Serialize(T data) - { - //TODO: implement serializer and test properly - var csv = CsvSerializer.SerializeToString(data); - csv.Print(); - } - - [Test] - public void Can_Serialize_Movie() - { - Serialize(MoviesData.Movies[0]); - } - - [Test] - public void Can_Serialize_Movies() - { - Serialize(MoviesData.Movies); - } + [TestFixture] +#if NETCORE + [Ignore("Fix Northwind.dll")] +#endif + public class CsvSerializerTests + { + static CsvSerializerTests() + { + NorthwindData.LoadData(false); + } + + [OneTimeSetUp] + public void TestFixtureSetUp() + { + JsConfig.SkipDateTimeConversion = true; + } + + [OneTimeTearDown] + public void TestFixtureTearDown() + { + JsConfig.Reset(); + } + + public void Serialize(T data) + { + //TODO: implement serializer and test properly + var csv = CsvSerializer.SerializeToString(data); + csv.Print(); + } + + public object SerializeAndDeserialize(T data) + { + var csv = CsvSerializer.SerializeToString(data); + csv.Print(); + + var dto = CsvSerializer.DeserializeFromString(csv); + AssertEqual(dto, data); + + using (var reader = new StringReader(csv)) + { + dto = CsvSerializer.DeserializeFromReader(reader); + AssertEqual(dto, data); + } + + using (var ms = new MemoryStream(csv.ToUtf8Bytes())) + { + dto = CsvSerializer.DeserializeFromStream(ms); + AssertEqual(dto, data); + } + + using (var ms = new MemoryStream(csv.ToUtf8Bytes())) + { + dto = (T)CsvSerializer.DeserializeFromStream(typeof(T), ms); + AssertEqual(dto, data); + } + + return dto; + } + + private static void AssertEqual(T dto, T data) + { + var dataArray = data is IEnumerable ? (data as IEnumerable).Map(x => x).ToArray() : null; + var dtoArray = dto is IEnumerable ? (dto as IEnumerable).Map(x => x).ToArray() : null; + + if (dataArray != null && dtoArray != null) + Assert.That(dtoArray, Is.EquivalentTo(dataArray)); + else + Assert.That(dto, Is.EqualTo(data)); + } + + [Test] + public void Does_parse_new_lines() + { + Assert.That(CsvReader.ParseLines("A,B\nC,D"), Is.EquivalentTo(new[] { "A,B", "C,D" })); + Assert.That(CsvReader.ParseLines("A,B\nC,D\n"), Is.EquivalentTo(new[] { "A,B", "C,D" })); + Assert.That(CsvReader.ParseLines("A,B\nC,D\n\n"), Is.EquivalentTo(new[] { "A,B", "C,D" })); + + Assert.That(CsvReader.ParseLines("A,B\r\nC,D"), Is.EquivalentTo(new[] { "A,B", "C,D" })); + Assert.That(CsvReader.ParseLines("A,B\r\nC,D\r\n"), Is.EquivalentTo(new[] { "A,B", "C,D" })); + Assert.That(CsvReader.ParseLines("A,B\r\nC,D\r\n\r\n"), Is.EquivalentTo(new[] { "A,B", "C,D" })); + + Assert.That(CsvReader.ParseLines("\"A,B\"\n\"C,D\""), Is.EquivalentTo(new[] { "\"A,B\"", "\"C,D\"" })); + Assert.That(CsvReader.ParseLines("\"A,B\",B\nC,\"C,D\""), Is.EquivalentTo(new[] { "\"A,B\",B", "C,\"C,D\"" })); + + Assert.That(CsvReader.ParseLines("\"A\nB\",B\nC,\"C\r\nD\""), Is.EquivalentTo(new[] { "\"A\nB\",B", "C,\"C\r\nD\"" })); + } + + [Test] + public void Does_parse_fields() + { + Assert.That(CsvReader.ParseFields("A,B"), Is.EquivalentTo(new[] { "A", "B" })); + Assert.That(CsvReader.ParseFields("\"A\",B"), Is.EquivalentTo(new[] { "A", "B" })); + Assert.That(CsvReader.ParseFields("\"A\",\"B,C\""), Is.EquivalentTo(new[] { "A", "B,C" })); + Assert.That(CsvReader.ParseFields("\"A\nB\",\"B,\r\nC\""), Is.EquivalentTo(new[] { "A\nB", "B,\r\nC" })); + Assert.That(CsvReader.ParseFields("\"A\"\",B\""), Is.EquivalentTo(new[] { "A\",B" })); + + Assert.That(CsvReader.ParseFields(",A,B"), Is.EquivalentTo(new[] { null, "A", "B" })); + Assert.That(CsvReader.ParseFields("A,,B"), Is.EquivalentTo(new[] { "A", null, "B" })); + Assert.That(CsvReader.ParseFields("A,B,"), Is.EquivalentTo(new[] { "A", "B", null })); + + Assert.That(CsvReader.ParseFields("\"\",A,B"), Is.EquivalentTo(new[] { "", "A", "B" })); + Assert.That(CsvReader.ParseFields("A,\"\",B"), Is.EquivalentTo(new[] { "A", "", "B" })); + Assert.That(CsvReader.ParseFields("A,B,\"\""), Is.EquivalentTo(new[] { "A", "B", "" })); + } + + [Test] + public void Does_parse_fields_with_unmatchedJsMark() + { + Assert.That(CsvReader.ParseFields("{A,B"), Is.EqualTo(new[] { "{A", "B" })); + Assert.That(CsvReader.ParseFields("{A},B"), Is.EqualTo(new[] { "{A}", "B" })); + Assert.That(CsvReader.ParseFields("[A,B"), Is.EqualTo(new[] { "[A", "B" })); + Assert.That(CsvReader.ParseFields("[A],B"), Is.EqualTo(new[] { "[A]", "B" })); + Assert.That(CsvReader.ParseFields("[{A],B"), Is.EqualTo(new[] { "[{A]", "B" })); + Assert.That(CsvReader.ParseFields("[A},B"), Is.EqualTo(new[] { "[A}", "B" })); + Assert.That(CsvReader.ParseFields("[[A],B"), Is.EqualTo(new[] { "[[A]", "B" })); + Assert.That(CsvReader.ParseFields("A],B"), Is.EqualTo(new[] { "A]", "B" })); + Assert.That(CsvReader.ParseFields("A},B"), Is.EqualTo(new[] { "A}", "B" })); + } + + [Test] + public void Can_Serialize_Movie() + { + Serialize(MoviesData.Movies[0]); + } + + [Test] + public void Can_Serialize_Movies() + { + SerializeAndDeserialize(MoviesData.Movies); + } + + [Test] + public void Can_Serialize_inherited_Movies() + { + SerializeAndDeserialize(new Movies(MoviesData.Movies)); + } + + [Test] + public void Does_Serialize_back_into_Array() + { + var dto = SerializeAndDeserialize(MoviesData.Movies.ToArray()); + Assert.That(dto.GetType().IsArray); + } + + public class SubMovie + { + public DateTime ReleaseDate { get; set; } + public string Title { get; set; } + public decimal Rating { get; set; } + public string ImdbId { get; set; } + } + + [Test] + public void Does_serialize_partial_DTO_in_order_of_Headers() + { + var subMovies = MoviesData.Movies.Map(x => x.ConvertTo()); + var csv = CsvSerializer.SerializeToString(subMovies); + + csv.Print(); + Assert.That(csv, Does.StartWith("ReleaseDate,Title,Rating,ImdbId\r\n")); + + var movies = csv.FromCsv>(); + + Assert.That(movies.Count, Is.EqualTo(subMovies.Count)); + for (int i = 0; i < subMovies.Count; i++) + { + var actual = movies[i]; + var expected = MoviesData.Movies[i]; + + Assert.That(actual.Id, Is.EqualTo(0)); + Assert.That(actual.ReleaseDate, Is.EqualTo(expected.ReleaseDate)); + Assert.That(actual.Title, Is.EqualTo(expected.Title)); + Assert.That(actual.Rating, Is.EqualTo(expected.Rating)); + Assert.That(actual.ImdbId, Is.EqualTo(expected.ImdbId)); + } + } [Test] public void Can_Serialize_MovieResponse_Dto() { - Serialize(new MovieResponse { Movie = MoviesData.Movies[0] }); + SerializeAndDeserialize(new MovieResponse { Movie = MoviesData.Movies[0] }); } [Test] public void Can_Serialize_MoviesResponse_Dto() { - Serialize(new MoviesResponse { Movies = MoviesData.Movies }); + SerializeAndDeserialize(new MoviesResponse { Movies = MoviesData.Movies }); } [Test] public void Can_Serialize_MoviesResponse2_Dto() { - Serialize(new MoviesResponse2 { Movies = MoviesData.Movies }); - } - - [Test] - public void serialize_Category() - { - Serialize(NorthwindData.Categories[0]); - } - - [Test] - public void serialize_Categories() - { - Serialize(NorthwindData.Categories); - } - - [Test] - public void serialize_Customer() - { - Serialize(NorthwindData.Customers[0]); - } - - [Test] - public void serialize_Customers() - { - Serialize(NorthwindData.Customers); - } - - [Test] - public void serialize_Employee() - { - Serialize(NorthwindData.Employees[0]); - } - - [Test] - public void serialize_Employees() - { - Serialize(NorthwindData.Employees); - } - - [Test] - public void serialize_EmployeeTerritory() - { - Serialize(NorthwindData.EmployeeTerritories[0]); - } - - [Test] - public void serialize_EmployeeTerritories() - { - Serialize(NorthwindData.EmployeeTerritories); - } - - [Test] - public void serialize_OrderDetail() - { - Serialize(NorthwindData.OrderDetails[0]); - } - - [Test] - public void serialize_OrderDetails() - { - Serialize(NorthwindData.OrderDetails); - } - - [Test] - public void serialize_Order() - { - Serialize(NorthwindData.Orders[0]); - } - - [Test] - public void serialize_Orders() - { - Serialize(NorthwindData.Orders); - } - - [Test] - public void serialize_Product() - { - Serialize(NorthwindData.Products[0]); - } - - [Test] - public void serialize_Products() - { - Serialize(NorthwindData.Products); - } - - [Test] - public void serialize_Region() - { - Serialize(NorthwindData.Regions[0]); - } - - [Test] - public void serialize_Regions() - { - Serialize(NorthwindData.Regions); - } - - [Test] - public void serialize_Shipper() - { - Serialize(NorthwindData.Shippers[0]); - } - - [Test] - public void serialize_Shippers() - { - Serialize(NorthwindData.Shippers); - } - - [Test] - public void serialize_Supplier() - { - Serialize(NorthwindData.Suppliers[0]); - } - - [Test] - public void serialize_Suppliers() - { - Serialize(NorthwindData.Suppliers); - } - - [Test] - public void serialize_Territory() - { - Serialize(NorthwindData.Territories[0]); - } - - [Test] - public void serialize_Territories() - { - Serialize(NorthwindData.Territories); - } - - } + SerializeAndDeserialize(new MoviesResponse2 { Movies = MoviesData.Movies }); + } + + [Test] + public void Can_Deserialize_into_String_Dictionary() + { + var csv = MoviesData.Movies.ToCsv(); + + var dynamicMap = csv.FromCsv>>(); + Assert.That(dynamicMap.Count, Is.EqualTo(MoviesData.Movies.Count)); + + dynamicMap.PrintDump(); + + var movie = MoviesData.Movies[0]; + var map = dynamicMap[0]; + + Assert.That(map["Id"], Is.EqualTo(movie.Id.ToString())); + Assert.That(map["ImdbId"], Is.EqualTo(movie.ImdbId)); + Assert.That(map["Title"], Is.EqualTo(movie.Title)); + Assert.That(map["Rating"], Is.EqualTo(movie.Rating.ToString(CultureInfo.InvariantCulture))); + Assert.That(map["Director"], Is.EqualTo(movie.Director)); + Assert.That(map["ReleaseDate"], Is.EqualTo(movie.ReleaseDate.ToJsv())); + Assert.That(map["TagLine"], Is.EqualTo(movie.TagLine)); + Assert.That(map["Genres"], Is.EqualTo(movie.Genres.ToJsv())); + } + + [Test] + public void Can_deserialize_into_String_List() + { + var csv = MoviesData.Movies.ToCsv(); + + var dynamicList = csv.FromCsv>>(); + Assert.That(dynamicList.Count - 1, Is.EqualTo(MoviesData.Movies.Count)); + + dynamicList.PrintDump(); + + var movie = MoviesData.Movies[0]; + var headers = dynamicList[0]; + var first = dynamicList[1]; + + Assert.That(headers[0], Is.EqualTo("Id")); + Assert.That(headers[1], Is.EqualTo("ImdbId")); + Assert.That(headers[2], Is.EqualTo("Title")); + Assert.That(headers[3], Is.EqualTo("Rating")); + Assert.That(headers[4], Is.EqualTo("Director")); + Assert.That(headers[5], Is.EqualTo("ReleaseDate")); + Assert.That(headers[6], Is.EqualTo("TagLine")); + Assert.That(headers[7], Is.EqualTo("Genres")); + + Assert.That(first[0], Is.EqualTo(movie.Id.ToString())); + Assert.That(first[1], Is.EqualTo(movie.ImdbId)); + Assert.That(first[2], Is.EqualTo(movie.Title)); + Assert.That(first[3], Is.EqualTo(movie.Rating.ToString(CultureInfo.InvariantCulture))); + Assert.That(first[4], Is.EqualTo(movie.Director)); + Assert.That(first[5], Is.EqualTo(movie.ReleaseDate.ToJsv())); + Assert.That(first[6], Is.EqualTo(movie.TagLine)); + Assert.That(first[7], Is.EqualTo(movie.Genres.ToJsv())); + } + + [Test] + public void Can_Serialize_using_custom_CSV_ItemString() + { + CsvConfig.ItemSeperatorString = ";"; + var csv = NorthwindData.OrderDetails[0].ToCsv(); + + Assert.That(csv, Is.EqualTo( + "Id;OrderId;ProductId;UnitPrice;Quantity;Discount\r\n10248/11;10248;11;14;12;0\r\n")); + + var row = csv.FromCsv(); + + Assert.That(row, Is.EqualTo(NorthwindData.OrderDetails[0])); + + CsvConfig.Reset(); + } + + [Test] + public void Can_serialize_ObjectDictionary_list() + { + var rows = new List> + { + new Dictionary + { + { "Id", 1 }, + { "CustomerId", "ALFKI" }, + }, + new Dictionary + { + { "Id", 2 }, + { "CustomerId", "ANATR" }, + }, + }; + + Assert.That(rows.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI\n2,ANATR").Or.EqualTo("CustomerId,Id\nALFKI,1\nANATR,2")); + } + + [Test] + public void Can_serialize_StringDictionary_list() + { + var rows = new List> + { + new Dictionary + { + { "Id", "1" }, + { "CustomerId", "ALFKI" }, + }, + new Dictionary + { + { "Id", "2" }, + { "CustomerId", "ANATR" }, + }, + }; + + Assert.That(rows.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI\n2,ANATR").Or.EqualTo("CustomerId,Id\nALFKI,1\nANATR,2")); + } + + [Test] + public void Can_serialize_single_ObjectDictionary_or_ObjectKvps() + { + var row = new Dictionary + { + { "Id", 1 }, + { "CustomerId", "ALFKI" }, + }; + + Assert.That(row.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI").Or.EqualTo("CustomerId,Id\nALFKI,1")); + + var kvps = new[] + { + new KeyValuePair("Id", 1), + new KeyValuePair("CustomerId", "ALFKI"), + }; + + Assert.That(kvps.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI").Or.EqualTo("CustomerId,Id\nALFKI,1")); + } + + [Test] + public void Can_serialize_single_ObjectDictionary_or_ObjectKvps_WithEmptyString() + { + var row = new Dictionary + { + { "Id", 1 }, + { "CustomerId", "" }, + }; + + Assert.That(row.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,").Or.EqualTo("CustomerId,Id\n,1")); + + var kvps = new[] + { + new KeyValuePair("Id", 1), + new KeyValuePair("CustomerId", ""), + }; + + Assert.That(kvps.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,").Or.EqualTo("CustomerId,Id\n,1")); + } + + [Test] + public void Can_serialize_single_StringDictionary_or_StringKvps() + { + var row = new Dictionary + { + { "Id", "1" }, + { "CustomerId", "ALFKI" }, + }; + + Assert.That(row.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI").Or.EqualTo("CustomerId,Id\nALFKI,1")); + + var kvps = new[] + { + new KeyValuePair("Id", "1"), + new KeyValuePair("CustomerId", "ALFKI"), + }; + + Assert.That(kvps.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI").Or.EqualTo("CustomerId,Id\nALFKI,1")); + } + + [Test] + public void Can_serialize_fields_with_double_quotes() + { + var person = new Person { Id = 1, Name = "\"Mr. Lee\"" }; + Assert.That(person.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,Name\n1,\"\"\"Mr. Lee\"\"\"")); + var fromCsv = person.ToCsv().FromCsv(); + Assert.That(fromCsv, Is.EqualTo(person)); + + person = new Person { Id = 1, Name = "\"Anon\" Review" }; + Assert.That(person.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,Name\n1,\"\"\"Anon\"\" Review\"")); + fromCsv = person.ToCsv().FromCsv(); + Assert.That(fromCsv, Is.EqualTo(person)); + } + + public Order Clone(Order o) => new Order { + Id = o.Id, + CustomerId = o.CustomerId, + EmployeeId = o.EmployeeId, + OrderDate = o.OrderDate, + RequiredDate = o.RequiredDate, + ShippedDate = o.ShippedDate, + ShipVia = o.ShipVia, + Freight = o.Freight, + ShipName = o.ShipName, + ShipAddress = o.ShipAddress, + ShipCity = o.ShipCity, + ShipRegion = o.ShipRegion, + ShipPostalCode = o.ShipPostalCode, + ShipCountry = o.ShipCountry, + }; + + [Test] + public void Can_only_serialize_NonDefaultValues() + { + using var scope = JsConfig.With(new Config { + ExcludeDefaultValues = true + }); + + var orders = NorthwindData.Orders.Take(5).Map(Clone); + orders.ForEach(x => { + //non-default min values + x.RequiredDate = DateTime.MinValue; + x.ShipVia = 0; + + //default values + x.ShippedDate = null; + x.EmployeeId = default; + x.Freight = default; + x.ShipPostalCode = null; + x.ShipCountry = null; + }); + + var csv = orders.ToCsv(); + // csv.Print(); + var headers = csv.LeftPart('\r'); + headers.Print(); + Assert.That(headers, Is.EquivalentTo( + "Id,CustomerId,OrderDate,RequiredDate,ShipVia,ShipName,ShipAddress,ShipCity,ShipRegion")); + } + + [Test] + public void serialize_Category() + { + SerializeAndDeserialize(NorthwindData.Categories[0]); + } + + [Test] + public void serialize_Categories() + { + SerializeAndDeserialize(NorthwindData.Categories); + } + + [Test] + public void serialize_Customer() + { + SerializeAndDeserialize(NorthwindData.Customers[0]); + } + + [Test] + public void serialize_Customers() + { + SerializeAndDeserialize(NorthwindData.Customers); + } + + [Test] + public void serialize_Employee() + { + SerializeAndDeserialize(NorthwindData.Employees[0]); + } + + [Test] + public void serialize_Employees() + { + SerializeAndDeserialize(NorthwindData.Employees); + } + + [Test] + public void serialize_EmployeeTerritory() + { + SerializeAndDeserialize(NorthwindData.EmployeeTerritories[0]); + } + + [Test] + public void serialize_EmployeeTerritories() + { + SerializeAndDeserialize(NorthwindData.EmployeeTerritories); + } + + [Test] + public void serialize_OrderDetail() + { + SerializeAndDeserialize(NorthwindData.OrderDetails[0]); + } + + [Test] + public void serialize_OrderDetails() + { + SerializeAndDeserialize(NorthwindData.OrderDetails); + } + + [Test] + public void serialize_Order() + { + SerializeAndDeserialize(NorthwindData.Orders[0]); + } + + [Test] + public void serialize_Orders() + { + Serialize(NorthwindData.Orders); + } + + [Test] + public void serialize_Product() + { + SerializeAndDeserialize(NorthwindData.Products[0]); + } + + [Test] + public void serialize_Products() + { + SerializeAndDeserialize(NorthwindData.Products); + } + + [Test] + public void serialize_Region() + { + SerializeAndDeserialize(NorthwindData.Regions[0]); + } + + [Test] + public void serialize_Regions() + { + SerializeAndDeserialize(NorthwindData.Regions); + } + + [Test] + public void serialize_Shipper() + { + SerializeAndDeserialize(NorthwindData.Shippers[0]); + } + + [Test] + public void serialize_Shippers() + { + SerializeAndDeserialize(NorthwindData.Shippers); + } + + [Test] + public void serialize_Supplier() + { + SerializeAndDeserialize(NorthwindData.Suppliers[0]); + } + + [Test] + public void serialize_Suppliers() + { + SerializeAndDeserialize(NorthwindData.Suppliers); + } + + [Test] + public void serialize_Territory() + { + SerializeAndDeserialize(NorthwindData.Territories[0]); + } + + [Test] + public void serialize_Territories() + { + SerializeAndDeserialize(NorthwindData.Territories); + } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CsvStreamTests.cs b/tests/ServiceStack.Text.Tests/CsvStreamTests.cs index 394463bad..770f094db 100644 --- a/tests/ServiceStack.Text.Tests/CsvStreamTests.cs +++ b/tests/ServiceStack.Text.Tests/CsvStreamTests.cs @@ -4,13 +4,13 @@ namespace ServiceStack.Text.Tests { - [TestFixture] - public class CsvStreamTests - { - protected void Log(string fmt, params object[] args) - { - Console.WriteLine("{0}", String.Format(fmt, args).Trim()); - } + [TestFixture] + public class CsvStreamTests + { + protected void Log(string fmt, params object[] args) + { + Console.WriteLine("{0}", String.Format(fmt, args).Trim()); + } [TearDown] public void TearDown() @@ -18,222 +18,231 @@ public void TearDown() CsvConfig.Reset(); } - [Test] - public void Can_create_csv_from_Customers() - { - NorthwindData.LoadData(false); - var csv = CsvSerializer.SerializeToCsv(NorthwindData.Customers); - Log(csv); - Assert.That(csv, Is.Not.Null); - } - - [Test] - public void Can_create_csv_from_Customers_pipe_separator() - { - CsvConfig.ItemSeperatorString = "|"; - NorthwindData.LoadData(false); - var csv = CsvSerializer.SerializeToCsv(NorthwindData.Customers); - Log(csv); - Assert.That(csv, Is.Not.Null); - } - - [Test] - public void Can_create_csv_from_Customers_pipe_delimiter() - { - CsvConfig.ItemDelimiterString = "|"; - NorthwindData.LoadData(false); - var csv = CsvSerializer.SerializeToCsv(NorthwindData.Customers); - Log(csv); - Assert.That(csv, Is.Not.Null); - } - - [Test] - public void Can_create_csv_from_Customers_pipe_row_separator() - { - CsvConfig.RowSeparatorString ="|"; - NorthwindData.LoadData(false); - var csv = CsvSerializer.SerializeToCsv(NorthwindData.Customers); - Log(csv); - Assert.That(csv, Is.Not.Null); - } - - [Test] - public void Can_create_csv_from_Categories() - { - NorthwindData.LoadData(false); - var category = NorthwindFactory.Category(1, "between \"quotes\" here", "with, comma", null); - var categories = new[] { category, category }; - var csv = CsvSerializer.SerializeToCsv(categories); - Log(csv); - Assert.That(csv, Is.EqualTo( - "Id,CategoryName,Description,Picture" - + Environment.NewLine - + "1,\"between \"\"quotes\"\" here\",\"with, comma\"," - + Environment.NewLine - + "1,\"between \"\"quotes\"\" here\",\"with, comma\"," - + Environment.NewLine - )); - } - - [Test] - public void Can_create_csv_from_Categories_pipe_separator() - { - CsvConfig.ItemSeperatorString = "|"; - NorthwindData.LoadData(false); - var category = NorthwindFactory.Category(1, "between \"quotes\" here", "with, comma", null); - var categories = new[] { category, category }; - var csv = CsvSerializer.SerializeToCsv(categories); - Log(csv); - Assert.That(csv, Is.EqualTo( - "Id|CategoryName|Description|Picture" - + Environment.NewLine - + "1|\"between \"\"quotes\"\" here\"|with, comma|" - + Environment.NewLine - + "1|\"between \"\"quotes\"\" here\"|with, comma|" - + Environment.NewLine - )); - } - - [Test] - public void Can_create_csv_from_Categories_pipe_delimiter() - { - CsvConfig.ItemDelimiterString = "|"; - NorthwindData.LoadData(false); - var category = NorthwindFactory.Category(1, "between \"quotes\" here", "with, comma", null); - var categories = new[] { category, category }; - var csv = CsvSerializer.SerializeToCsv(categories); - Log(csv); - Assert.That(csv, Is.EqualTo( - "Id,CategoryName,Description,Picture" - + Environment.NewLine - + "1,between \"quotes\" here,|with, comma|," - + Environment.NewLine - + "1,between \"quotes\" here,|with, comma|," - + Environment.NewLine - )); - } - - [Test] - public void Can_create_csv_from_Categories_long_delimiter() - { - CsvConfig.ItemDelimiterString = "~^~"; - NorthwindData.LoadData(false); - var category = NorthwindFactory.Category(1, "between \"quotes\" here", "with, comma", null); - var categories = new[] { category, category }; - var csv = CsvSerializer.SerializeToCsv(categories); - Log(csv); - Assert.That(csv, Is.EqualTo( - "Id,CategoryName,Description,Picture" - + Environment.NewLine - + "1,between \"quotes\" here,~^~with, comma~^~," - + Environment.NewLine - + "1,between \"quotes\" here,~^~with, comma~^~," - + Environment.NewLine - )); - } - - [Test] - public void Can_generate_csv_with_invalid_chars() - { - var fields = new[] { "1", "2", "3\"", "4", "5\"five,six\"", "7,7.1", "\"7,7.1\"", "8" }; - var csv = CsvSerializer.SerializeToCsv(fields); - Log(csv); - Assert.That(csv, Is.EqualTo( - "1,2,\"3\"\"\",4,\"5\"\"five,six\"\"\",\"7,7.1\",\"\"\"7,7.1\"\"\",8" - + Environment.NewLine - )); - } - - [Test] - public void Can_generate_csv_with_invalid_chars_pipe_delimiter() - { - CsvConfig.ItemDelimiterString = "|"; - var fields = new[] { "1", "2", "3\"", "4", "5\"five,six\"", "7,7.1", "\"7,7.1\"", "8" }; - var csv = CsvSerializer.SerializeToCsv(fields); - Log(csv); - Assert.That(csv, Is.EqualTo( - "1,2,3\",4,|5\"five,six\"|,|7,7.1|,|\"7,7.1\"|,8" - + Environment.NewLine - )); - } - - [Test] - public void Can_generate_csv_with_invalid_chars_pipe_separator() - { - CsvConfig.ItemSeperatorString = "|"; - var fields = new[] { "1", "2", "3\"", "4", "5\"five,six\"", "7,7.1", "\"7,7.1\"", "8" }; - var csv = CsvSerializer.SerializeToCsv(fields); - Log(csv); - Assert.That(csv, Is.EqualTo( - "1|2|\"3\"\"\"|4|\"5\"\"five,six\"\"\"|7,7.1|\"\"\"7,7.1\"\"\"|8" - + Environment.NewLine - )); - } - - [Test] - public void Can_convert_to_csv_field() - { - Assert.That("1".ToCsvField(), Is.EqualTo("1")); - Assert.That("3\"".ToCsvField(), Is.EqualTo("\"3\"\"\"")); - Assert.That("5\"five,six\"".ToCsvField(), Is.EqualTo("\"5\"\"five,six\"\"\"")); - Assert.That("7,7.1".ToCsvField(), Is.EqualTo("\"7,7.1\"")); - Assert.That("\"7,7.1\"".ToCsvField(), Is.EqualTo("\"\"\"7,7.1\"\"\"")); - } - - [Test] - public void Can_convert_to_csv_field_pipe_separator() - { - CsvConfig.ItemSeperatorString = "|"; - Assert.That("1".ToCsvField(), Is.EqualTo("1")); - Assert.That("3\"".ToCsvField(), Is.EqualTo("\"3\"\"\"")); - Assert.That("5\"five,six\"".ToCsvField(), Is.EqualTo("\"5\"\"five,six\"\"\"")); - Assert.That("7,7.1".ToCsvField(), Is.EqualTo("7,7.1")); - Assert.That("\"7,7.1\"".ToCsvField(), Is.EqualTo("\"\"\"7,7.1\"\"\"")); - } - - [Test] - public void Can_convert_to_csv_field_pipe_delimiter() - { - CsvConfig.ItemDelimiterString = "|"; - Assert.That("1".ToCsvField(), Is.EqualTo("1")); - Assert.That("3\"".ToCsvField(), Is.EqualTo("3\"")); - Assert.That("5\"five,six\"".ToCsvField(), Is.EqualTo("|5\"five,six\"|")); - Assert.That("7,7.1".ToCsvField(), Is.EqualTo("|7,7.1|")); - Assert.That("\"7,7.1\"".ToCsvField(), Is.EqualTo("|\"7,7.1\"|")); - } - - [Test] - public void Can_convert_from_csv_field() - { - Assert.That("1".FromCsvField(), Is.EqualTo("1")); - Assert.That("\"3\"\"\"".FromCsvField(), Is.EqualTo("3\"")); - Assert.That("\"5\"\"five,six\"\"\"".FromCsvField(), Is.EqualTo("5\"five,six\"")); - Assert.That("\"7,7.1\"".FromCsvField(), Is.EqualTo("7,7.1")); - Assert.That("\"\"\"7,7.1\"\"\"".FromCsvField(), Is.EqualTo("\"7,7.1\"")); - } - - [Test] - public void Can_convert_from_csv_field_pipe_separator() - { - CsvConfig.ItemSeperatorString = "|"; - Assert.That("1".FromCsvField(), Is.EqualTo("1")); - Assert.That("\"3\"\"\"".FromCsvField(), Is.EqualTo("3\"")); - Assert.That("\"5\"\"five,six\"\"\"".FromCsvField(), Is.EqualTo("5\"five,six\"")); - Assert.That("\"7,7.1\"".FromCsvField(), Is.EqualTo("7,7.1")); - Assert.That("7,7.1".FromCsvField(), Is.EqualTo("7,7.1")); - Assert.That("\"\"\"7,7.1\"\"\"".FromCsvField(), Is.EqualTo("\"7,7.1\"")); - } - - [Test] - public void Can_convert_from_csv_field_pipe_delimiter() - { - CsvConfig.ItemDelimiterString = "|"; - Assert.That("1".FromCsvField(), Is.EqualTo("1")); - Assert.That("3\"".FromCsvField(), Is.EqualTo("3\"")); - Assert.That("|5\"five,six\"|".FromCsvField(), Is.EqualTo("5\"five,six\"")); - Assert.That("|7,7.1|".FromCsvField(), Is.EqualTo("7,7.1")); - Assert.That("|\"7,7.1\"|".FromCsvField(), Is.EqualTo("\"7,7.1\"")); - } - - } + [Test] +#if NETCORE + [Ignore("Fix Northwind.dll")] +#endif + public void Can_create_csv_from_Customers() + { + NorthwindData.LoadData(false); + var csv = CsvSerializer.SerializeToCsv(NorthwindData.Customers); + Log(csv); + Assert.That(csv, Is.Not.Null); + } + + [Test] +#if NETCORE + [Ignore("Fix Northwind.dll")] +#endif + public void Can_create_csv_from_Customers_pipe_separator() + { + CsvConfig.ItemSeperatorString = "|"; + NorthwindData.LoadData(false); + var csv = CsvSerializer.SerializeToCsv(NorthwindData.Customers); + Log(csv); + Assert.That(csv, Is.Not.Null); + } + + [Test] +#if NETCORE + [Ignore("Fix Northwind.dll")] +#endif + public void Can_create_csv_from_Customers_pipe_delimiter() + { + CsvConfig.ItemDelimiterString = "|"; + NorthwindData.LoadData(false); + var csv = CsvSerializer.SerializeToCsv(NorthwindData.Customers); + Log(csv); + Assert.That(csv, Is.Not.Null); + } + + [Test] +#if NETCORE + [Ignore("Fix Northwind.dll")] +#endif + public void Can_create_csv_from_Customers_pipe_row_separator() + { + CsvConfig.RowSeparatorString = "|"; + NorthwindData.LoadData(false); + var csv = CsvSerializer.SerializeToCsv(NorthwindData.Customers); + Log(csv); + Assert.That(csv, Is.Not.Null); + } + + [Test] +#if NETCORE + [Ignore("Fix Northwind.dll")] +#endif + public void Can_create_csv_from_Categories() + { + NorthwindData.LoadData(false); + var category = NorthwindFactory.Category(1, "between \"quotes\" here", "with, comma", null); + var categories = new[] { category, category }; + var csv = CsvSerializer.SerializeToCsv(categories); + Log(csv); + Assert.That(csv, Is.EqualTo( + "Id,CategoryName,Description,Picture\r\n" + + "1,\"between \"\"quotes\"\" here\",\"with, comma\",\r\n" + + "1,\"between \"\"quotes\"\" here\",\"with, comma\",\r\n" + )); + } + + [Test] +#if NETCORE + [Ignore("Fix Northwind.dll")] +#endif + public void Can_create_csv_from_Categories_pipe_separator() + { + CsvConfig.ItemSeperatorString = "|"; + NorthwindData.LoadData(false); + var category = NorthwindFactory.Category(1, "between \"quotes\" here", "with, comma", null); + var categories = new[] { category, category }; + var csv = CsvSerializer.SerializeToCsv(categories); + Log(csv); + Assert.That(csv, Is.EqualTo( + "Id|CategoryName|Description|Picture\r\n" + + "1|\"between \"\"quotes\"\" here\"|with, comma|\r\n" + + "1|\"between \"\"quotes\"\" here\"|with, comma|\r\n" + )); + } + + [Test] +#if NETCORE + [Ignore("Fix Northwind.dll")] +#endif + public void Can_create_csv_from_Categories_pipe_delimiter() + { + CsvConfig.ItemDelimiterString = "|"; + NorthwindData.LoadData(false); + var category = NorthwindFactory.Category(1, "between \"quotes\" here", "with, comma", null); + var categories = new[] { category, category }; + var csv = CsvSerializer.SerializeToCsv(categories); + Log(csv); + Assert.That(csv, Is.EqualTo( + "Id,CategoryName,Description,Picture\r\n" + + "1,between \"quotes\" here,|with, comma|,\r\n" + + "1,between \"quotes\" here,|with, comma|,\r\n" + )); + } + + [Test] +#if NETCORE + [Ignore("Fix Northwind.dll")] +#endif + public void Can_create_csv_from_Categories_long_delimiter() + { + CsvConfig.ItemDelimiterString = "~^~"; + NorthwindData.LoadData(false); + var category = NorthwindFactory.Category(1, "between \"quotes\" here", "with, comma", null); + var categories = new[] { category, category }; + var csv = CsvSerializer.SerializeToCsv(categories); + Log(csv); + Assert.That(csv, Is.EqualTo( + "Id,CategoryName,Description,Picture\r\n" + + "1,between \"quotes\" here,~^~with, comma~^~,\r\n" + + "1,between \"quotes\" here,~^~with, comma~^~,\r\n" + )); + } + + [Test] + public void Can_generate_csv_with_invalid_chars() + { + var fields = new[] { "1", "2", "3\"", "4", "5\"five,six\"", "7,7.1", "\"7,7.1\"", "8" }; + var csv = CsvSerializer.SerializeToCsv(fields); + Log(csv); + Assert.That(csv, Is.EqualTo( + "1,2,\"3\"\"\",4,\"5\"\"five,six\"\"\",\"7,7.1\",\"\"\"7,7.1\"\"\",8\r\n" + )); + } + + [Test] + public void Can_generate_csv_with_invalid_chars_pipe_delimiter() + { + CsvConfig.ItemDelimiterString = "|"; + var fields = new[] { "1", "2", "3\"", "4", "5\"five,six\"", "7,7.1", "\"7,7.1\"", "8" }; + var csv = CsvSerializer.SerializeToCsv(fields); + Log(csv); + Assert.That(csv, Is.EqualTo( + "1,2,3\",4,|5\"five,six\"|,|7,7.1|,|\"7,7.1\"|,8\r\n" + )); + } + + [Test] + public void Can_generate_csv_with_invalid_chars_pipe_separator() + { + CsvConfig.ItemSeperatorString = "|"; + var fields = new[] { "1", "2", "3\"", "4", "5\"five,six\"", "7,7.1", "\"7,7.1\"", "8" }; + var csv = CsvSerializer.SerializeToCsv(fields); + Log(csv); + Assert.That(csv, Is.EqualTo( + "1|2|\"3\"\"\"|4|\"5\"\"five,six\"\"\"|7,7.1|\"\"\"7,7.1\"\"\"|8\r\n" + )); + } + + [Test] + public void Can_convert_to_csv_field() + { + Assert.That("1".ToCsvField(), Is.EqualTo("1")); + Assert.That("3\"".ToCsvField(), Is.EqualTo("\"3\"\"\"")); + Assert.That("5\"five,six\"".ToCsvField(), Is.EqualTo("\"5\"\"five,six\"\"\"")); + Assert.That("7,7.1".ToCsvField(), Is.EqualTo("\"7,7.1\"")); + Assert.That("\"7,7.1\"".ToCsvField(), Is.EqualTo("\"\"\"7,7.1\"\"\"")); + } + + [Test] + public void Can_convert_to_csv_field_pipe_separator() + { + CsvConfig.ItemSeperatorString = "|"; + Assert.That("1".ToCsvField(), Is.EqualTo("1")); + Assert.That("3\"".ToCsvField(), Is.EqualTo("\"3\"\"\"")); + Assert.That("5\"five,six\"".ToCsvField(), Is.EqualTo("\"5\"\"five,six\"\"\"")); + Assert.That("7,7.1".ToCsvField(), Is.EqualTo("7,7.1")); + Assert.That("\"7,7.1\"".ToCsvField(), Is.EqualTo("\"\"\"7,7.1\"\"\"")); + } + + [Test] + public void Can_convert_to_csv_field_pipe_delimiter() + { + CsvConfig.ItemDelimiterString = "|"; + Assert.That("1".ToCsvField(), Is.EqualTo("1")); + Assert.That("3\"".ToCsvField(), Is.EqualTo("3\"")); + Assert.That("5\"five,six\"".ToCsvField(), Is.EqualTo("|5\"five,six\"|")); + Assert.That("7,7.1".ToCsvField(), Is.EqualTo("|7,7.1|")); + Assert.That("\"7,7.1\"".ToCsvField(), Is.EqualTo("|\"7,7.1\"|")); + } + + [Test] + public void Can_convert_from_csv_field() + { + Assert.That("1".FromCsvField(), Is.EqualTo("1")); + Assert.That("\"3\"\"\"".FromCsvField(), Is.EqualTo("3\"")); + Assert.That("\"5\"\"five,six\"\"\"".FromCsvField(), Is.EqualTo("5\"five,six\"")); + Assert.That("\"7,7.1\"".FromCsvField(), Is.EqualTo("7,7.1")); + Assert.That("\"\"\"7,7.1\"\"\"".FromCsvField(), Is.EqualTo("\"7,7.1\"")); + } + + [Test] + public void Can_convert_from_csv_field_pipe_separator() + { + CsvConfig.ItemSeperatorString = "|"; + Assert.That("1".FromCsvField(), Is.EqualTo("1")); + Assert.That("\"3\"\"\"".FromCsvField(), Is.EqualTo("3\"")); + Assert.That("\"5\"\"five,six\"\"\"".FromCsvField(), Is.EqualTo("5\"five,six\"")); + Assert.That("\"7,7.1\"".FromCsvField(), Is.EqualTo("7,7.1")); + Assert.That("7,7.1".FromCsvField(), Is.EqualTo("7,7.1")); + Assert.That("\"\"\"7,7.1\"\"\"".FromCsvField(), Is.EqualTo("\"7,7.1\"")); + } + + [Test] + public void Can_convert_from_csv_field_pipe_delimiter() + { + CsvConfig.ItemDelimiterString = "|"; + Assert.That("1".FromCsvField(), Is.EqualTo("1")); + Assert.That("3\"".FromCsvField(), Is.EqualTo("3\"")); + Assert.That("|5\"five,six\"|".FromCsvField(), Is.EqualTo("5\"five,six\"")); + Assert.That("|7,7.1|".FromCsvField(), Is.EqualTo("7,7.1")); + Assert.That("|\"7,7.1\"|".FromCsvField(), Is.EqualTo("\"7,7.1\"")); + } + + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CsvTests/CustomHeaderTests.cs b/tests/ServiceStack.Text.Tests/CsvTests/CustomHeaderTests.cs index b4e52f4ca..79e57e2f9 100644 --- a/tests/ServiceStack.Text.Tests/CsvTests/CustomHeaderTests.cs +++ b/tests/ServiceStack.Text.Tests/CsvTests/CustomHeaderTests.cs @@ -5,128 +5,142 @@ namespace ServiceStack.Text.Tests.CsvTests { - [TestFixture] - public class CustomHeaderTests - { - [TestFixtureTearDown] - public void TestFixtureTearDown() - { - CsvConfig.Reset(); - } - - [Test] - public void Can_serialize_custom_headers_map() - { - CsvConfig.CustomHeadersMap = new Dictionary { - {"Column1Data", "Column 1"}, - {"Column2Data", "Column 2"}, - {"Column3Data", "Column,3"}, - {"Column4Data", "Column\n4"}, - {"Column5Data", "Column 5"}, - }; - var data = new List { - new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, - new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, - }; - - var csv = CsvSerializer.SerializeToCsv(data); - - Console.WriteLine(csv); - - Assert.That(csv, Is.EqualTo( - "Column 1,Column 2,\"Column,3\",\"Column\n4\",Column 5" - + Environment.NewLine - + "I,Like,To,Read,Novels" - + Environment.NewLine - + "I am,Very,Cool,And,Awesome" - + Environment.NewLine - )); - } - - [Test] - public void Can_serialize_custom_anonymous_type_headers() - { - CsvConfig.CustomHeaders = new { - Column1Data = "Column 1", - Column2Data = "Column 2", - Column3Data = "Column,3", - Column4Data = "Column\n4", - Column5Data = "Column 5", - }; - var data = new List { - new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, - new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, - }; - - var csv = CsvSerializer.SerializeToCsv(data); - - Console.WriteLine(csv); - - Assert.That(csv, Is.EqualTo( - "Column 1,Column 2,\"Column,3\",\"Column\n4\",Column 5" - + Environment.NewLine - + "I,Like,To,Read,Novels" - + Environment.NewLine - + "I am,Very,Cool,And,Awesome" - + Environment.NewLine - )); - } - - [Test] - public void Can_serialize_partial_custom_headers_map() - { - CsvConfig.CustomHeadersMap = new Dictionary { - {"Column1Data", "Column 1"}, - {"Column3Data", "Column,3"}, - {"Column5Data", "Column 5"}, - }; - var data = new List { - new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, - new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, - }; - - var csv = CsvSerializer.SerializeToCsv(data); - - Console.WriteLine(csv); - - Assert.That(csv, Is.EqualTo( - "Column 1,\"Column,3\",Column 5" - + Environment.NewLine - + "I,To,Novels" - + Environment.NewLine - + "I am,Cool,Awesome" - + Environment.NewLine - )); - } - - [Test] - public void Can_serialize_without_headers() - { - CsvConfig.OmitHeaders = true; - - CsvConfig.CustomHeadersMap = new Dictionary { - {"Column1Data", "Column 1"}, - {"Column2Data", "Column 2"}, - {"Column3Data", "Column,3"}, - {"Column4Data", "Column\n4"}, - {"Column5Data", "Column 5"}, - }; - var data = new List { - new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, - new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, - }; - - var csv = CsvSerializer.SerializeToCsv(data); - - Console.WriteLine(csv); - - Assert.That(csv, Is.EqualTo( - "I,Like,To,Read,Novels" - + Environment.NewLine - + "I am,Very,Cool,And,Awesome" - + Environment.NewLine - )); - } - - } + [TestFixture] + public class CustomHeaderTests + { + [OneTimeTearDown] + public void TestFixtureTearDown() + { + CsvConfig.Reset(); + } + + [Test] + public void Can_serialize_custom_headers_map() + { + CsvConfig.CustomHeadersMap = new Dictionary { + {"Column1Data", "Column 1"}, + {"Column2Data", "Column 2"}, + {"Column3Data", "Column,3"}, + {"Column4Data", "Column\n4"}, + {"Column5Data", "Column 5"}, + }; + var data = new List { + new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, + new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, + }; + + var csv = CsvSerializer.SerializeToCsv(data); + + Console.WriteLine(csv); + + Assert.That(csv, Is.EqualTo( + "Column 1,Column 2,\"Column,3\",\"Column\n4\",Column 5\r\n" + + "I,Like,To,Read,Novels\r\n" + + "I am,Very,Cool,And,Awesome\r\n" + )); + } + + [Test] + public void Can_serialize_custom_anonymous_type_headers() + { + CsvConfig.CustomHeaders = new + { + Column1Data = "Column 1", + Column2Data = "Column 2", + Column3Data = "Column,3", + Column4Data = "Column\n4", + Column5Data = "Column 5", + }; + var data = new List { + new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, + new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, + }; + + var csv = CsvSerializer.SerializeToCsv(data); + + Console.WriteLine(csv); + + Assert.That(csv, Is.EqualTo( + "Column 1,Column 2,\"Column,3\",\"Column\n4\",Column 5\r\n" + + "I,Like,To,Read,Novels\r\n" + + "I am,Very,Cool,And,Awesome\r\n" + )); + } + + [Test] + public void Can_serialize_partial_custom_headers_map() + { + CsvConfig.CustomHeadersMap = new Dictionary { + {"Column1Data", "Column 1"}, + {"Column3Data", "Column,3"}, + {"Column5Data", "Column 5"}, + }; + var data = new List { + new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, + new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, + }; + + var csv = CsvSerializer.SerializeToCsv(data); + + Console.WriteLine(csv); + + Assert.That(csv, Is.EqualTo( + "Column 1,\"Column,3\",Column 5\r\n" + + "I,To,Novels\r\n" + + "I am,Cool,Awesome\r\n" + )); + } + + [Test] + public void Can_deserialize_partial_custom_headers_map() + { + CsvConfig.CustomHeadersMap = new Dictionary { + {"Column1Data", "Column 1"}, + {"Column3Data", "Column,3"}, + {"Column5Data", "Column 5"}, + }; + + var csv = "Column 1,\"Column,3\",Column 5\r\n" + + "I,To,Novels\r\n" + + "I am,Cool,Awesome\r\n"; + + var data = CsvSerializer.DeserializeFromString>(csv); + + Assert.That(data[0].Column1Data, Is.EqualTo("I")); + Assert.That(data[0].Column3Data, Is.EqualTo("To")); + Assert.That(data[0].Column5Data, Is.EqualTo("Novels")); + + Assert.That(data[1].Column1Data, Is.EqualTo("I am")); + Assert.That(data[1].Column3Data, Is.EqualTo("Cool")); + Assert.That(data[1].Column5Data, Is.EqualTo("Awesome")); + } + + [Test] + public void Can_serialize_without_headers() + { + CsvConfig.OmitHeaders = true; + + CsvConfig.CustomHeadersMap = new Dictionary { + {"Column1Data", "Column 1"}, + {"Column2Data", "Column 2"}, + {"Column3Data", "Column,3"}, + {"Column4Data", "Column\n4"}, + {"Column5Data", "Column 5"}, + }; + var data = new List { + new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, + new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, + }; + + var csv = CsvSerializer.SerializeToCsv(data); + + Console.WriteLine(csv); + + Assert.That(csv, Is.EqualTo( + "I,Like,To,Read,Novels\r\n" + + "I am,Very,Cool,And,Awesome\r\n" + )); + } + + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CsvTests/DictionaryTests.cs b/tests/ServiceStack.Text.Tests/CsvTests/DictionaryTests.cs index cf30c1842..9c885ef76 100644 --- a/tests/ServiceStack.Text.Tests/CsvTests/DictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/CsvTests/DictionaryTests.cs @@ -5,74 +5,60 @@ namespace ServiceStack.Text.Tests.CsvTests { - [TestFixture] - public class DictionaryTests - { - [Test] - public void Serializes_dictionary_mismatched_keys_deserializes_tabular_csv() - { - var data = new List> { + [TestFixture] + public class DictionaryTests + { + [Test] + public void Serializes_dictionary_mismatched_keys_deserializes_tabular_csv() + { + var data = new List> { new Dictionary { {"Column2Data", "Like"}, {"Column3Data", "To"}, {"Column4Data", "Read"}, {"Column5Data", "Novels"}}, new Dictionary { { "Column1Data", "I am" }, {"Column3Data", "Cool"}, {"Column4Data", "And"}, {"Column5Data", "Awesome"}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", " Like "}, {"Column4Data", null}, {"Column5Data", null}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Don't"}, {"Column3Data", "Know,"}, {"Column5Data", "You?"}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Saw"}, {"Column3Data", "The"}, {"Column4Data", "Movie"}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Went"}, {"Column3Data", "To"}, {"Column4Data", "Space\nCamp"}, {"Column5Data", "Last\r\nYear"}} - }; - - var csv = CsvSerializer.SerializeToCsv(data); - Console.WriteLine(csv); - - Assert.That(csv, Is.EqualTo( - "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data" - + Environment.NewLine - + ",Like,To,Read,Novels" - + Environment.NewLine - + "I am,,Cool,And,Awesome" - + Environment.NewLine - + "I, Like ,,," - + Environment.NewLine - + "I,Don't,\"Know,\",,You?" - + Environment.NewLine - + "I,Saw,The,Movie," - + Environment.NewLine - + "I,Went,To,\"Space\nCamp\",\"Last\r\nYear\"" - + Environment.NewLine - )); - } - - [Test] - public void Serializes_dictionary_data() - { - var data = new List> { + }; + + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + + Assert.That(csv, Is.EqualTo( + "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data\r\n" + + ",Like,To,Read,Novels\r\n" + + "I am,,Cool,And,Awesome\r\n" + + "I, Like ,,,\r\n" + + "I,Don't,\"Know,\",,You?\r\n" + + "I,Saw,The,Movie,\r\n" + + "I,Went,To,\"Space\nCamp\",\"Last\r\nYear\"\r\n" + )); + } + + [Test] + public void Serializes_dictionary_data() + { + var data = new List> { new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Like"}, {"Column3Data", "To"}, {"Column4Data", "Read"}, {"Column5Data", "Novels"}}, new Dictionary { { "Column1Data", "I am" }, {"Column2Data", "Very"}, {"Column3Data", "Cool"}, {"Column4Data", "And"}, {"Column5Data", "Awesome"}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", " Like "}, {"Column3Data", "Reading"}, {"Column4Data", null}, {"Column5Data", null}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Don't"}, {"Column3Data", "Know,"}, {"Column4Data", "Do"}, {"Column5Data", "You?"}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Saw"}, {"Column3Data", "The"}, {"Column4Data", "Movie"}, {"Column5Data", "\"Jaws\""}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Went"}, {"Column3Data", "To"}, {"Column4Data", "Space\nCamp"}, {"Column5Data", "Last\r\nYear"}} - }; - - var csv = CsvSerializer.SerializeToCsv(data); - Console.WriteLine(csv); - - Assert.That(csv, Is.EqualTo( - "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data" - + Environment.NewLine - + "I,Like,To,Read,Novels" - + Environment.NewLine - + "I am,Very,Cool,And,Awesome" - + Environment.NewLine - + "I, Like ,Reading,," - + Environment.NewLine - + "I,Don't,\"Know,\",Do,You?" - + Environment.NewLine - + "I,Saw,The,Movie,\"\"\"Jaws\"\"\"" - + Environment.NewLine - + "I,Went,To,\"Space\nCamp\",\"Last\r\nYear\"" - + Environment.NewLine - )); - } + }; + + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + + Assert.That(csv, Is.EqualTo( + "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data\r\n" + + "I,Like,To,Read,Novels\r\n" + + "I am,Very,Cool,And,Awesome\r\n" + + "I, Like ,Reading,,\r\n" + + "I,Don't,\"Know,\",Do,You?\r\n" + + "I,Saw,The,Movie,\"\"\"Jaws\"\"\"\r\n" + + "I,Went,To,\"Space\nCamp\",\"Last\r\nYear\"\r\n" + )); + } [Test] public void Serializes_dictionary_object_data() @@ -133,95 +119,74 @@ public void Serializes_dictionary_object_data() Console.WriteLine(csv); Assert.That(csv, Is.EqualTo( - "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data" - + Environment.NewLine - + "I,Like,To,Read,123" - + Environment.NewLine - + "I am,Very,Cool,And,4" - + Environment.NewLine - + "I, Like ,2,," - + Environment.NewLine - + "I,Don't,\"Know,\",Do,You?" - + Environment.NewLine - + "I,Saw,The,Movie,\"\"\"Jaws\"\"\"" - + Environment.NewLine - + "I,Went,To,\"Space\nCamp\",\"Last\r\nYear\"" - + Environment.NewLine + "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data\r\n" + + "I,Like,To,Read,123\r\n" + + "I am,Very,Cool,And,4\r\n" + + "I, Like ,2,,\r\n" + + "I,Don't,\"Know,\",Do,You?\r\n" + + "I,Saw,The,Movie,\"\"\"Jaws\"\"\"\r\n" + + "I,Went,To,\"Space\nCamp\",\"Last\r\nYear\"\r\n" )); } - [TearDown] + [TearDown] public void TearDown() { CsvConfig.Reset(); } - [Test] - public void Serializes_dictionary_data_long_delimiter() - { - CsvConfig.ItemDelimiterString = "^~^"; - var data = new List> { + [Test] + public void Serializes_dictionary_data_long_delimiter() + { + CsvConfig.ItemDelimiterString = "^~^"; + var data = new List> { new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Like"}, {"Column3Data", "To"}, {"Column4Data", "Read"}, {"Column5Data", "Novels"}}, new Dictionary { { "Column1Data", "I am" }, {"Column2Data", "Very"}, {"Column3Data", "Cool"}, {"Column4Data", "And"}, {"Column5Data", "Awesome"}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", " Like "}, {"Column3Data", "Reading"}, {"Column4Data", null}, {"Column5Data", null}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Don't"}, {"Column3Data", "Know,"}, {"Column4Data", "Do"}, {"Column5Data", "You?"}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Saw"}, {"Column3Data", "The"}, {"Column4Data", "Movie"}, {"Column5Data", "\"Jaws\""}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Went"}, {"Column3Data", "To"}, {"Column4Data", "Space\nCamp"}, {"Column5Data", "Last\r\nYear"}} - }; - - var csv = CsvSerializer.SerializeToCsv(data); - Console.WriteLine(csv); - - Assert.That(csv, Is.EqualTo( - "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data" - + Environment.NewLine - + "I,Like,To,Read,Novels" - + Environment.NewLine - + "I am,Very,Cool,And,Awesome" - + Environment.NewLine - + "I, Like ,Reading,," - + Environment.NewLine - + "I,Don't,^~^Know,^~^,Do,You?" - + Environment.NewLine - + "I,Saw,The,Movie,\"Jaws\"" - + Environment.NewLine - + "I,Went,To,^~^Space\nCamp^~^,^~^Last\r\nYear^~^" - + Environment.NewLine - )); - } - - [Test] - public void Serializes_dictionary_data_pipe_separator() - { - CsvConfig.ItemSeperatorString = "|"; - var data = new List> { + }; + + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + + Assert.That(csv, Is.EqualTo( + "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data\r\n" + + "I,Like,To,Read,Novels\r\n" + + "I am,Very,Cool,And,Awesome\r\n" + + "I, Like ,Reading,,\r\n" + + "I,Don't,^~^Know,^~^,Do,You?\r\n" + + "I,Saw,The,Movie,\"Jaws\"\r\n" + + "I,Went,To,^~^Space\nCamp^~^,^~^Last\r\nYear^~^\r\n" + )); + } + + [Test] + public void Serializes_dictionary_data_pipe_separator() + { + CsvConfig.ItemSeperatorString = "|"; + var data = new List> { new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Like"}, {"Column3Data", "To"}, {"Column4Data", "Read"}, {"Column5Data", "Novels"}}, new Dictionary { { "Column1Data", "I am" }, {"Column2Data", "Very"}, {"Column3Data", "Cool"}, {"Column4Data", "And"}, {"Column5Data", "Awesome"}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", " Like "}, {"Column3Data", "Reading"}, {"Column4Data", null}, {"Column5Data", null}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Don't"}, {"Column3Data", "Know,"}, {"Column4Data", "Do"}, {"Column5Data", "You?"}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Saw"}, {"Column3Data", "The"}, {"Column4Data", "Movie"}, {"Column5Data", "\"Jaws\""}}, new Dictionary { { "Column1Data", "I" }, {"Column2Data", "Went"}, {"Column3Data", "To"}, {"Column4Data", "Space\nCamp"}, {"Column5Data", "Last\r\nYear"}} - }; - - var csv = CsvSerializer.SerializeToCsv(data); - Console.WriteLine(csv); - - Assert.That(csv, Is.EqualTo( - "Column1Data|Column2Data|Column3Data|Column4Data|Column5Data" - + Environment.NewLine - + "I|Like|To|Read|Novels" - + Environment.NewLine - + "I am|Very|Cool|And|Awesome" - + Environment.NewLine - + "I| Like |Reading||" - + Environment.NewLine - + "I|Don't|Know,|Do|You?" - + Environment.NewLine - + "I|Saw|The|Movie|\"\"\"Jaws\"\"\"" - + Environment.NewLine - + "I|Went|To|\"Space\nCamp\"|\"Last\r\nYear\"" - + Environment.NewLine - )); - } - } + }; + + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + + Assert.That(csv, Is.EqualTo( + "Column1Data|Column2Data|Column3Data|Column4Data|Column5Data\r\n" + + "I|Like|To|Read|Novels\r\n" + + "I am|Very|Cool|And|Awesome\r\n" + + "I| Like |Reading||\r\n" + + "I|Don't|Know,|Do|You?\r\n" + + "I|Saw|The|Movie|\"\"\"Jaws\"\"\"\r\n" + + "I|Went|To|\"Space\nCamp\"|\"Last\r\nYear\"\r\n" + )); + } + } } diff --git a/tests/ServiceStack.Text.Tests/CsvTests/NewLineTests.cs b/tests/ServiceStack.Text.Tests/CsvTests/NewLineTests.cs index c6606eca0..5471c8d89 100644 --- a/tests/ServiceStack.Text.Tests/CsvTests/NewLineTests.cs +++ b/tests/ServiceStack.Text.Tests/CsvTests/NewLineTests.cs @@ -5,113 +5,92 @@ namespace ServiceStack.Text.Tests.CsvTests { - [TestFixture] - public class NewLineTests - { - [Test] - public void Serializes_adhoc_data() - { - var data = new List { - new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, - new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, - new TableItem { Column1Data = "I", Column2Data = " Like ", Column3Data = "Reading", Column4Data = null, Column5Data = null }, - new TableItem { Column1Data = "I", Column2Data = "Don't", Column3Data = "Know,", Column4Data = "Do", Column5Data = "You?" }, - new TableItem { Column1Data = "I", Column2Data = "Saw", Column3Data = "The", Column4Data = "Movie", Column5Data = "\"Jaws\"" }, - new TableItem { Column1Data = "I", Column2Data = "Went", Column3Data = "To", Column4Data = "Space\nCamp", Column5Data = "Last\r\nYear" } - }; + [TestFixture] + public class NewLineTests + { + [Test] + public void Serializes_adhoc_data() + { + var data = new List { + new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, + new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, + new TableItem { Column1Data = "I", Column2Data = " Like ", Column3Data = "Reading", Column4Data = null, Column5Data = null }, + new TableItem { Column1Data = "I", Column2Data = "Don't", Column3Data = "Know,", Column4Data = "Do", Column5Data = "You?" }, + new TableItem { Column1Data = "I", Column2Data = "Saw", Column3Data = "The", Column4Data = "Movie", Column5Data = "\"Jaws\"" }, + new TableItem { Column1Data = "I", Column2Data = "Went", Column3Data = "To", Column4Data = "Space\nCamp", Column5Data = "Last\r\nYear" } + }; - var csv = CsvSerializer.SerializeToCsv(data); - Console.WriteLine(csv); + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); - Assert.That(csv, Is.EqualTo( - "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data" - + Environment.NewLine - + "I,Like,To,Read,Novels" - + Environment.NewLine - + "I am,Very,Cool,And,Awesome" - + Environment.NewLine - + "I, Like ,Reading,," - + Environment.NewLine - + "I,Don't,\"Know,\",Do,You?" - + Environment.NewLine - + "I,Saw,The,Movie,\"\"\"Jaws\"\"\"" - + Environment.NewLine - + "I,Went,To,\"Space\nCamp\",\"Last\r\nYear\"" - + Environment.NewLine - )); - } + Assert.That(csv, Is.EqualTo( + "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data\r\n" + + "I,Like,To,Read,Novels\r\n" + + "I am,Very,Cool,And,Awesome\r\n" + + "I, Like ,Reading,,\r\n" + + "I,Don't,\"Know,\",Do,You?\r\n" + + "I,Saw,The,Movie,\"\"\"Jaws\"\"\"\r\n" + + "I,Went,To,\"Space\nCamp\",\"Last\r\nYear\"\r\n" + )); + } [TearDown] public void TearDown() { CsvConfig.Reset(); } - [Test] - public void Serializes_adhoc_data_pipe_separator() - { - CsvConfig.ItemSeperatorString = "|"; - var data = new List { - new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, - new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, - new TableItem { Column1Data = "I", Column2Data = " Like ", Column3Data = "Reading", Column4Data = null, Column5Data = null }, - new TableItem { Column1Data = "I", Column2Data = "Don't", Column3Data = "Know,", Column4Data = "Do", Column5Data = "You?" }, - new TableItem { Column1Data = "I", Column2Data = "Saw", Column3Data = "The", Column4Data = "Movie", Column5Data = "\"Jaws\"" }, - new TableItem { Column1Data = "I", Column2Data = "Went", Column3Data = "To", Column4Data = "Space\nCamp", Column5Data = "Last\r\nYear" } - }; + [Test] + public void Serializes_adhoc_data_pipe_separator() + { + CsvConfig.ItemSeperatorString = "|"; + var data = new List { + new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, + new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, + new TableItem { Column1Data = "I", Column2Data = " Like ", Column3Data = "Reading", Column4Data = null, Column5Data = null }, + new TableItem { Column1Data = "I", Column2Data = "Don't", Column3Data = "Know,", Column4Data = "Do", Column5Data = "You?" }, + new TableItem { Column1Data = "I", Column2Data = "Saw", Column3Data = "The", Column4Data = "Movie", Column5Data = "\"Jaws\"" }, + new TableItem { Column1Data = "I", Column2Data = "Went", Column3Data = "To", Column4Data = "Space\nCamp", Column5Data = "Last\r\nYear" } + }; - var csv = CsvSerializer.SerializeToCsv(data); - Console.WriteLine(csv); + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); - Assert.That(csv, Is.EqualTo( - "Column1Data|Column2Data|Column3Data|Column4Data|Column5Data" - + Environment.NewLine - + "I|Like|To|Read|Novels" - + Environment.NewLine - + "I am|Very|Cool|And|Awesome" - + Environment.NewLine - + "I| Like |Reading||" - + Environment.NewLine - + "I|Don't|Know,|Do|You?" - + Environment.NewLine - + "I|Saw|The|Movie|\"\"\"Jaws\"\"\"" - + Environment.NewLine - + "I|Went|To|\"Space\nCamp\"|\"Last\r\nYear\"" - + Environment.NewLine - )); - } + Assert.That(csv, Is.EqualTo( + "Column1Data|Column2Data|Column3Data|Column4Data|Column5Data\r\n" + + "I|Like|To|Read|Novels\r\n" + + "I am|Very|Cool|And|Awesome\r\n" + + "I| Like |Reading||\r\n" + + "I|Don't|Know,|Do|You?\r\n" + + "I|Saw|The|Movie|\"\"\"Jaws\"\"\"\r\n" + + "I|Went|To|\"Space\nCamp\"|\"Last\r\nYear\"\r\n" + )); + } - [Test] - public void Serializes_adhoc_data_pipe_delimiter() - { - CsvConfig.ItemDelimiterString = "|"; - var data = new List { - new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, - new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, - new TableItem { Column1Data = "I", Column2Data = " Like ", Column3Data = "Reading", Column4Data = null, Column5Data = null }, - new TableItem { Column1Data = "I", Column2Data = "Don't", Column3Data = "Know,", Column4Data = "Do", Column5Data = "You?" }, - new TableItem { Column1Data = "I", Column2Data = "Saw", Column3Data = "The", Column4Data = "Movie", Column5Data = "\"Jaws\"" }, - new TableItem { Column1Data = "I", Column2Data = "Went", Column3Data = "To", Column4Data = "Space\nCamp", Column5Data = "Last\r\nYear" } - }; + [Test] + public void Serializes_adhoc_data_pipe_delimiter() + { + CsvConfig.ItemDelimiterString = "|"; + var data = new List { + new TableItem { Column1Data = "I", Column2Data = "Like", Column3Data = "To", Column4Data = "Read", Column5Data = "Novels" }, + new TableItem { Column1Data = "I am", Column2Data = "Very", Column3Data = "Cool", Column4Data = "And", Column5Data = "Awesome" }, + new TableItem { Column1Data = "I", Column2Data = " Like ", Column3Data = "Reading", Column4Data = null, Column5Data = null }, + new TableItem { Column1Data = "I", Column2Data = "Don't", Column3Data = "Know,", Column4Data = "Do", Column5Data = "You?" }, + new TableItem { Column1Data = "I", Column2Data = "Saw", Column3Data = "The", Column4Data = "Movie", Column5Data = "\"Jaws\"" }, + new TableItem { Column1Data = "I", Column2Data = "Went", Column3Data = "To", Column4Data = "Space\nCamp", Column5Data = "Last\r\nYear" } + }; - var csv = CsvSerializer.SerializeToCsv(data); - Console.WriteLine(csv); + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); - Assert.That(csv, Is.EqualTo( - "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data" - + Environment.NewLine - + "I,Like,To,Read,Novels" - + Environment.NewLine - + "I am,Very,Cool,And,Awesome" - + Environment.NewLine - + "I, Like ,Reading,," - + Environment.NewLine - + "I,Don't,|Know,|,Do,You?" - + Environment.NewLine - + "I,Saw,The,Movie,\"Jaws\"" - + Environment.NewLine - + "I,Went,To,|Space\nCamp|,|Last\r\nYear|" - + Environment.NewLine - )); - } - } + Assert.That(csv, Is.EqualTo( + "Column1Data,Column2Data,Column3Data,Column4Data,Column5Data\r\n" + + "I,Like,To,Read,Novels\r\n" + + "I am,Very,Cool,And,Awesome\r\n" + + "I, Like ,Reading,,\r\n" + + "I,Don't,|Know,|,Do,You?\r\n" + + "I,Saw,The,Movie,\"Jaws\"\r\n" + + "I,Went,To,|Space\nCamp|,|Last\r\nYear|\r\n" + )); + } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CsvTests/ObjectSerializerTests.cs b/tests/ServiceStack.Text.Tests/CsvTests/ObjectSerializerTests.cs new file mode 100644 index 000000000..b0920e8e6 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/CsvTests/ObjectSerializerTests.cs @@ -0,0 +1,243 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using ServiceStack.Text.Common; + +namespace ServiceStack.Text.Tests.CsvTests +{ + [TestFixture] + public class ObjectSerializerTests + { + [Test] + public void MidnightAndNoonTestSerialization() + { + JsConfig.Reset(); + JsConfig.SerializeFn = null; + JsConfig.Reset(); + + JsConfig.AlwaysUseUtc = true; + JsConfig.AssumeUtc = true; + // Set the format for DatTimeFormatting explicitly using DateTimeSerializer.XsdDateTimeFormat because it is ISO8601 fractional seconds + JsConfig.DateTimeFormat = DateTimeSerializer.XsdDateTimeFormat; + + var midnight = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var noon = midnight.AddHours(12); + var dotnetValues = new + { + Midnight = midnight.ToString("o"), + Noon = noon.ToString("o") + }; + var data = new object[] { + new POCO { DateTime = midnight }, + new POCO { DateTime = noon } + }; + var csv = CsvSerializer.SerializeToCsv(data); + // Reset back to defaults + JsConfig.Reset(); + JsConfig.SerializeFn = null; + JsConfig.Reset(); + + Console.WriteLine(csv); + + const string endLineChars = "\r\n"; + Assert.AreEqual($"DateTime{endLineChars}" + + $"{dotnetValues.Midnight}{endLineChars}" + + $"{dotnetValues.Noon}{endLineChars}", csv); + + // Now don't use custom DateTimeFormat + JsConfig.AlwaysUseUtc = true; + JsConfig.AssumeUtc = true; + csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + Assert.AreEqual($"DateTime{endLineChars}" + + $"2018-01-01{endLineChars}" + + $"2018-01-01T12:00:00Z{endLineChars}", csv); + + JsConfig.Reset(); + JsConfig.SerializeFn = null; + JsConfig.Reset(); + } + + [Test] + public void IEnumerableObjectSerialization() + { + var data = GenerateSampleData(); + + JsConfig.SerializeFn = + time => new DateTime(time.Ticks, DateTimeKind.Utc).ToString("yyyy-MM-dd HH:mm:ss"); + + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + + Assert.AreEqual("DateTime\r\n" + + "2017-06-14 00:00:00\r\n" + + "2017-01-31 01:23:45\r\n", + csv); + } + + [Test] + public void IEnumerableObjectSerializationBaseline() + { + var data = new object[] + { + new { Value = true }, + new { Value = false }, + new { Value = new bool?() } + }; + + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + + Assert.AreEqual("Value\r\n" + + "True\r\n" + + "False\r\n" + + "\r\n", + csv); + } + + [Test] + public void IEnumerableObjectSerializationCustomSerializer() + { + var data = new object[] + { + new { Value = true }, + new { Value = false } + }; + + JsConfig.SerializeFn = + value => value == true ? "Yes" : "No"; + + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + + Assert.AreEqual("Value\r\n" + + "Yes\r\n" + + "No\r\n", + csv); + } + + [Test] + public void IEnumerableObjectSerializationCustomSerializerOfNullableType() + { + var data = new object[] + { + new { Value = new bool?(true) }, + new { Value = new bool?(false) }, + new { Value = new bool?() } + }; + + JsConfig.SerializeFn = + value => value.HasValue ? (value == true ? "Yes" : "No") : "Maybe"; + + var csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + + Assert.AreEqual("Value\r\n" + + "Yes\r\n" + + "No\r\n" + + "\r\n", + csv); + } + + [OneTimeTearDown] + public void TearDown() + { + JsConfig.SerializeFn = null; + JsConfig.Reset(); + JsConfig.SerializeFn = null; + JsConfig.Reset(); + JsConfig.SerializeFn = null; + JsConfig.Reset(); + + CsvConfig.Reset(); + JsConfig.Reset(); + } + + object[] GenerateSampleData() + { + return new object[] { + new POCO + { + DateTime = new DateTime(2017,6,14) + }, + new POCO + { + DateTime = new DateTime(2017,1,31, 01, 23, 45) + } + }; + } + + [Test] + public void Can_serialize_text_with_unmatched_list_or_map_chars() + { + var src = new List + { + new POCO2 + { + Prop1 = "1", + Prop2 = JsWriter.ListStartChar + "2", + Prop3 = JsWriter.MapStartChar + "3", + Prop4 = "4", + Prop5 = "5" + } + }; + + var csv = CsvSerializer.SerializeToCsv(src); + var des = csv.FromCsv>(); + + Assert.That(des[0].Prop1, Is.EqualTo(src[0].Prop1)); + Assert.That(des[0].Prop2, Is.EqualTo(src[0].Prop2)); + Assert.That(des[0].Prop3, Is.EqualTo(src[0].Prop3)); + Assert.That(des[0].Prop4, Is.EqualTo(src[0].Prop4)); + Assert.That(des[0].Prop5, Is.EqualTo(src[0].Prop5)); + } + + [Test] + public void Can_serialize_csv_and_deserialize_List_string() + { + var list = new List + { + "one", + "two", + "three" + }; + + var flatList = list.ToCsv(); + + var originalList = flatList.FromCsv>(); + + Assert.That(originalList.Count, Is.EqualTo(3)); + } + + [Test] + public void Can_serialize_csv_and_deserialize_List_primitives() + { + var list = new List + { + 1, + 2, + 3 + }; + + var flatList = list.ToCsv(); + + var originalList = flatList.FromCsv>(); + + Assert.That(originalList.Count, Is.EqualTo(3)); + } + } + + public class POCO + { + public DateTime DateTime { get; set; } + } + + public class POCO2 + { + public string Prop1 { get; set; } + public string Prop2 { get; set; } + public string Prop3 { get; set; } + public string Prop4 { get; set; } + public string Prop5 { get; set; } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CsvTypeTests.cs b/tests/ServiceStack.Text.Tests/CsvTypeTests.cs new file mode 100644 index 000000000..8aa88274b --- /dev/null +++ b/tests/ServiceStack.Text.Tests/CsvTypeTests.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using NUnit.Framework; + +namespace ServiceStack.Text.Tests +{ + [TestFixture] + public class CsvTypeTests + { + private int id; + static string[] Names = new[] { "Foo", "Bar" }; + + object Create(string name) + { + return new { id = ++id, name = name }; + } + + [SetUp] + public void SetUp() + { + id = 0; + } + + [Test] + public void Can_serialize_Dynamic_List() + { + List rows = Names.Map(Create); + var csv = rows.ToCsv(); + Assert.That(csv, Is.EqualTo("id,name\r\n1,Foo\r\n2,Bar\r\n")); + } + + [Test] + public void Can_serialize_Dynamic_Objects() + { + List rows = Names.Map(Create); + var csv = rows.ToCsv(); + Assert.That(csv, Is.EqualTo("id,name\r\n1,Foo\r\n2,Bar\r\n")); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CultureInfoTests.cs b/tests/ServiceStack.Text.Tests/CultureInfoTests.cs index fc461d75e..54d6bba7f 100644 --- a/tests/ServiceStack.Text.Tests/CultureInfoTests.cs +++ b/tests/ServiceStack.Text.Tests/CultureInfoTests.cs @@ -2,92 +2,112 @@ using System.Globalization; using System.Threading; using NUnit.Framework; +using ServiceStack.Text.Tests.Support; namespace ServiceStack.Text.Tests { - [TestFixture] - public class CultureInfoTests - : TestBase - { - public class Point - { - public double Latitude { get; set; } - public double Longitude { get; set; } + [TestFixture] + public class CultureInfoTests + : TestBase + { - public bool Equals(Point other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return other.Latitude == Latitude && other.Longitude == Longitude; - } + private CultureInfo previousCulture = CultureInfo.InvariantCulture; - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(Point)) return false; - return Equals((Point)obj); - } + [OneTimeSetUp] + public void TestFixtureSetUp() + { +#if NETCORE + previousCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = new CultureInfo("fr-FR"); +#else + previousCulture = Thread.CurrentThread.CurrentCulture; + //Thread.CurrentThread.CurrentCulture = new CultureInfo("pt-BR"); + Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR"); + Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR"); +#endif + } - public override int GetHashCode() - { - unchecked - { - return (Latitude.GetHashCode() * 397) ^ Longitude.GetHashCode(); - } - } - } + [OneTimeTearDown] + public void TestFixtureTearDown() + { +#if NETCORE + CultureInfo.CurrentCulture = previousCulture; +#else + Thread.CurrentThread.CurrentCulture = previousCulture; +#endif + } - private CultureInfo previousCulture = CultureInfo.InvariantCulture; + [Test] + public void Can_deserialize_type_with_doubles_in_different_culture() + { + var point = new Point { Latitude = -23.5707, Longitude = -46.57239 }; + SerializeAndCompare(point); + } - [TestFixtureSetUp] - public void TestFixtureSetUp() - { - previousCulture = Thread.CurrentThread.CurrentCulture; - //Thread.CurrentThread.CurrentCulture = new CultureInfo("pt-BR"); - Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR"); - Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR"); - } + [Test] + public void Can_deserialize_type_with_Single_in_different_culture() + { + Single single = (float)1.123; + var txt = TypeSerializer.SerializeToString(single); - [TestFixtureTearDown] - public void TestFixtureTearDown() - { - Thread.CurrentThread.CurrentCulture = previousCulture; - } + Console.WriteLine(txt); + } - [Test] - public void Can_deserialize_type_with_doubles_in_different_culture() - { - var point = new Point { Latitude = -23.5707, Longitude = -46.57239 }; - SerializeAndCompare(point); - } + [Test] + public void Serializes_doubles_using_InvariantCulture() + { + //Used in RedisClient + var doubleUtf8 = 66121.202.ToUtf8Bytes(); + var doubleStr = doubleUtf8.FromUtf8Bytes(); + Assert.That(doubleStr, Is.EqualTo("66121.202")); + } - [Test] - public void Can_deserialize_type_with_Single_in_different_culture() - { - Single single = (float) 1.123; - var txt = TypeSerializer.SerializeToString(single); + [Test] + public void Serializes_long_double_without_E_notation() + { + //Used in RedisClient + var doubleUtf8 = 1234567890123456d.ToUtf8Bytes(); + var doubleStr = doubleUtf8.FromUtf8Bytes(); + Assert.That(doubleStr, Is.EqualTo("1234567890123456")); + } - Console.WriteLine(txt); - } + public class NumberClass + { + public int IntValue { get; set; } + public uint UIntValue { get; set; } + public long LongValue { get; set; } + public ulong ULongValue { get; set; } + public float FloatValue { get; set; } + public double DoubleValue { get; set; } + public decimal DecimalValue { get; set; } - [Test] - public void Serializes_doubles_using_InvariantCulture() - { - //Used in RedisClient - var doubleUtf8 = 66121.202.ToUtf8Bytes(); - var doubleStr = doubleUtf8.FromUtf8Bytes(); - Assert.That(doubleStr, Is.EqualTo("66121.202")); - } + public static NumberClass Create(int i) + { + return new NumberClass + { + IntValue = i * 1000, + UIntValue = (uint)(i * 1000), + LongValue = i * 1000, + ULongValue = (ulong)(i * 1000), + FloatValue = (float)(i * 1000 + .999), + DoubleValue = i * 1000 + .999, + DecimalValue = (decimal)(i * 1000 + .999), + }; + } + } - [Test] - public void Serializes_long_double_without_E_notation() - { - //Used in RedisClient - var doubleUtf8 = 1234567890123456d.ToUtf8Bytes(); - var doubleStr = doubleUtf8.FromUtf8Bytes(); - Assert.That(doubleStr, Is.EqualTo("1234567890123456")); - } + [Test] + public void Does_use_invariant_culture_for_numbers() + { + var dto = NumberClass.Create(1); + dto.ToJson().Print(); + dto.ToJsv().Print(); + dto.ToCsv().Print(); - } -} \ No newline at end of file + Assert.That(dto.ToJson(), Does.Not.Contain("1000,9")); + Assert.That(dto.ToJsv(), Does.Not.Contain("1000,9")); + Assert.That(dto.ToCsv(), Does.Not.Contain("1000,9")); + } + + } +} diff --git a/tests/ServiceStack.Text.Tests/CustomCultureInfoTests.cs b/tests/ServiceStack.Text.Tests/CustomCultureInfoTests.cs new file mode 100644 index 000000000..d77ae1401 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/CustomCultureInfoTests.cs @@ -0,0 +1,39 @@ +using System.Globalization; +using NUnit.Framework; +using ServiceStack.Text.Tests.Support; + +namespace ServiceStack.Text.Tests +{ + [TestFixture] + public class CustomCultureInfoTests + : TestBase + { + [Test] + public void Does_not_use_custom_decimal() + { +#if NETCORE + CsvConfig.RealNumberCultureInfo = new CultureInfo("nl-NL"); +#else + CsvConfig.RealNumberCultureInfo = CultureInfo.CreateSpecificCulture("nl-NL"); +#endif + + var num = new NumberTypes + { + Int = 1111, + Float = 2222.2222f, + Double = 3333.3333, + Decimal = 4444.4444M, + }; + + num.ToJson().Print(); + num.ToJsv().Print(); + num.ToCsv().Print(); + + Assert.That(num.ToJson(), Does.Contain("4444.4444")); + Assert.That(num.ToJsv(), Does.Contain("4444.4444")); + Assert.That(num.ToCsv(), Does.Contain("4444,4444")); + + CsvConfig.RealNumberCultureInfo = null; + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CustomStructTests.cs b/tests/ServiceStack.Text.Tests/CustomStructTests.cs index 29fcfc75a..d4b978170 100644 --- a/tests/ServiceStack.Text.Tests/CustomStructTests.cs +++ b/tests/ServiceStack.Text.Tests/CustomStructTests.cs @@ -1,126 +1,116 @@ using System; -using System.Collections.Generic; using NUnit.Framework; -#if !MONOTOUCH -using ServiceStack.Common.Extensions; -#endif namespace ServiceStack.Text.Tests { - public struct UserStat - { - public Guid UserId { get; set; } - - public int TimesRecommended { get; set; } - - public int TimesPurchased { get; set; } - - public int TimesFlowed { get; set; } - - public int TimesPreviewed { get; set; } - - public int GetWeightedValue() - { - return (this.TimesRecommended * 10) - + (this.TimesPurchased * 3) - + (this.TimesFlowed * 2) - + this.TimesPreviewed; - } - - public void Add(UserStat userStat) - { - this.TimesRecommended += userStat.TimesRecommended; - this.TimesFlowed += userStat.TimesFlowed; - this.TimesPreviewed += userStat.TimesPreviewed; - this.TimesPurchased += userStat.TimesPurchased; - } - - public static UserStat Parse(string userStatString) - { - var parts = userStatString.Split(':'); - if (parts.Length != 6) - throw new ArgumentException("userStatString must have 6 parts"); - - var i = 0; - var userStat = new UserStat { - UserId = new Guid(parts[i++]), - TimesRecommended = int.Parse(parts[i++]), - TimesPurchased = int.Parse(parts[i++]), - TimesFlowed = int.Parse(parts[i++]), - TimesPreviewed = int.Parse(parts[i++]), - }; - return userStat; - } - - public override string ToString() - { - return string.Format("{0}:{1}:{2}:{3}:{4}:{5}", - this.UserId.ToString("n"), - TimesRecommended, - TimesPurchased, - TimesRecommended, - TimesPreviewed, - GetWeightedValue()); - } - } - - [TestFixture] - public class CustomStructTests - : TestBase - { - private static UserStat CreateUserStat(Guid userId, int score) - { - return new UserStat { - UserId = userId, - TimesRecommended = score, - TimesPurchased = score, - TimesFlowed = score, - TimesPreviewed = score - }; - } - - [Test] - public void Can_serialize_empty_UserStat() - { - var userStat = new UserStat(); - var dtoStr = TypeSerializer.SerializeToString(userStat); - - Assert.That(dtoStr, Is.EqualTo("\"00000000000000000000000000000000:0:0:0:0:0\"")); - - SerializeAndCompare(userStat); - } - - [Test] - public void Can_serialize_UserStat() - { - var userId = new Guid("96d7a49f7a0f46918661217995c5e4cc"); - var userStat = CreateUserStat(userId, 1); - var dtoStr = TypeSerializer.SerializeToString(userStat); - - Assert.That(dtoStr, Is.EqualTo("\"96d7a49f7a0f46918661217995c5e4cc:1:1:1:1:16\"")); - - SerializeAndCompare(userStat); - } -#if !MONOTOUCH - [Test] - public void Can_serialize_UserStats_list() - { - var guidValues = new[] { - new Guid("6203A3AF-1738-4CDF-A3AD-0F578AD198F0"), - new Guid("C7C87DF5-4821-400D-B9F7-D8EEE23C5842"), - new Guid("33EB45D4-21A0-41CC-A07D-43BFAB4B3E92"), - new Guid("ED041F82-572A-41CB-90D3-E227786BE9EB"), - new Guid("D703F00C-613A-44A9-AC2B-C46ED0F23D3C"), - }; - - var userStats = 5.Times(i => CreateUserStat(guidValues[i], i)); - var dtoStr = TypeSerializer.SerializeToString(userStats); - - Assert.That(dtoStr, Is.EqualTo( - "[\"6203a3af17384cdfa3ad0f578ad198f0:0:0:0:0:0\",\"c7c87df54821400db9f7d8eee23c5842:1:1:1:1:16\",\"33eb45d421a041cca07d43bfab4b3e92:2:2:2:2:32\",\"ed041f82572a41cb90d3e227786be9eb:3:3:3:3:48\",\"d703f00c613a44a9ac2bc46ed0f23d3c:4:4:4:4:64\"]")); - - SerializeAndCompare(userStats); - } + public struct UserStat + { + public Guid UserId { get; set; } + + public int TimesRecommended { get; set; } + + public int TimesPurchased { get; set; } + + public int TimesFlowed { get; set; } + + public int TimesPreviewed { get; set; } + + public int GetWeightedValue() + { + return (this.TimesRecommended * 10) + + (this.TimesPurchased * 3) + + (this.TimesFlowed * 2) + + this.TimesPreviewed; + } + + public void Add(UserStat userStat) + { + this.TimesRecommended += userStat.TimesRecommended; + this.TimesFlowed += userStat.TimesFlowed; + this.TimesPreviewed += userStat.TimesPreviewed; + this.TimesPurchased += userStat.TimesPurchased; + } + + public static UserStat Parse(string userStatString) + { + var parts = userStatString.Split(':'); + if (parts.Length != 6) + throw new ArgumentException("userStatString must have 6 parts"); + + var i = 0; + var userStat = new UserStat + { + UserId = new Guid(parts[i++]), + TimesRecommended = int.Parse(parts[i++]), + TimesPurchased = int.Parse(parts[i++]), + TimesFlowed = int.Parse(parts[i++]), + TimesPreviewed = int.Parse(parts[i++]), + }; + return userStat; + } + + public override string ToString() => + $"{this.UserId:n}:{TimesRecommended}:{TimesPurchased}:{TimesRecommended}:{TimesPreviewed}:{GetWeightedValue()}"; + } + + [TestFixture] + public class CustomStructTests + : TestBase + { + private static UserStat CreateUserStat(Guid userId, int score) + { + return new UserStat + { + UserId = userId, + TimesRecommended = score, + TimesPurchased = score, + TimesFlowed = score, + TimesPreviewed = score + }; + } + + [Test] + public void Can_serialize_empty_UserStat() + { + var userStat = new UserStat(); + var dtoStr = TypeSerializer.SerializeToString(userStat); + + Assert.That(dtoStr, Is.EqualTo("\"00000000000000000000000000000000:0:0:0:0:0\"")); + + SerializeAndCompare(userStat); + } + + [Test] + public void Can_serialize_UserStat() + { + var userId = new Guid("96d7a49f7a0f46918661217995c5e4cc"); + var userStat = CreateUserStat(userId, 1); + var dtoStr = TypeSerializer.SerializeToString(userStat); + + Assert.That(dtoStr, Is.EqualTo("\"96d7a49f7a0f46918661217995c5e4cc:1:1:1:1:16\"")); + + SerializeAndCompare(userStat); + } +#if !IOS + [Test] + public void Can_serialize_UserStats_list() + { + var guidValues = new[] { + new Guid("6203A3AF-1738-4CDF-A3AD-0F578AD198F0"), + new Guid("C7C87DF5-4821-400D-B9F7-D8EEE23C5842"), + new Guid("33EB45D4-21A0-41CC-A07D-43BFAB4B3E92"), + new Guid("ED041F82-572A-41CB-90D3-E227786BE9EB"), + new Guid("D703F00C-613A-44A9-AC2B-C46ED0F23D3C"), + }; + + var userStats = 5.Times(i => CreateUserStat(guidValues[i], i)); + var dtoStr = TypeSerializer.SerializeToString(userStats); + + Assert.That(dtoStr, Is.EqualTo( + "[\"6203a3af17384cdfa3ad0f578ad198f0:0:0:0:0:0\",\"c7c87df54821400db9f7d8eee23c5842:1:1:1:1:16\",\"33eb45d421a041cca07d43bfab4b3e92:2:2:2:2:32\",\"ed041f82572a41cb90d3e227786be9eb:3:3:3:3:48\",\"d703f00c613a44a9ac2bc46ed0f23d3c:4:4:4:4:64\"]")); + + SerializeAndCompare(userStats); + } #endif - } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/CyclicalDependencyTests.cs b/tests/ServiceStack.Text.Tests/CyclicalDependencyTests.cs index d46140ca4..f76d48815 100644 --- a/tests/ServiceStack.Text.Tests/CyclicalDependencyTests.cs +++ b/tests/ServiceStack.Text.Tests/CyclicalDependencyTests.cs @@ -1,156 +1,248 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Text; +using System.Runtime.Serialization; using NUnit.Framework; namespace ServiceStack.Text.Tests { - public class Module - { - public Module() - { - ExtendedData = new Dictionary(); - } - - public string Name { get; set; } - public string Version { get; set; } - public IDictionary ExtendedData { get; set; } - } - - public class StackFrame - { - public StackFrame() - { - ExtendedData = new Dictionary(); - Parameters = new Collection(); - } - - public string FileName { get; set; } - public int LineNumber { get; set; } - public int Column { get; set; } - public IDictionary ExtendedData { get; set; } - public string Type { get; set; } - public string Namespace { get; set; } - public Module Module { get; set; } - public string Method { get; set; } - public ICollection Parameters { get; set; } - } - - public class Parameter - { - public Parameter() - { - ExtendedData = new Dictionary(); - } - - public string Name { get; set; } - public string Type { get; set; } - public IDictionary ExtendedData { get; set; } - } - - public class Error - { - public Error() - { - ExtendedData = new Dictionary(); - Tags = new HashSet(); - StackTrace = new Collection(); - } - - public string Id { get; set; } - public string Message { get; set; } - public string Type { get; set; } - public Module Module { get; set; } - public string Description { get; set; } - public DateTime OccurrenceDate { get; set; } - public string Code { get; set; } - public IDictionary ExtendedData { get; set; } - public HashSet Tags { get; set; } - - public Error Inner { get; set; } - - public ICollection StackTrace { get; set; } - public string Contact { get; set; } - public string Notes { get; set; } - } - - - [TestFixture] - public class CyclicalDependencyTests : TestBase - { - [Test] - public void Can_serialize_Error() - { - var dto = new Error { - Id = "Id", - Message = "Message", - Type = "Type", - Description = "Description", - OccurrenceDate = new DateTime(2012, 01, 01), - Code = "Code", - ExtendedData = new Dictionary { { "Key", "Value" } }, - Tags = new HashSet { "C#", "ruby" }, - Inner = new Error { - Id = "Id2", - Message = "Message2", - ExtendedData = new Dictionary { { "InnerKey", "InnerValue" } }, - Module = new Module { - Name = "Name", - Version = "v1.0" - }, - StackTrace = new Collection { - new StackFrame { - Column = 1, - Module = new Module { - Name = "StackTrace.Name", - Version = "StackTrace.v1.0" - }, - ExtendedData = new Dictionary { { "StackTraceKey", "StackTraceValue" } }, - FileName = "FileName", - Type = "Type", - LineNumber = 1, - Method = "Method", - Namespace = "Namespace", - Parameters = new Collection { - new Parameter { Name = "Parameter", Type = "ParameterType" }, - } - } - } - }, - Contact = "Contact", - Notes = "Notes", - }; - - var from = Serialize(dto, includeXml: false); - Console.WriteLine(from.Dump()); - - Assert.That(from.Id, Is.EqualTo(dto.Id)); - Assert.That(from.Message, Is.EqualTo(dto.Message)); - Assert.That(from.Type, Is.EqualTo(dto.Type)); - Assert.That(from.Description, Is.EqualTo(dto.Description)); - Assert.That(from.OccurrenceDate, Is.EqualTo(dto.OccurrenceDate)); - Assert.That(from.Code, Is.EqualTo(dto.Code)); - - Assert.That(from.Inner.Id, Is.EqualTo(dto.Inner.Id)); - Assert.That(from.Inner.Message, Is.EqualTo(dto.Inner.Message)); - Assert.That(from.Inner.ExtendedData["InnerKey"], Is.EqualTo(dto.Inner.ExtendedData["InnerKey"])); - Assert.That(from.Inner.Module.Name, Is.EqualTo(dto.Inner.Module.Name)); - Assert.That(from.Inner.Module.Version, Is.EqualTo(dto.Inner.Module.Version)); - - var actualStack = from.Inner.StackTrace.First(); - var expectedStack = dto.Inner.StackTrace.First(); - Assert.That(actualStack.Column, Is.EqualTo(expectedStack.Column)); - Assert.That(actualStack.FileName, Is.EqualTo(expectedStack.FileName)); - Assert.That(actualStack.Type, Is.EqualTo(expectedStack.Type)); - Assert.That(actualStack.LineNumber, Is.EqualTo(expectedStack.LineNumber)); - Assert.That(actualStack.Method, Is.EqualTo(expectedStack.Method)); - - Assert.That(from.Contact, Is.EqualTo(dto.Contact)); - Assert.That(from.Notes, Is.EqualTo(dto.Notes)); - } - } + public class Module + { + public Module() + { + ExtendedData = new Dictionary(); + } + + public string Name { get; set; } + public string Version { get; set; } + public IDictionary ExtendedData { get; set; } + } + + public class StackFrame + { + public StackFrame() + { + ExtendedData = new Dictionary(); + Parameters = new Collection(); + } + + public string FileName { get; set; } + public int LineNumber { get; set; } + public int Column { get; set; } + public IDictionary ExtendedData { get; set; } + public string Type { get; set; } + public string Namespace { get; set; } + public Module Module { get; set; } + public string Method { get; set; } + public ICollection Parameters { get; set; } + } + + public class Parameter + { + public Parameter() + { + ExtendedData = new Dictionary(); + } + + public string Name { get; set; } + public string Type { get; set; } + public IDictionary ExtendedData { get; set; } + } + + public class Error + { + public Error() + { + ExtendedData = new Dictionary(); + Tags = new HashSet(); + StackTrace = new Collection(); + } + + public string Id { get; set; } + public string Message { get; set; } + public string Type { get; set; } + public Module Module { get; set; } + public string Description { get; set; } + public DateTime OccurrenceDate { get; set; } + public string Code { get; set; } + public IDictionary ExtendedData { get; set; } + public HashSet Tags { get; set; } + + public Error Inner { get; set; } + + public ICollection StackTrace { get; set; } + public string Contact { get; set; } + public string Notes { get; set; } + } + + + [TestFixture] + public class CyclicalDependencyTests : TestBase + { + [Test] + public void Can_serialize_Error() + { + var dto = new Error + { + Id = "Id", + Message = "Message", + Type = "Type", + Description = "Description", + OccurrenceDate = new DateTime(2012, 01, 01), + Code = "Code", + ExtendedData = new Dictionary { { "Key", "Value" } }, + Tags = new HashSet { "C#", "ruby" }, + Inner = new Error + { + Id = "Id2", + Message = "Message2", + ExtendedData = new Dictionary { { "InnerKey", "InnerValue" } }, + Module = new Module + { + Name = "Name", + Version = "v1.0" + }, + StackTrace = new Collection { + new StackFrame { + Column = 1, + Module = new Module { + Name = "StackTrace.Name", + Version = "StackTrace.v1.0" + }, + ExtendedData = new Dictionary { { "StackTraceKey", "StackTraceValue" } }, + FileName = "FileName", + Type = "Type", + LineNumber = 1, + Method = "Method", + Namespace = "Namespace", + Parameters = new Collection { + new Parameter { Name = "Parameter", Type = "ParameterType" }, + } + } + } + }, + Contact = "Contact", + Notes = "Notes", + }; + + var from = Serialize(dto, includeXml: false); + Console.WriteLine(from.Dump()); + + Assert.That(from.Id, Is.EqualTo(dto.Id)); + Assert.That(from.Message, Is.EqualTo(dto.Message)); + Assert.That(from.Type, Is.EqualTo(dto.Type)); + Assert.That(from.Description, Is.EqualTo(dto.Description)); + Assert.That(from.OccurrenceDate, Is.EqualTo(dto.OccurrenceDate)); + Assert.That(from.Code, Is.EqualTo(dto.Code)); + + Assert.That(from.Inner.Id, Is.EqualTo(dto.Inner.Id)); + Assert.That(from.Inner.Message, Is.EqualTo(dto.Inner.Message)); + Assert.That(from.Inner.ExtendedData["InnerKey"], Is.EqualTo(dto.Inner.ExtendedData["InnerKey"])); + Assert.That(from.Inner.Module.Name, Is.EqualTo(dto.Inner.Module.Name)); + Assert.That(from.Inner.Module.Version, Is.EqualTo(dto.Inner.Module.Version)); + + var actualStack = from.Inner.StackTrace.First(); + var expectedStack = dto.Inner.StackTrace.First(); + Assert.That(actualStack.Column, Is.EqualTo(expectedStack.Column)); + Assert.That(actualStack.FileName, Is.EqualTo(expectedStack.FileName)); + Assert.That(actualStack.Type, Is.EqualTo(expectedStack.Type)); + Assert.That(actualStack.LineNumber, Is.EqualTo(expectedStack.LineNumber)); + Assert.That(actualStack.Method, Is.EqualTo(expectedStack.Method)); + + Assert.That(from.Contact, Is.EqualTo(dto.Contact)); + Assert.That(from.Notes, Is.EqualTo(dto.Notes)); + } + + class person + { + public string name { get; set; } + public person teacher { get; set; } + } + + [Test] + public void Can_limit_cyclical_dependencies() + { + using (JsConfig.With(new Config { MaxDepth = 4 })) + { + var p = new person(); + p.teacher = new person { name = "sam", teacher = p }; + p.name = "bob"; + p.PrintDump(); + p.ToJsv().Print(); + p.ToJson().Print(); + } + } + + class Node + { + public string Name { get; set; } + + [IgnoreDataMember] + public Node Parent { get; set; } + + public List Children { get; set; } + } + + [Test] + public void Ignore_Cyclical_dependencies() + { + JsConfig.OnDeserializedFn = (node) => + { + node.Children.Each(child => child.Parent = node); + return node; + }; + + var parent = new Node + { + Name = "Parent", + }; + parent.Children = new List + { + new Node { Name = "Child", Parent = parent }, + }; + + var json = parent.ToJson(); + Assert.That(json, + Is.EqualTo("{\"Name\":\"Parent\",\"Children\":[{\"Name\":\"Child\"}]}")); + + var fromJson = json.FromJson(); + + Assert.That(fromJson.Children[0].Parent, Is.EqualTo(fromJson)); + + JsConfig.OnDeserializedFn = null; + JsConfig.Reset(); + } + + public class ReflectionType + { + public string Name { get; set; } = "A"; + public Type Type { get; set; } + public MethodInfo MethodInfo { get; set; } + public PropertyInfo PropertyInfo { get; set; } + public FieldInfo FieldInfo; + public MemberInfo MemberInfo { get; set; } + + public void Method() {} + } + + [Test] + public void Can_serialize_POCO_with_Type() + { + var dto = new ReflectionType { + Type = typeof(ReflectionType), + MethodInfo = typeof(ReflectionType).GetMethod(nameof(ReflectionType.Method)), + PropertyInfo = typeof(ReflectionType).GetProperty(nameof(ReflectionType.PropertyInfo)), + FieldInfo = typeof(ReflectionType).GetPublicFields().FirstOrDefault(), + MemberInfo = typeof(ReflectionType).GetMembers().FirstOrDefault(), + }; + + dto.Name.Print(); + dto.ToJson().Print(); + dto.ToJsv().Print(); + dto.PrintDump(); + } + + } } diff --git a/tests/ServiceStack.Text.Tests/DataContractTests.cs b/tests/ServiceStack.Text.Tests/DataContractTests.cs index e25832a8e..c4f689049 100644 --- a/tests/ServiceStack.Text.Tests/DataContractTests.cs +++ b/tests/ServiceStack.Text.Tests/DataContractTests.cs @@ -188,6 +188,20 @@ public class ClassThree public string Title { get; set; } } + [DataContract] + public class ClassFour + { + [DataMember(Name = "some-title")] + public string Title; + } + + [DataContract] + public class ClassFive + { + [DataMember(Name = "some-bytes")] + public byte[] Bytes; + } + [Test] public void Csv_Serialize_Should_Respects_DataContract_Name() { @@ -196,7 +210,7 @@ public void Csv_Serialize_Should_Respects_DataContract_Name() }; Assert.That(CsvSerializer.SerializeToString(classTwo), - Is.EqualTo(String.Format("NewName{0}Value{0}", Environment.NewLine))); + Is.EqualTo(String.Format("NewName\r\nValue\r\n"))); } [Test] @@ -218,6 +232,13 @@ public void Json_Serialize_Should_Respects_DataContract_Name_When_Deserialize() Assert.That(t.Title, Is.EqualTo("right")); } + [Test] + public void Json_Serialize_Should_Respects_DataContract_Field_Name_When_Deserialize() + { + var t = JsonSerializer.DeserializeFromString("{\"some-title\": \"right\", \"Title\": \"wrong\"}"); + Assert.That(t.Title, Is.EqualTo("right")); + } + [Test] public void Json_Serialize_Should_Respects_DataContract_Name() { @@ -231,15 +252,10 @@ public void Json_Serialize_Should_Respects_DataContract_Name() } [Test] - public void Can_get_weak_DataMember() + public void Json_Serialize_Should_Respects_DataContract_Field_Name_With_Bytes_Deserialize() { - var dto = new ClassOne(); - var dataMemberAttr = dto.GetType().GetProperty("Id").GetWeakDataMember(); - Assert.That(dataMemberAttr.Name, Is.Null); - - dataMemberAttr = dto.GetType().GetProperty("List").GetWeakDataMember(); - Assert.That(dataMemberAttr.Name, Is.EqualTo("listClassTwo")); - Assert.That(dataMemberAttr.Order, Is.EqualTo(1)); + var t = JsonSerializer.DeserializeFromString("{\"some-bytes\": \"YWI=\"}"); + Assert.IsTrue (t.Bytes.AreEqual (new byte[] { 0x61, 0x62 })); } [DataContract(Name = "my-class", Namespace = "http://schemas.ns.com")] @@ -250,22 +266,46 @@ public class MyClass } [Test] - public void Can_get_weak_DataContract() + public void Does_use_DataMember_Name() { var mc = new MyClass { Title = "Some random title" }; - var attr = mc.GetType().GetWeakDataContract(); + Assert.That(mc.ToJson(), Is.EqualTo("{\"some-title\":\"Some random title\"}")); + } + + [DataContract] + public class AliasWithDataContract + { + public int Id { get; set; } - Assert.That(attr.Name, Is.EqualTo("my-class")); - Assert.That(attr.Namespace, Is.EqualTo("http://schemas.ns.com")); + [DataMember(Name = "alias")] + public string Name { get; set; } } [Test] - public void Does_use_DataMember_Name() + public void Does_use_alias_and_is_optin_with_DataContract_Attribute() { - var mc = new MyClass { Title = "Some random title" }; + var dto = new AliasWithDataContract { Id = 1, Name = "foo" }; + Assert.That(dto.ToJson(), Is.EqualTo("{\"alias\":\"foo\"}")); + } - Assert.That(mc.ToJson(), Is.EqualTo("{\"some-title\":\"Some random title\"}")); + public class AliasWithoutDataContract + { + public int Id { get; set; } + + [DataMember(Name = "alias")] + public string Name { get; set; } + } + + [Test] + public void Does_use_alias_and_is_not_optin_without_DataContract_Attribute() + { + var dto = new AliasWithoutDataContract { Id = 1, Name = "foo" }; + Assert.That(dto.ToJson(), Is.EqualTo("{\"Id\":1,\"alias\":\"foo\"}")); + + Assert.That(dto.ToJsv(), Is.EqualTo("{Id:1,alias:foo}")); + + Assert.That(dto.ToCsv(), Is.EqualTo("Id,alias\r\n1,foo\r\n")); } } diff --git a/tests/ServiceStack.Text.Tests/DataTests.cs b/tests/ServiceStack.Text.Tests/DataTests.cs index d072c5277..9854dc9c0 100644 --- a/tests/ServiceStack.Text.Tests/DataTests.cs +++ b/tests/ServiceStack.Text.Tests/DataTests.cs @@ -6,81 +6,88 @@ namespace ServiceStack.Text.Tests { - [TestFixture] - public class DataStressTests - : TestBase - { - public class TestClass - { - public string Value { get; set; } - - public bool Equals(TestClass other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other.Value, Value); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof (TestClass)) return false; - return Equals((TestClass) obj); - } - - public override int GetHashCode() - { - return (Value != null ? Value.GetHashCode() : 0); - } - } - - [Test] - public void serialize_Customer_BOLID() - { - var customer = NorthwindFactory.Customer( - "BOLID", "Blido Comidas preparadas", "Martn Sommer", "Owner", "C/ Araquil, 67", - "Madrid", null, "28023", "Spain", "(91) 555 22 82", "(91) 555 91 99", null); - - var model = new TestClass - { - Value = TypeSerializer.SerializeToString(customer) - }; - - var toModel = Serialize(model); - Console.WriteLine("toModel.Value: " + toModel.Value); - - var toCustomer = TypeSerializer.DeserializeFromString(toModel.Value); - Console.WriteLine("customer.Address: " + customer.Address); - Console.WriteLine("toCustomer.Address: " + toCustomer.Address); - } - - [DataContract] - public class GetValuesResponse - { - public GetValuesResponse() - { - this.ResponseStatus = new ResponseStatus(); - - this.Values = new ArrayOfString(); - } - - [DataMember] - public ArrayOfString Values { get; set; } - - [DataMember] - public ResponseStatus ResponseStatus { get; set; } - } - - [Test] - public void serialize_GetValuesResponse() - { - const string responseJsv = "{Values:[\"{Id:1,LastName:Davolio,FirstName:Nancy,Title:Sales Representative,TitleOfCourtesy:Ms.,BirthDate:1948-12-08,HireDate:1992-05-01,Address:507 - 20th Ave. E. Apt. 2A,City:Seattle,Region:WA,PostalCode:98122,Country:USA,HomePhone:(206) 555-9857,Extension:5467,Notes:Education includes a BA in psychology from Colorado State University in 1970. She also completed 'The Art of the Cold Call.' Nancy is a member of Toastmasters International.,ReportsTo:2,PhotoPath:http://accweb/emmployees/davolio.bmp}\",\"{Id:2,LastName:Fuller,FirstName:Andrew,Title:\"\"Vice President, Sales\"\",TitleOfCourtesy:Dr.,BirthDate:1952-02-19,HireDate:1992-08-14,Address:908 W. Capital Way,City:Tacoma,Region:WA,PostalCode:98401,Country:USA,HomePhone:(206) 555-9482,Extension:3457,Notes:\"\"Andrew received his BTS commercial in 1974 and a Ph.D. in international marketing from the University of Dallas in 1981. He is fluent in French and Italian and reads German. He joined the company as a sales representative, was promoted to sales manager in January 1992 and to vice president of sales in March 1993. Andrew is a member of the Sales Management Roundtable, the Seattle Chamber of Commerce, and the Pacific Rim Importers Association.\"\",PhotoPath:http://accweb/emmployees/fuller.bmp}\",\"{Id:3,LastName:Leverling,FirstName:Janet,Title:Sales Representative,TitleOfCourtesy:Ms.,BirthDate:1963-08-30,HireDate:1992-04-01,Address:722 Moss Bay Blvd.,City:Kirkland,Region:WA,PostalCode:98033,Country:USA,HomePhone:(206) 555-3412,Extension:3355,Notes:Janet has a BS degree in chemistry from Boston College (1984). She has also completed a certificate program in food retailing management. Janet was hired as a sales associate in 1991 and promoted to sales representative in February 1992.,ReportsTo:2,PhotoPath:http://accweb/emmployees/leverling.bmp}\",\"{Id:4,LastName:Peacock,FirstName:Margaret,Title:Sales Representative,TitleOfCourtesy:Mrs.,BirthDate:1937-09-19,HireDate:1993-05-03,Address:4110 Old Redmond Rd.,City:Redmond,Region:WA,PostalCode:98052,Country:USA,HomePhone:(206) 555-8122,Extension:5176,Notes:Margaret holds a BA in English literature from Concordia College (1958) and an MA from the American Institute of Culinary Arts (1966). She was assigned to the London office temporarily from July through November 1992.,ReportsTo:2,PhotoPath:http://accweb/emmployees/peacock.bmp}\",\"{Id:5,LastName:Buchanan,FirstName:Steven,Title:Sales Manager,TitleOfCourtesy:Mr.,BirthDate:1955-03-04,HireDate:1993-10-17,Address:14 Garrett Hill,City:London,PostalCode:SW1 8JR,Country:UK,HomePhone:(71) 555-4848,Extension:3453,Notes:\"\"Steven Buchanan graduated from St. Andrews University, Scotland, with a BSC degree in 1976. Upon joining the company as a sales representative in 1992, he spent 6 months in an orientation program at the Seattle office and then returned to his permanent post in London. He was promoted to sales manager in March 1993. Mr. Buchanan has completed the courses 'Successful Telemarketing' and 'International Sales Management.' He is fluent in French.\"\",ReportsTo:2,PhotoPath:http://accweb/emmployees/buchanan.bmp}\",\"{Id:6,LastName:Suyama,FirstName:Michael,Title:Sales Representative,TitleOfCourtesy:Mr.,BirthDate:1963-07-02,HireDate:1993-10-17,Address:Coventry House Miner Rd.,City:London,PostalCode:EC2 7JR,Country:UK,HomePhone:(71) 555-7773,Extension:428,Notes:\"\"Michael is a graduate of Sussex University (MA, economics, 1983) and the University of California at Los Angeles (MBA, marketing, 1986). He has also taken the courses 'Multi-Cultural Selling' and 'Time Management for the Sales Professional.' He is fluent in Japanese and can read and write French, Portuguese, and Spanish.\"\",ReportsTo:5,PhotoPath:http://accweb/emmployees/davolio.bmp}\",\"{Id:7,LastName:King,FirstName:Robert,Title:Sales Representative,TitleOfCourtesy:Mr.,BirthDate:1960-05-29,HireDate:1994-01-02,Address:Edgeham Hollow Winchester Way,City:London,PostalCode:RG1 9SP,Country:UK,HomePhone:(71) 555-5598,Extension:465,Notes:\"\"Robert King served in the Peace Corps and traveled extensively before completing his degree in English at the University of Michigan in 1992, the year he joined the company. After completing a course entitled 'Selling in Europe,' he was transferred to the London office in March 1993.\"\",ReportsTo:5,PhotoPath:http://accweb/emmployees/davolio.bmp}\",\"{Id:8,LastName:Callahan,FirstName:Laura,Title:Inside Sales Coordinator,TitleOfCourtesy:Ms.,BirthDate:1958-01-09,HireDate:1994-03-05,Address:4726 - 11th Ave. N.E.,City:Seattle,Region:WA,PostalCode:98105,Country:USA,HomePhone:(206) 555-1189,Extension:2344,Notes:Laura received a BA in psychology from the University of Washington. She has also completed a course in business French. She reads and writes French.,ReportsTo:2,PhotoPath:http://accweb/emmployees/davolio.bmp}\",\"{Id:9,LastName:Dodsworth,FirstName:Anne,Title:Sales Representative,TitleOfCourtesy:Ms.,BirthDate:1966-01-27,HireDate:1994-11-15,Address:7 Houndstooth Rd.,City:London,PostalCode:WG2 7LT,Country:UK,HomePhone:(71) 555-4444,Extension:452,Notes:Anne has a BA degree in English from St. Lawrence College. She is fluent in French and German.,ReportsTo:5,PhotoPath:http://accweb/emmployees/davolio.bmp}\"],ResponseStatus:{Errors:[]}}"; - - var response = TypeSerializer.DeserializeFromString(responseJsv); - - Assert.That(response.Values, Has.Count.EqualTo(9)); - } - - } + [TestFixture] + public class DataStressTests + : TestBase + { + public class TestClass + { + public string Value { get; set; } + + public bool Equals(TestClass other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(other.Value, Value); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(TestClass)) return false; + return Equals((TestClass)obj); + } + + public override int GetHashCode() + { + return (Value != null ? Value.GetHashCode() : 0); + } + } + + [Test] + public void serialize_Customer_BOLID() + { + var customer = NorthwindFactory.Customer( + "BOLID", "Blido Comidas preparadas", "Martn Sommer", "Owner", "C/ Araquil, 67", + "Madrid", null, "28023", "Spain", "(91) 555 22 82", "(91) 555 91 99", null); + + var model = new TestClass + { + Value = TypeSerializer.SerializeToString(customer) + }; + + var toModel = Serialize(model); + Console.WriteLine("toModel.Value: " + toModel.Value); + + var toCustomer = TypeSerializer.DeserializeFromString(toModel.Value); + Console.WriteLine("customer.Address: " + customer.Address); + Console.WriteLine("toCustomer.Address: " + toCustomer.Address); + } + + [DataContract] + public class GetValuesResponse + { + public GetValuesResponse() + { + this.ResponseStatus = new ResponseStatus(); + + this.Values = new ArrayOfString(); + } + + [DataMember] + public ArrayOfString Values { get; set; } + + [DataMember] + public ResponseStatus ResponseStatus { get; set; } + } + + [Test] + public void serialize_GetValuesResponse() + { + const string responseJsv = "{Values:[\"{Id:1,LastName:Davolio,FirstName:Nancy,Title:Sales Representative,TitleOfCourtesy:Ms.,BirthDate:1948-12-08,HireDate:1992-05-01,Address:507 - 20th Ave. E. Apt. 2A,City:Seattle,Region:WA,PostalCode:98122,Country:USA,HomePhone:(206) 555-9857,Extension:5467,Notes:Education includes a BA in psychology from Colorado State University in 1970. She also completed 'The Art of the Cold Call.' Nancy is a member of Toastmasters International.,ReportsTo:2,PhotoPath:http://accweb/emmployees/davolio.bmp}\",\"{Id:2,LastName:Fuller,FirstName:Andrew,Title:\"\"Vice President, Sales\"\",TitleOfCourtesy:Dr.,BirthDate:1952-02-19,HireDate:1992-08-14,Address:908 W. Capital Way,City:Tacoma,Region:WA,PostalCode:98401,Country:USA,HomePhone:(206) 555-9482,Extension:3457,Notes:\"\"Andrew received his BTS commercial in 1974 and a Ph.D. in international marketing from the University of Dallas in 1981. He is fluent in French and Italian and reads German. He joined the company as a sales representative, was promoted to sales manager in January 1992 and to vice president of sales in March 1993. Andrew is a member of the Sales Management Roundtable, the Seattle Chamber of Commerce, and the Pacific Rim Importers Association.\"\",PhotoPath:http://accweb/emmployees/fuller.bmp}\",\"{Id:3,LastName:Leverling,FirstName:Janet,Title:Sales Representative,TitleOfCourtesy:Ms.,BirthDate:1963-08-30,HireDate:1992-04-01,Address:722 Moss Bay Blvd.,City:Kirkland,Region:WA,PostalCode:98033,Country:USA,HomePhone:(206) 555-3412,Extension:3355,Notes:Janet has a BS degree in chemistry from Boston College (1984). She has also completed a certificate program in food retailing management. Janet was hired as a sales associate in 1991 and promoted to sales representative in February 1992.,ReportsTo:2,PhotoPath:http://accweb/emmployees/leverling.bmp}\",\"{Id:4,LastName:Peacock,FirstName:Margaret,Title:Sales Representative,TitleOfCourtesy:Mrs.,BirthDate:1937-09-19,HireDate:1993-05-03,Address:4110 Old Redmond Rd.,City:Redmond,Region:WA,PostalCode:98052,Country:USA,HomePhone:(206) 555-8122,Extension:5176,Notes:Margaret holds a BA in English literature from Concordia College (1958) and an MA from the American Institute of Culinary Arts (1966). She was assigned to the London office temporarily from July through November 1992.,ReportsTo:2,PhotoPath:http://accweb/emmployees/peacock.bmp}\",\"{Id:5,LastName:Buchanan,FirstName:Steven,Title:Sales Manager,TitleOfCourtesy:Mr.,BirthDate:1955-03-04,HireDate:1993-10-17,Address:14 Garrett Hill,City:London,PostalCode:SW1 8JR,Country:UK,HomePhone:(71) 555-4848,Extension:3453,Notes:\"\"Steven Buchanan graduated from St. Andrews University, Scotland, with a BSC degree in 1976. Upon joining the company as a sales representative in 1992, he spent 6 months in an orientation program at the Seattle office and then returned to his permanent post in London. He was promoted to sales manager in March 1993. Mr. Buchanan has completed the courses 'Successful Telemarketing' and 'International Sales Management.' He is fluent in French.\"\",ReportsTo:2,PhotoPath:http://accweb/emmployees/buchanan.bmp}\",\"{Id:6,LastName:Suyama,FirstName:Michael,Title:Sales Representative,TitleOfCourtesy:Mr.,BirthDate:1963-07-02,HireDate:1993-10-17,Address:Coventry House Miner Rd.,City:London,PostalCode:EC2 7JR,Country:UK,HomePhone:(71) 555-7773,Extension:428,Notes:\"\"Michael is a graduate of Sussex University (MA, economics, 1983) and the University of California at Los Angeles (MBA, marketing, 1986). He has also taken the courses 'Multi-Cultural Selling' and 'Time Management for the Sales Professional.' He is fluent in Japanese and can read and write French, Portuguese, and Spanish.\"\",ReportsTo:5,PhotoPath:http://accweb/emmployees/davolio.bmp}\",\"{Id:7,LastName:King,FirstName:Robert,Title:Sales Representative,TitleOfCourtesy:Mr.,BirthDate:1960-05-29,HireDate:1994-01-02,Address:Edgeham Hollow Winchester Way,City:London,PostalCode:RG1 9SP,Country:UK,HomePhone:(71) 555-5598,Extension:465,Notes:\"\"Robert King served in the Peace Corps and traveled extensively before completing his degree in English at the University of Michigan in 1992, the year he joined the company. After completing a course entitled 'Selling in Europe,' he was transferred to the London office in March 1993.\"\",ReportsTo:5,PhotoPath:http://accweb/emmployees/davolio.bmp}\",\"{Id:8,LastName:Callahan,FirstName:Laura,Title:Inside Sales Coordinator,TitleOfCourtesy:Ms.,BirthDate:1958-01-09,HireDate:1994-03-05,Address:4726 - 11th Ave. N.E.,City:Seattle,Region:WA,PostalCode:98105,Country:USA,HomePhone:(206) 555-1189,Extension:2344,Notes:Laura received a BA in psychology from the University of Washington. She has also completed a course in business French. She reads and writes French.,ReportsTo:2,PhotoPath:http://accweb/emmployees/davolio.bmp}\",\"{Id:9,LastName:Dodsworth,FirstName:Anne,Title:Sales Representative,TitleOfCourtesy:Ms.,BirthDate:1966-01-27,HireDate:1994-11-15,Address:7 Houndstooth Rd.,City:London,PostalCode:WG2 7LT,Country:UK,HomePhone:(71) 555-4444,Extension:452,Notes:Anne has a BA degree in English from St. Lawrence College. She is fluent in French and German.,ReportsTo:5,PhotoPath:http://accweb/emmployees/davolio.bmp}\"],ResponseStatus:{Errors:[]}}"; + + var response = TypeSerializer.DeserializeFromString(responseJsv); + + Assert.That(response.Values, Has.Count.EqualTo(9)); + } + + [Test] + public void Does_Combine_Byte() + { + var wordBytes = "HELLO".ToUtf8Bytes().Combine(" ".ToUtf8Bytes(), "WORLD".ToUtf8Bytes()); + var word = wordBytes.FromUtf8Bytes(); + Assert.That(word, Is.EqualTo("HELLO WORLD")); + } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/DateTimeExtensionsTests.cs b/tests/ServiceStack.Text.Tests/DateTimeExtensionsTests.cs new file mode 100644 index 000000000..1d235e72b --- /dev/null +++ b/tests/ServiceStack.Text.Tests/DateTimeExtensionsTests.cs @@ -0,0 +1,19 @@ +using System; +using NUnit.Framework; + +namespace ServiceStack.Text.Tests +{ + [TestFixture] + public class DateTimeExtensionsTests + { + [TestCase] + public void LastMondayTest() + { + var monday = new DateTime(2013, 04, 15); + + var lastMonday = DateTimeExtensions.LastMonday(monday); + + Assert.AreEqual(monday, lastMonday); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/DateTimeOffsetAndTimeSpanTests.cs b/tests/ServiceStack.Text.Tests/DateTimeOffsetAndTimeSpanTests.cs index a5a2aba34..b168d5e91 100644 --- a/tests/ServiceStack.Text.Tests/DateTimeOffsetAndTimeSpanTests.cs +++ b/tests/ServiceStack.Text.Tests/DateTimeOffsetAndTimeSpanTests.cs @@ -1,7 +1,7 @@ using System; using NUnit.Framework; -#if !MONOTOUCH -using ServiceStack.ServiceModel.Serialization; +#if !NETCORE +using ServiceStack.Serialization; #endif namespace ServiceStack.Text.Tests @@ -9,14 +9,14 @@ namespace ServiceStack.Text.Tests [TestFixture] public class DateTimeOffsetAndTimeSpanTests : TestBase { -#if !MONOTOUCH - [TestFixtureSetUp] +#if !IOS && !NETCORE + [OneTimeSetUp] public void TestFixtureSetUp() { JsonDataContractSerializer.Instance.UseBcl = true; } - [TestFixtureTearDown] + [OneTimeTearDown] public void TestFixtureTearDown() { JsonDataContractSerializer.Instance.UseBcl = false; @@ -33,7 +33,7 @@ public void Can_Serializable_DateTimeOffset_Field() //DataContractSerializer.Instance.Parse(model).Print(); var json = JsonSerializer.SerializeToString(model); - Assert.That(json, Is.StringContaining("\"TimeSpan\":\"PT0S\"")); + Assert.That(json, Does.Contain("\"TimeSpan\":\"PT0S\"")); var fromJson = json.FromJson(); @@ -52,7 +52,7 @@ public void Can_serialize_TimeSpan_field() var model = new SampleModel { Id = 1, TimeSpan = period }; var json = JsonSerializer.SerializeToString(model); - Assert.That(json, Is.StringContaining("\"TimeSpan\":\"P3652D\"")); + Assert.That(json, Does.Contain("\"TimeSpan\":\"P3652D\"")); //Behaviour of .NET's BCL classes //JsonDataContractSerializer.Instance.SerializeToString(model).Print(); @@ -64,41 +64,38 @@ public void Can_serialize_TimeSpan_field() [Test] public void Can_serialize_TimeSpan_field_with_StandardTimeSpanFormat() { - var period = TimeSpan.FromSeconds(70); - - JsConfig.TimeSpanHandler = JsonTimeSpanHandler.StandardFormat; - - var model = new SampleModel { Id = 1, TimeSpan = period }; - var json = JsonSerializer.SerializeToString(model); - Assert.That(json, Is.StringContaining("\"TimeSpan\":\"00:01:10\"")); - - JsConfig.TimeSpanHandler = JsonTimeSpanHandler.DurationFormat; //Revert so no other tests are affected + using (JsConfig.With(new Config { TimeSpanHandler = TimeSpanHandler.StandardFormat })) + { + var period = TimeSpan.FromSeconds(70); + + var model = new SampleModel { Id = 1, TimeSpan = period }; + var json = JsonSerializer.SerializeToString(model); + Assert.That(json, Does.Contain("\"TimeSpan\":\"00:01:10\"")); + } } [Test] public void Can_serialize_NullableTimeSpan_field_with_StandardTimeSpanFormat() { - var period = TimeSpan.FromSeconds(70); - - JsConfig.TimeSpanHandler = JsonTimeSpanHandler.StandardFormat; - - var model = new NullableSampleModel { Id = 1, TimeSpan = period }; - var json = JsonSerializer.SerializeToString(model); - Assert.That(json, Is.StringContaining("\"TimeSpan\":\"00:01:10\"")); - - JsConfig.TimeSpanHandler = JsonTimeSpanHandler.DurationFormat; //Revert so no other tests are affected + using (JsConfig.With(new Config { TimeSpanHandler = TimeSpanHandler.StandardFormat })) + { + var period = TimeSpan.FromSeconds(70); + + var model = new NullableSampleModel { Id = 1, TimeSpan = period }; + var json = JsonSerializer.SerializeToString(model); + Assert.That(json, Does.Contain("\"TimeSpan\":\"00:01:10\"")); + } } [Test] public void Can_serialize_NullTimeSpan_field_with_StandardTimeSpanFormat() { - JsConfig.TimeSpanHandler = JsonTimeSpanHandler.StandardFormat; - - var model = new NullableSampleModel { Id = 1 }; - var json = JsonSerializer.SerializeToString(model); - Assert.That(json, Is.Not.StringContaining("\"TimeSpan\"")); - - JsConfig.TimeSpanHandler = JsonTimeSpanHandler.DurationFormat; //Revert so no other tests are affected + using (JsConfig.With(new Config { TimeSpanHandler = TimeSpanHandler.StandardFormat })) + { + var model = new NullableSampleModel { Id = 1 }; + var json = JsonSerializer.SerializeToString(model); + Assert.That(json, Does.Not.Contain("\"TimeSpan\"")); + } } public class SampleModel diff --git a/tests/ServiceStack.Text.Tests/DdnContentIngestTests.cs b/tests/ServiceStack.Text.Tests/DdnContentIngestTests.cs deleted file mode 100644 index 78ebc169c..000000000 --- a/tests/ServiceStack.Text.Tests/DdnContentIngestTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Generic; -using NUnit.Framework; -using ServiceStack.Common.Tests.Models; - -namespace ServiceStack.Text.Tests -{ - [TestFixture] - public class DdnContentIngestTests - : TestBase - { - [Test] - public void Can_serialize_ReleaseSet() - { - const string dtoString = @"{BatchId:1b842f15293d4cfba8a25b4d8b10798b,BatchName:2000000916259,BatchSequence:2000000916259,BatchPath:transcoded-i:///universal/2000000916259,Partial:False,Release:{Delete:False,ExternalRef:00028946767825,ExternalUrn:urn:album:universal:00028946767825,SupplierKeyName:universal,Name:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,NameExVersion:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,ReleaseType:Album,LabelText:Decca Music Group Ltd.,ArtistText:""Orchestra [Orchestra]"",UpcEan:00028946767825,SetCount:1,TrackCount:23,DurationMs:3594000,ContinuousMix:False,ExplicitType:NotExplicit,ReleaseDate:2002-03-11,Copyright:2001 Decca Music Group Limited,Genres:[Soundtrack],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingman [Conductor]"",Role:Artist,ExternalRef:nickingmanconductor,ExternalUrn:urn:artist:universal:nickingmanconductor},{Name:""Nick Ingman [Conductor]"",Role:Conductor,ExternalRef:nickingmanconductor,ExternalUrn:urn:artist:universal:nickingmanconductor},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:deccamusicgroupltd,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickingmanconductor]},ExternalTrackUrns:{Partial:False,Items:[urn:track:universal:00028946767825/gbbbb2014301,urn:track:universal:00028946767825/gbbbb2014302,urn:track:universal:00028946767825/gbbbb2014303,urn:track:universal:00028946767825/gbbbb2014304,urn:track:universal:00028946767825/gbbbb2014305,urn:track:universal:00028946767825/gbbbb2014306,urn:track:universal:00028946767825/gbbbb2014307,urn:track:universal:00028946767825/gbbbb2014308,urn:track:universal:00028946767825/gbbbb2014309,urn:track:universal:00028946767825/gbbbb2014310,urn:track:universal:00028946767825/gbbbb2014311,urn:track:universal:00028946767825/gbbbb2014312,urn:track:universal:00028946767825/gbbbb2014313,urn:track:universal:00028946767825/gbbbb2014314,urn:track:universal:00028946767825/gbbbb2014315,urn:track:universal:00028946767825/gbbbb2014316,urn:track:universal:00028946767825/gbbbb2014317,urn:track:universal:00028946767825/gbbbb2014318,urn:track:universal:00028946767825/gbbbb2014319,urn:track:universal:00028946767825/gbbbb2014320,urn:track:universal:00028946767825/gbbbb2014321,urn:track:universal:00028946767825/gbbbb2014322,urn:track:universal:00028946767825/gbbbb2014323]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TP}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},Labels:[{ExternalRef:deccamusicgroupltd,ExternalUrn:urn:label:universal:deccamusicgroupltd,SupplierKeyName:universal,Name:Decca Music Group Ltd.},{ExternalRef:decca,ExternalUrn:urn:label:universal:decca,SupplierKeyName:universal,Name:Decca}],Artists:[{ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra,SupplierKeyName:universal,Name:""Orchestra [Orchestra]""},{ExternalRef:nickingmanconductor,ExternalUrn:urn:artist:universal:nickingmanconductor,SupplierKeyName:universal,Name:""Nick Ingman [Conductor]""},{ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor,SupplierKeyName:universal,Name:""Nick Ingham [Conductor]""},{ExternalRef:lascalaensemble,ExternalUrn:urn:artist:universal:lascalaensemble,SupplierKeyName:universal,Name:""La Scala [Ensemble]""},{ExternalRef:nucciosianovocalist,ExternalUrn:urn:artist:universal:nucciosianovocalist,SupplierKeyName:universal,Name:""Nuccio Siano [Vocalist]""},{ExternalRef:enricocarusotenor,ExternalUrn:urn:artist:universal:enricocarusotenor,SupplierKeyName:universal,Name:""Enrico Caruso [Tenor]""},{ExternalRef:russellwatsontenor,ExternalUrn:urn:artist:universal:russellwatsontenor,SupplierKeyName:universal,Name:""Russell Watson [Tenor]""}],Tracks:[{Delete:False,ExternalRef:00028946767825/gbbbb2014301,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014301,SupplierKeyName:universal,Name:""Pelagia's Song [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Pelagia's Song,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014301,DiscNumber:1,TrackNumber:1,DurationMs:251000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/ed7d91022e3ab950bfebe3a29eb6a911d0f543d63e030f9ec56d1bec612a1a6c/00028946767825/gbbbb2014301/ogg/160/251000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/ed7d91022e3ab950bfebe3a29eb6a911d0f543d63e030f9ec56d1bec612a1a6c/00028946767825/gbbbb2014301/mp3/320/251000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014302,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014302,SupplierKeyName:universal,Name:""The Recruiting Officer [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:The Recruiting Officer,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014302,DiscNumber:1,TrackNumber:2,DurationMs:90000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT2C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/363d64260615da5bcce091feb3ed15177e6e4a55eab80236075821c42913aecb/00028946767825/gbbbb2014302/ogg/160/90000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/363d64260615da5bcce091feb3ed15177e6e4a55eab80236075821c42913aecb/00028946767825/gbbbb2014302/mp3/320/90000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014303,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014303,SupplierKeyName:universal,Name:""To Albania [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:To Albania,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014303,DiscNumber:1,TrackNumber:3,DurationMs:121000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/2c0eb2b8c383f9099572e854117f8ad475c1b98c85ab248e67e9e442f925868f/00028946767825/gbbbb2014303/ogg/160/121000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/2c0eb2b8c383f9099572e854117f8ad475c1b98c85ab248e67e9e442f925868f/00028946767825/gbbbb2014303/mp3/320/121000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014304,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014304,SupplierKeyName:universal,Name:""Horgota Beach [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Horgota Beach,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014304,DiscNumber:1,TrackNumber:4,DurationMs:110000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT2C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/341b7ebe9260ce938f002b77d68c5b0c0ff1e4910a048d967e3d6efddac15ed8/00028946767825/gbbbb2014304/ogg/160/110000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/341b7ebe9260ce938f002b77d68c5b0c0ff1e4910a048d967e3d6efddac15ed8/00028946767825/gbbbb2014304/mp3/320/110000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014305,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014305,SupplierKeyName:universal,Name:""Albania [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Albania,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014305,DiscNumber:1,TrackNumber:5,DurationMs:128000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/32458fb87475d1a7b33580ee00a5dc42379499cdb591252114e3e63b6254e25a/00028946767825/gbbbb2014305/ogg/160/128000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/32458fb87475d1a7b33580ee00a5dc42379499cdb591252114e3e63b6254e25a/00028946767825/gbbbb2014305/mp3/320/128000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014306,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014306,SupplierKeyName:universal,Name:""The Arrival of the Italians [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:The Arrival of the Italians,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014306,DiscNumber:1,TrackNumber:6,DurationMs:155000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/86b0d012918868d914eb0916e26524d174d17be54f1daed66085372c70ecb0db/00028946767825/gbbbb2014306/ogg/160/155000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/86b0d012918868d914eb0916e26524d174d17be54f1daed66085372c70ecb0db/00028946767825/gbbbb2014306/mp3/320/155000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014307,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014307,SupplierKeyName:universal,Name:""Act 3 [Rigoletto} - Lili Marlene"",NameExVersion:""Act 3 [Rigoletto} - Lili Marlene"",LabelText:Decca,ArtistText:""La Scala [Ensemble]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014307,DiscNumber:1,TrackNumber:7,DurationMs:188000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""La Scala [Ensemble]"",Role:Main Artist,ExternalRef:lascalaensemble,ExternalUrn:urn:artist:universal:lascalaensemble},{Name:""Nuccio Siano [Vocalist]"",Role:Artist,ExternalRef:nucciosianovocalist,ExternalUrn:urn:artist:universal:nucciosianovocalist},{Name:""Nuccio Siano [Vocalist]"",Role:Performer,ExternalRef:nucciosianovocalist,ExternalUrn:urn:artist:universal:nucciosianovocalist},{Name:""Verdi, Giuseppe [Composer]"",Role:Composer,ExternalRef:verdigiuseppecomposer,ExternalUrn:urn:artist:universal:verdigiuseppecomposer},{Name:""Piave, Francesco Maria [Author]"",Role:Composer,ExternalRef:piavefrancescomariaauthor,ExternalUrn:urn:artist:universal:piavefrancescomariaauthor},{Name:Verdi,ExternalRef:verdi,ExternalUrn:urn:artist:universal:verdi},{Name:""La Scala [Ensemble]"",Role:Orchestra,ExternalRef:lascalaensemble,ExternalUrn:urn:artist:universal:lascalaensemble}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:lascalaensemble,urn:artist:universal:nucciosianovocalist]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc/00028946767825/gbbbb2014307/ogg/160/188000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc/00028946767825/gbbbb2014307/mp3/320/188000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014308,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014308,SupplierKeyName:universal,Name:""The Tango [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:The Tango,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014308,DiscNumber:1,TrackNumber:8,DurationMs:134000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/054813892448ba3aa71066c53f36f12caa8cb2a63eb00491718ef6b8c7ad148e/00028946767825/gbbbb2014308/ogg/160/134000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/054813892448ba3aa71066c53f36f12caa8cb2a63eb00491718ef6b8c7ad148e/00028946767825/gbbbb2014308/mp3/320/134000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014309,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014309,SupplierKeyName:universal,Name:Santa Lucia,NameExVersion:Santa Lucia,LabelText:Decca,ArtistText:""Enrico Caruso [Tenor]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014309,DiscNumber:1,TrackNumber:9,DurationMs:204000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Enrico Caruso [Tenor]"",Role:Main Artist,ExternalRef:enricocarusotenor,ExternalUrn:urn:artist:universal:enricocarusotenor},{Name:""La Scala [Ensemble]"",Role:Artist,ExternalRef:lascalaensemble,ExternalUrn:urn:artist:universal:lascalaensemble},{Name:""Enrico Caruso [Tenor]"",Role:Performer,ExternalRef:enricocarusotenor,ExternalUrn:urn:artist:universal:enricocarusotenor},{Name:""Cottrau, Teodoro [Composer]"",Role:Composer,ExternalRef:cottrauteodorocomposer,ExternalUrn:urn:artist:universal:cottrauteodorocomposer},{Name:""Cossovich, J. [Author]"",Role:Composer,ExternalRef:cossovichjauthor,ExternalUrn:urn:artist:universal:cossovichjauthor},{Name:Cottrau,ExternalRef:cottrau,ExternalUrn:urn:artist:universal:cottrau},{Name:""La Scala [Ensemble]"",Role:Orchestra,ExternalRef:lascalaensemble,ExternalUrn:urn:artist:universal:lascalaensemble}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:enricocarusotenor,urn:artist:universal:lascalaensemble]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/1e49670eabb34dfbc193ef939f5f6680edde526cedcab3ea427449922da8434c/00028946767825/gbbbb2014309/ogg/160/204000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/1e49670eabb34dfbc193ef939f5f6680edde526cedcab3ea427449922da8434c/00028946767825/gbbbb2014309/mp3/320/204000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014310,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014310,SupplierKeyName:universal,Name:""The Mandolin [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:The Mandolin,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014310,DiscNumber:1,TrackNumber:10,DurationMs:108000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT2C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/b16af358560813ef02083f5b78957e1c3f0c9c69d50e2990392c2ff5b90c4bc1/00028946767825/gbbbb2014310/ogg/160/108000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/b16af358560813ef02083f5b78957e1c3f0c9c69d50e2990392c2ff5b90c4bc1/00028946767825/gbbbb2014310/mp3/320/108000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014311,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014311,SupplierKeyName:universal,Name:""After the Dance [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:After the Dance,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014311,DiscNumber:1,TrackNumber:11,DurationMs:135000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/369b6c4a1b1baccf9753fa6987f5ecbbb0d9e85c23cddc3e2a99ef3b96a93dfb/00028946767825/gbbbb2014311/ogg/160/135000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/369b6c4a1b1baccf9753fa6987f5ecbbb0d9e85c23cddc3e2a99ef3b96a93dfb/00028946767825/gbbbb2014311/mp3/320/135000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014312,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014312,SupplierKeyName:universal,Name:""Agii Fanentes [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Agii Fanentes,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014312,DiscNumber:1,TrackNumber:12,DurationMs:95000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT2C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/6f810026cd9eaeaeb9b6fb3d2478f98503428866aba9bf745ca26de718cd7fe4/00028946767825/gbbbb2014312/ogg/160/95000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/6f810026cd9eaeaeb9b6fb3d2478f98503428866aba9bf745ca26de718cd7fe4/00028946767825/gbbbb2014312/mp3/320/95000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014313,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014313,SupplierKeyName:universal,Name:""Lemoni [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Lemoni,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014313,DiscNumber:1,TrackNumber:13,DurationMs:85000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT2C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/06f1ae2753d3312d480b046508dea5eac5d1ef2924013ae1c6dda7c1b62910f7/00028946767825/gbbbb2014313/ogg/160/85000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/06f1ae2753d3312d480b046508dea5eac5d1ef2924013ae1c6dda7c1b62910f7/00028946767825/gbbbb2014313/mp3/320/85000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014314,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014314,SupplierKeyName:universal,Name:""The Guitar [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:The Guitar,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014314,DiscNumber:1,TrackNumber:14,DurationMs:105000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT2C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/cbc8034b4e1ebdf114c3578bc4662b03ec5c4a2418fd95c8c4e13d563100d5e8/00028946767825/gbbbb2014314/ogg/160/105000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/cbc8034b4e1ebdf114c3578bc4662b03ec5c4a2418fd95c8c4e13d563100d5e8/00028946767825/gbbbb2014314/mp3/320/105000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014315,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014315,SupplierKeyName:universal,Name:""Surrender [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Surrender,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014315,DiscNumber:1,TrackNumber:15,DurationMs:136000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/608e17488e8ca29f3eefffe2c54bedc90034399e4ad9a3a433e4f86561d442e7/00028946767825/gbbbb2014315/ogg/160/136000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/608e17488e8ca29f3eefffe2c54bedc90034399e4ad9a3a433e4f86561d442e7/00028946767825/gbbbb2014315/mp3/320/136000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014316,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014316,SupplierKeyName:universal,Name:""On the Jetty [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:On the Jetty,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014316,DiscNumber:1,TrackNumber:16,DurationMs:127000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/5d30fb4ea9227ec9b6082943f4920f70bd711248453f4460e6c4dc90a3ec372d/00028946767825/gbbbb2014316/ogg/160/127000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/5d30fb4ea9227ec9b6082943f4920f70bd711248453f4460e6c4dc90a3ec372d/00028946767825/gbbbb2014316/mp3/320/127000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014317,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014317,SupplierKeyName:universal,Name:""The Battle [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:The Battle,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014317,DiscNumber:1,TrackNumber:17,DurationMs:183000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/817623ce54055365022077593f42e1daf69a682f3d80d7f868e429e6e73e0bbf/00028946767825/gbbbb2014317/ogg/160/183000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/817623ce54055365022077593f42e1daf69a682f3d80d7f868e429e6e73e0bbf/00028946767825/gbbbb2014317/mp3/320/183000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014318,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014318,SupplierKeyName:universal,Name:""Senza di te [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Senza di te,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Russell Watson [Tenor]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014318,DiscNumber:1,TrackNumber:18,DurationMs:181000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Russell Watson [Tenor]"",Role:Main Artist,ExternalRef:russellwatsontenor,ExternalUrn:urn:artist:universal:russellwatsontenor},{Name:""Russell Watson [Tenor]"",Role:Performer,ExternalRef:russellwatsontenor,ExternalUrn:urn:artist:universal:russellwatsontenor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:russellwatsontenor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/220c7d1e376121620238b34693088e9c665fbac4956ea35cb0ca95001c99ed50/00028946767825/gbbbb2014318/ogg/160/181000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/220c7d1e376121620238b34693088e9c665fbac4956ea35cb0ca95001c99ed50/00028946767825/gbbbb2014318/mp3/320/181000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014319,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014319,SupplierKeyName:universal,Name:""Escape from the Island [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Escape from the Island,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014319,DiscNumber:1,TrackNumber:19,DurationMs:103000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT2C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/ea7b1658d8503b553e8ef1caca41e544275338c27bf86079101278046e32ed1f/00028946767825/gbbbb2014319/ogg/160/103000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/ea7b1658d8503b553e8ef1caca41e544275338c27bf86079101278046e32ed1f/00028946767825/gbbbb2014319/mp3/320/103000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014320,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014320,SupplierKeyName:universal,Name:""The Aftermath [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:The Aftermath,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014320,DiscNumber:1,TrackNumber:20,DurationMs:407000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/e37547579793faf5d81db478ffc83c2df9f4ba479d69c6e3566a5fbc1abbad5d/00028946767825/gbbbb2014320/ogg/160/407000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/e37547579793faf5d81db478ffc83c2df9f4ba479d69c6e3566a5fbc1abbad5d/00028946767825/gbbbb2014320/mp3/320/407000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014321,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014321,SupplierKeyName:universal,Name:""Iannis' Letter [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Iannis' Letter,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014321,DiscNumber:1,TrackNumber:21,DurationMs:97000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT2C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/9a3d948a640fbebf5700699aab34cf2f6709f20042c63a6580a99b7a3b67ab94/00028946767825/gbbbb2014321/ogg/160/97000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/9a3d948a640fbebf5700699aab34cf2f6709f20042c63a6580a99b7a3b67ab94/00028946767825/gbbbb2014321/mp3/320/97000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014322,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014322,SupplierKeyName:universal,Name:""Ricordo Ancor (Pelagia's Song) [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Ricordo Ancor (Pelagia's Song),NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Russell Watson [Tenor]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014322,DiscNumber:1,TrackNumber:22,DurationMs:225000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Russell Watson [Tenor]"",Role:Main Artist,ExternalRef:russellwatsontenor,ExternalUrn:urn:artist:universal:russellwatsontenor},{Name:""Russell Watson [Tenor]"",Role:Performer,ExternalRef:russellwatsontenor,ExternalUrn:urn:artist:universal:russellwatsontenor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:russellwatsontenor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/80f169143230bd5f69282b98a4f1d176883c33776e42a3510a09d2082057548e/00028946767825/gbbbb2014322/ogg/160/225000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/80f169143230bd5f69282b98a4f1d176883c33776e42a3510a09d2082057548e/00028946767825/gbbbb2014322/mp3/320/225000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]},{Delete:False,ExternalRef:00028946767825/gbbbb2014323,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014323,SupplierKeyName:universal,Name:""Reunion [Captain Corelli's Mandolin - Original Motion Picture Soundtrack]"",NameExVersion:Reunion,NameVersion:Captain Corelli's Mandolin - Original Motion Picture Soundtrack,LabelText:Decca,ArtistText:""Orchestra [Orchestra]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014323,DiscNumber:1,TrackNumber:23,DurationMs:226000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""Orchestra [Orchestra]"",Role:Main Artist,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra},{Name:""Nick Ingham [Conductor]"",Role:Artist,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Nick Ingham [Conductor]"",Role:Conductor,ExternalRef:nickinghamconductor,ExternalUrn:urn:artist:universal:nickinghamconductor},{Name:""Warbeck, Stephen [Composer]"",Role:Composer,ExternalRef:warbeckstephencomposer,ExternalUrn:urn:artist:universal:warbeckstephencomposer},{Name:Warbeck,ExternalRef:warbeck,ExternalUrn:urn:artist:universal:warbeck},{Name:""Orchestra [Orchestra]"",Role:Orchestra,ExternalRef:orchestraorchestra,ExternalUrn:urn:artist:universal:orchestraorchestra}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:orchestraorchestra,urn:artist:universal:nickinghamconductor]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/56b82ef9e60ae760e60d56f5c9dde28ccb5ea916c0663271b2e6a22f27e63788/00028946767825/gbbbb2014323/ogg/160/226000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/56b82ef9e60ae760e60d56f5c9dde28ccb5ea916c0663271b2e6a22f27e63788/00028946767825/gbbbb2014323/mp3/320/226000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]}],Assets:[{ExternalRef:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4,SupplierKeyName:universal,AssetType:MasterCoverArt,ExternalOwnerUrn:urn:album:universal:00028946767825,FileName:UMG_cvrart_00028946767825_01_RGB300_800x793_1257915.jpg,FileExtension:jpg,FileSizeBytes:77661,MasterSha256Checksum:3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4,Width:793,Height:800,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_cvrart_00028946767825_01_RGB300_800x793_1257915.jpg},{ExternalRef:trackpreview/ed7d91022e3ab950bfebe3a29eb6a911d0f543d63e030f9ec56d1bec612a1a6c/00028946767825/gbbbb2014301/ogg/160/251000,ExternalUrn:urn:asset:universal:trackpreview/ed7d91022e3ab950bfebe3a29eb6a911d0f543d63e030f9ec56d1bec612a1a6c/00028946767825/gbbbb2014301/ogg/160/251000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014301,FileName:UMG_audtrk_00028946767825_01_001_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:4624528,MasterSha256Checksum:ed7d91022e3ab950bfebe3a29eb6a911d0f543d63e030f9ec56d1bec612a1a6c,DurationMs:251000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_001_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/ed7d91022e3ab950bfebe3a29eb6a911d0f543d63e030f9ec56d1bec612a1a6c/00028946767825/gbbbb2014301/mp3/320/251000,ExternalUrn:urn:asset:universal:trackproduct/ed7d91022e3ab950bfebe3a29eb6a911d0f543d63e030f9ec56d1bec612a1a6c/00028946767825/gbbbb2014301/mp3/320/251000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014301,FileName:UMG_audtrk_00028946767825_01_001_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:7167050,MasterSha256Checksum:ed7d91022e3ab950bfebe3a29eb6a911d0f543d63e030f9ec56d1bec612a1a6c,DurationMs:251000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_001_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/363d64260615da5bcce091feb3ed15177e6e4a55eab80236075821c42913aecb/00028946767825/gbbbb2014302/ogg/160/90000,ExternalUrn:urn:asset:universal:trackpreview/363d64260615da5bcce091feb3ed15177e6e4a55eab80236075821c42913aecb/00028946767825/gbbbb2014302/ogg/160/90000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014302,FileName:UMG_audtrk_00028946767825_01_002_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:1625235,MasterSha256Checksum:363d64260615da5bcce091feb3ed15177e6e4a55eab80236075821c42913aecb,DurationMs:90000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_002_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/363d64260615da5bcce091feb3ed15177e6e4a55eab80236075821c42913aecb/00028946767825/gbbbb2014302/mp3/320/90000,ExternalUrn:urn:asset:universal:trackproduct/363d64260615da5bcce091feb3ed15177e6e4a55eab80236075821c42913aecb/00028946767825/gbbbb2014302/mp3/320/90000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014302,FileName:UMG_audtrk_00028946767825_01_002_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:2687974,MasterSha256Checksum:363d64260615da5bcce091feb3ed15177e6e4a55eab80236075821c42913aecb,DurationMs:90000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_002_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/2c0eb2b8c383f9099572e854117f8ad475c1b98c85ab248e67e9e442f925868f/00028946767825/gbbbb2014303/ogg/160/121000,ExternalUrn:urn:asset:universal:trackpreview/2c0eb2b8c383f9099572e854117f8ad475c1b98c85ab248e67e9e442f925868f/00028946767825/gbbbb2014303/ogg/160/121000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014303,FileName:UMG_audtrk_00028946767825_01_003_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:2177905,MasterSha256Checksum:2c0eb2b8c383f9099572e854117f8ad475c1b98c85ab248e67e9e442f925868f,DurationMs:121000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_003_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/2c0eb2b8c383f9099572e854117f8ad475c1b98c85ab248e67e9e442f925868f/00028946767825/gbbbb2014303/mp3/320/121000,ExternalUrn:urn:asset:universal:trackproduct/2c0eb2b8c383f9099572e854117f8ad475c1b98c85ab248e67e9e442f925868f/00028946767825/gbbbb2014303/mp3/320/121000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014303,FileName:UMG_audtrk_00028946767825_01_003_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:3662748,MasterSha256Checksum:2c0eb2b8c383f9099572e854117f8ad475c1b98c85ab248e67e9e442f925868f,DurationMs:121000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_003_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/341b7ebe9260ce938f002b77d68c5b0c0ff1e4910a048d967e3d6efddac15ed8/00028946767825/gbbbb2014304/ogg/160/110000,ExternalUrn:urn:asset:universal:trackpreview/341b7ebe9260ce938f002b77d68c5b0c0ff1e4910a048d967e3d6efddac15ed8/00028946767825/gbbbb2014304/ogg/160/110000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014304,FileName:UMG_audtrk_00028946767825_01_004_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:1924641,MasterSha256Checksum:341b7ebe9260ce938f002b77d68c5b0c0ff1e4910a048d967e3d6efddac15ed8,DurationMs:110000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_004_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/341b7ebe9260ce938f002b77d68c5b0c0ff1e4910a048d967e3d6efddac15ed8/00028946767825/gbbbb2014304/mp3/320/110000,ExternalUrn:urn:asset:universal:trackproduct/341b7ebe9260ce938f002b77d68c5b0c0ff1e4910a048d967e3d6efddac15ed8/00028946767825/gbbbb2014304/mp3/320/110000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014304,FileName:UMG_audtrk_00028946767825_01_004_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:3111303,MasterSha256Checksum:341b7ebe9260ce938f002b77d68c5b0c0ff1e4910a048d967e3d6efddac15ed8,DurationMs:110000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_004_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/32458fb87475d1a7b33580ee00a5dc42379499cdb591252114e3e63b6254e25a/00028946767825/gbbbb2014305/ogg/160/128000,ExternalUrn:urn:asset:universal:trackpreview/32458fb87475d1a7b33580ee00a5dc42379499cdb591252114e3e63b6254e25a/00028946767825/gbbbb2014305/ogg/160/128000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014305,FileName:UMG_audtrk_00028946767825_01_005_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:2316597,MasterSha256Checksum:32458fb87475d1a7b33580ee00a5dc42379499cdb591252114e3e63b6254e25a,DurationMs:128000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_005_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/32458fb87475d1a7b33580ee00a5dc42379499cdb591252114e3e63b6254e25a/00028946767825/gbbbb2014305/mp3/320/128000,ExternalUrn:urn:asset:universal:trackproduct/32458fb87475d1a7b33580ee00a5dc42379499cdb591252114e3e63b6254e25a/00028946767825/gbbbb2014305/mp3/320/128000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014305,FileName:UMG_audtrk_00028946767825_01_005_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:3960289,MasterSha256Checksum:32458fb87475d1a7b33580ee00a5dc42379499cdb591252114e3e63b6254e25a,DurationMs:128000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_005_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/86b0d012918868d914eb0916e26524d174d17be54f1daed66085372c70ecb0db/00028946767825/gbbbb2014306/ogg/160/155000,ExternalUrn:urn:asset:universal:trackpreview/86b0d012918868d914eb0916e26524d174d17be54f1daed66085372c70ecb0db/00028946767825/gbbbb2014306/ogg/160/155000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014306,FileName:UMG_audtrk_00028946767825_01_006_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:3055351,MasterSha256Checksum:86b0d012918868d914eb0916e26524d174d17be54f1daed66085372c70ecb0db,DurationMs:155000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_006_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/86b0d012918868d914eb0916e26524d174d17be54f1daed66085372c70ecb0db/00028946767825/gbbbb2014306/mp3/320/155000,ExternalUrn:urn:asset:universal:trackproduct/86b0d012918868d914eb0916e26524d174d17be54f1daed66085372c70ecb0db/00028946767825/gbbbb2014306/mp3/320/155000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014306,FileName:UMG_audtrk_00028946767825_01_006_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:4491772,MasterSha256Checksum:86b0d012918868d914eb0916e26524d174d17be54f1daed66085372c70ecb0db,DurationMs:155000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_006_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc/00028946767825/gbbbb2014307/ogg/160/188000,ExternalUrn:urn:asset:universal:trackpreview/9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc/00028946767825/gbbbb2014307/ogg/160/188000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014307,FileName:UMG_audtrk_00028946767825_01_007_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:3224900,MasterSha256Checksum:9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc,DurationMs:188000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_007_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc/00028946767825/gbbbb2014307/mp3/320/188000,ExternalUrn:urn:asset:universal:trackproduct/9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc/00028946767825/gbbbb2014307/mp3/320/188000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014307,FileName:UMG_audtrk_00028946767825_01_007_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:5469721,MasterSha256Checksum:9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc,DurationMs:188000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_007_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/054813892448ba3aa71066c53f36f12caa8cb2a63eb00491718ef6b8c7ad148e/00028946767825/gbbbb2014308/ogg/160/134000,ExternalUrn:urn:asset:universal:trackpreview/054813892448ba3aa71066c53f36f12caa8cb2a63eb00491718ef6b8c7ad148e/00028946767825/gbbbb2014308/ogg/160/134000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014308,FileName:UMG_audtrk_00028946767825_01_008_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:2588580,MasterSha256Checksum:054813892448ba3aa71066c53f36f12caa8cb2a63eb00491718ef6b8c7ad148e,DurationMs:134000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_008_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/054813892448ba3aa71066c53f36f12caa8cb2a63eb00491718ef6b8c7ad148e/00028946767825/gbbbb2014308/mp3/320/134000,ExternalUrn:urn:asset:universal:trackproduct/054813892448ba3aa71066c53f36f12caa8cb2a63eb00491718ef6b8c7ad148e/00028946767825/gbbbb2014308/mp3/320/134000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014308,FileName:UMG_audtrk_00028946767825_01_008_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:3924455,MasterSha256Checksum:054813892448ba3aa71066c53f36f12caa8cb2a63eb00491718ef6b8c7ad148e,DurationMs:134000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_008_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/1e49670eabb34dfbc193ef939f5f6680edde526cedcab3ea427449922da8434c/00028946767825/gbbbb2014309/ogg/160/204000,ExternalUrn:urn:asset:universal:trackpreview/1e49670eabb34dfbc193ef939f5f6680edde526cedcab3ea427449922da8434c/00028946767825/gbbbb2014309/ogg/160/204000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014309,FileName:UMG_audtrk_00028946767825_01_009_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:2881331,MasterSha256Checksum:1e49670eabb34dfbc193ef939f5f6680edde526cedcab3ea427449922da8434c,DurationMs:204000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_009_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/1e49670eabb34dfbc193ef939f5f6680edde526cedcab3ea427449922da8434c/00028946767825/gbbbb2014309/mp3/320/204000,ExternalUrn:urn:asset:universal:trackproduct/1e49670eabb34dfbc193ef939f5f6680edde526cedcab3ea427449922da8434c/00028946767825/gbbbb2014309/mp3/320/204000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014309,FileName:UMG_audtrk_00028946767825_01_009_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:5946636,MasterSha256Checksum:1e49670eabb34dfbc193ef939f5f6680edde526cedcab3ea427449922da8434c,DurationMs:204000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_009_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/b16af358560813ef02083f5b78957e1c3f0c9c69d50e2990392c2ff5b90c4bc1/00028946767825/gbbbb2014310/ogg/160/108000,ExternalUrn:urn:asset:universal:trackpreview/b16af358560813ef02083f5b78957e1c3f0c9c69d50e2990392c2ff5b90c4bc1/00028946767825/gbbbb2014310/ogg/160/108000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014310,FileName:UMG_audtrk_00028946767825_01_010_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:2013656,MasterSha256Checksum:b16af358560813ef02083f5b78957e1c3f0c9c69d50e2990392c2ff5b90c4bc1,DurationMs:108000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_010_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/b16af358560813ef02083f5b78957e1c3f0c9c69d50e2990392c2ff5b90c4bc1/00028946767825/gbbbb2014310/mp3/320/108000,ExternalUrn:urn:asset:universal:trackproduct/b16af358560813ef02083f5b78957e1c3f0c9c69d50e2990392c2ff5b90c4bc1/00028946767825/gbbbb2014310/mp3/320/108000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014310,FileName:UMG_audtrk_00028946767825_01_010_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:3246169,MasterSha256Checksum:b16af358560813ef02083f5b78957e1c3f0c9c69d50e2990392c2ff5b90c4bc1,DurationMs:108000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_010_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/369b6c4a1b1baccf9753fa6987f5ecbbb0d9e85c23cddc3e2a99ef3b96a93dfb/00028946767825/gbbbb2014311/ogg/160/135000,ExternalUrn:urn:asset:universal:trackpreview/369b6c4a1b1baccf9753fa6987f5ecbbb0d9e85c23cddc3e2a99ef3b96a93dfb/00028946767825/gbbbb2014311/ogg/160/135000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014311,FileName:UMG_audtrk_00028946767825_01_011_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:2388464,MasterSha256Checksum:369b6c4a1b1baccf9753fa6987f5ecbbb0d9e85c23cddc3e2a99ef3b96a93dfb,DurationMs:135000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_011_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/369b6c4a1b1baccf9753fa6987f5ecbbb0d9e85c23cddc3e2a99ef3b96a93dfb/00028946767825/gbbbb2014311/mp3/320/135000,ExternalUrn:urn:asset:universal:trackproduct/369b6c4a1b1baccf9753fa6987f5ecbbb0d9e85c23cddc3e2a99ef3b96a93dfb/00028946767825/gbbbb2014311/mp3/320/135000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014311,FileName:UMG_audtrk_00028946767825_01_011_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:3708865,MasterSha256Checksum:369b6c4a1b1baccf9753fa6987f5ecbbb0d9e85c23cddc3e2a99ef3b96a93dfb,DurationMs:135000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_011_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/6f810026cd9eaeaeb9b6fb3d2478f98503428866aba9bf745ca26de718cd7fe4/00028946767825/gbbbb2014312/ogg/160/95000,ExternalUrn:urn:asset:universal:trackpreview/6f810026cd9eaeaeb9b6fb3d2478f98503428866aba9bf745ca26de718cd7fe4/00028946767825/gbbbb2014312/ogg/160/95000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014312,FileName:UMG_audtrk_00028946767825_01_012_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:1541218,MasterSha256Checksum:6f810026cd9eaeaeb9b6fb3d2478f98503428866aba9bf745ca26de718cd7fe4,DurationMs:95000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_012_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/6f810026cd9eaeaeb9b6fb3d2478f98503428866aba9bf745ca26de718cd7fe4/00028946767825/gbbbb2014312/mp3/320/95000,ExternalUrn:urn:asset:universal:trackproduct/6f810026cd9eaeaeb9b6fb3d2478f98503428866aba9bf745ca26de718cd7fe4/00028946767825/gbbbb2014312/mp3/320/95000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014312,FileName:UMG_audtrk_00028946767825_01_012_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:2670657,MasterSha256Checksum:6f810026cd9eaeaeb9b6fb3d2478f98503428866aba9bf745ca26de718cd7fe4,DurationMs:95000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_012_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/06f1ae2753d3312d480b046508dea5eac5d1ef2924013ae1c6dda7c1b62910f7/00028946767825/gbbbb2014313/ogg/160/85000,ExternalUrn:urn:asset:universal:trackpreview/06f1ae2753d3312d480b046508dea5eac5d1ef2924013ae1c6dda7c1b62910f7/00028946767825/gbbbb2014313/ogg/160/85000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014313,FileName:UMG_audtrk_00028946767825_01_013_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:1568808,MasterSha256Checksum:06f1ae2753d3312d480b046508dea5eac5d1ef2924013ae1c6dda7c1b62910f7,DurationMs:85000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_013_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/06f1ae2753d3312d480b046508dea5eac5d1ef2924013ae1c6dda7c1b62910f7/00028946767825/gbbbb2014313/mp3/320/85000,ExternalUrn:urn:asset:universal:trackproduct/06f1ae2753d3312d480b046508dea5eac5d1ef2924013ae1c6dda7c1b62910f7/00028946767825/gbbbb2014313/mp3/320/85000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014313,FileName:UMG_audtrk_00028946767825_01_013_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:2447584,MasterSha256Checksum:06f1ae2753d3312d480b046508dea5eac5d1ef2924013ae1c6dda7c1b62910f7,DurationMs:85000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_013_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/cbc8034b4e1ebdf114c3578bc4662b03ec5c4a2418fd95c8c4e13d563100d5e8/00028946767825/gbbbb2014314/ogg/160/105000,ExternalUrn:urn:asset:universal:trackpreview/cbc8034b4e1ebdf114c3578bc4662b03ec5c4a2418fd95c8c4e13d563100d5e8/00028946767825/gbbbb2014314/ogg/160/105000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014314,FileName:UMG_audtrk_00028946767825_01_014_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:1520184,MasterSha256Checksum:cbc8034b4e1ebdf114c3578bc4662b03ec5c4a2418fd95c8c4e13d563100d5e8,DurationMs:105000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_014_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/cbc8034b4e1ebdf114c3578bc4662b03ec5c4a2418fd95c8c4e13d563100d5e8/00028946767825/gbbbb2014314/mp3/320/105000,ExternalUrn:urn:asset:universal:trackproduct/cbc8034b4e1ebdf114c3578bc4662b03ec5c4a2418fd95c8c4e13d563100d5e8/00028946767825/gbbbb2014314/mp3/320/105000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014314,FileName:UMG_audtrk_00028946767825_01_014_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:2821822,MasterSha256Checksum:cbc8034b4e1ebdf114c3578bc4662b03ec5c4a2418fd95c8c4e13d563100d5e8,DurationMs:105000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_014_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/608e17488e8ca29f3eefffe2c54bedc90034399e4ad9a3a433e4f86561d442e7/00028946767825/gbbbb2014315/ogg/160/136000,ExternalUrn:urn:asset:universal:trackpreview/608e17488e8ca29f3eefffe2c54bedc90034399e4ad9a3a433e4f86561d442e7/00028946767825/gbbbb2014315/ogg/160/136000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014315,FileName:UMG_audtrk_00028946767825_01_015_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:2639424,MasterSha256Checksum:608e17488e8ca29f3eefffe2c54bedc90034399e4ad9a3a433e4f86561d442e7,DurationMs:136000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_015_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/608e17488e8ca29f3eefffe2c54bedc90034399e4ad9a3a433e4f86561d442e7/00028946767825/gbbbb2014315/mp3/320/136000,ExternalUrn:urn:asset:universal:trackproduct/608e17488e8ca29f3eefffe2c54bedc90034399e4ad9a3a433e4f86561d442e7/00028946767825/gbbbb2014315/mp3/320/136000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014315,FileName:UMG_audtrk_00028946767825_01_015_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:3818998,MasterSha256Checksum:608e17488e8ca29f3eefffe2c54bedc90034399e4ad9a3a433e4f86561d442e7,DurationMs:136000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_015_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/5d30fb4ea9227ec9b6082943f4920f70bd711248453f4460e6c4dc90a3ec372d/00028946767825/gbbbb2014316/ogg/160/127000,ExternalUrn:urn:asset:universal:trackpreview/5d30fb4ea9227ec9b6082943f4920f70bd711248453f4460e6c4dc90a3ec372d/00028946767825/gbbbb2014316/ogg/160/127000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014316,FileName:UMG_audtrk_00028946767825_01_016_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:2164772,MasterSha256Checksum:5d30fb4ea9227ec9b6082943f4920f70bd711248453f4460e6c4dc90a3ec372d,DurationMs:127000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_016_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/5d30fb4ea9227ec9b6082943f4920f70bd711248453f4460e6c4dc90a3ec372d/00028946767825/gbbbb2014316/mp3/320/127000,ExternalUrn:urn:asset:universal:trackproduct/5d30fb4ea9227ec9b6082943f4920f70bd711248453f4460e6c4dc90a3ec372d/00028946767825/gbbbb2014316/mp3/320/127000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014316,FileName:UMG_audtrk_00028946767825_01_016_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:3587188,MasterSha256Checksum:5d30fb4ea9227ec9b6082943f4920f70bd711248453f4460e6c4dc90a3ec372d,DurationMs:127000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_016_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/817623ce54055365022077593f42e1daf69a682f3d80d7f868e429e6e73e0bbf/00028946767825/gbbbb2014317/ogg/160/183000,ExternalUrn:urn:asset:universal:trackpreview/817623ce54055365022077593f42e1daf69a682f3d80d7f868e429e6e73e0bbf/00028946767825/gbbbb2014317/ogg/160/183000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014317,FileName:UMG_audtrk_00028946767825_01_017_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:3166112,MasterSha256Checksum:817623ce54055365022077593f42e1daf69a682f3d80d7f868e429e6e73e0bbf,DurationMs:183000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_017_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/817623ce54055365022077593f42e1daf69a682f3d80d7f868e429e6e73e0bbf/00028946767825/gbbbb2014317/mp3/320/183000,ExternalUrn:urn:asset:universal:trackproduct/817623ce54055365022077593f42e1daf69a682f3d80d7f868e429e6e73e0bbf/00028946767825/gbbbb2014317/mp3/320/183000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014317,FileName:UMG_audtrk_00028946767825_01_017_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:5236906,MasterSha256Checksum:817623ce54055365022077593f42e1daf69a682f3d80d7f868e429e6e73e0bbf,DurationMs:183000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_017_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/220c7d1e376121620238b34693088e9c665fbac4956ea35cb0ca95001c99ed50/00028946767825/gbbbb2014318/ogg/160/181000,ExternalUrn:urn:asset:universal:trackpreview/220c7d1e376121620238b34693088e9c665fbac4956ea35cb0ca95001c99ed50/00028946767825/gbbbb2014318/ogg/160/181000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014318,FileName:UMG_audtrk_00028946767825_01_018_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:2983432,MasterSha256Checksum:220c7d1e376121620238b34693088e9c665fbac4956ea35cb0ca95001c99ed50,DurationMs:181000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_018_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/220c7d1e376121620238b34693088e9c665fbac4956ea35cb0ca95001c99ed50/00028946767825/gbbbb2014318/mp3/320/181000,ExternalUrn:urn:asset:universal:trackproduct/220c7d1e376121620238b34693088e9c665fbac4956ea35cb0ca95001c99ed50/00028946767825/gbbbb2014318/mp3/320/181000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014318,FileName:UMG_audtrk_00028946767825_01_018_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:5081258,MasterSha256Checksum:220c7d1e376121620238b34693088e9c665fbac4956ea35cb0ca95001c99ed50,DurationMs:181000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_018_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/ea7b1658d8503b553e8ef1caca41e544275338c27bf86079101278046e32ed1f/00028946767825/gbbbb2014319/ogg/160/103000,ExternalUrn:urn:asset:universal:trackpreview/ea7b1658d8503b553e8ef1caca41e544275338c27bf86079101278046e32ed1f/00028946767825/gbbbb2014319/ogg/160/103000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014319,FileName:UMG_audtrk_00028946767825_01_019_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:1761498,MasterSha256Checksum:ea7b1658d8503b553e8ef1caca41e544275338c27bf86079101278046e32ed1f,DurationMs:103000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_019_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/ea7b1658d8503b553e8ef1caca41e544275338c27bf86079101278046e32ed1f/00028946767825/gbbbb2014319/mp3/320/103000,ExternalUrn:urn:asset:universal:trackproduct/ea7b1658d8503b553e8ef1caca41e544275338c27bf86079101278046e32ed1f/00028946767825/gbbbb2014319/mp3/320/103000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014319,FileName:UMG_audtrk_00028946767825_01_019_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:3092185,MasterSha256Checksum:ea7b1658d8503b553e8ef1caca41e544275338c27bf86079101278046e32ed1f,DurationMs:103000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_019_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/e37547579793faf5d81db478ffc83c2df9f4ba479d69c6e3566a5fbc1abbad5d/00028946767825/gbbbb2014320/ogg/160/407000,ExternalUrn:urn:asset:universal:trackpreview/e37547579793faf5d81db478ffc83c2df9f4ba479d69c6e3566a5fbc1abbad5d/00028946767825/gbbbb2014320/ogg/160/407000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014320,FileName:UMG_audtrk_00028946767825_01_020_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:7343705,MasterSha256Checksum:e37547579793faf5d81db478ffc83c2df9f4ba479d69c6e3566a5fbc1abbad5d,DurationMs:407000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_020_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/e37547579793faf5d81db478ffc83c2df9f4ba479d69c6e3566a5fbc1abbad5d/00028946767825/gbbbb2014320/mp3/320/407000,ExternalUrn:urn:asset:universal:trackproduct/e37547579793faf5d81db478ffc83c2df9f4ba479d69c6e3566a5fbc1abbad5d/00028946767825/gbbbb2014320/mp3/320/407000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014320,FileName:UMG_audtrk_00028946767825_01_020_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:11912335,MasterSha256Checksum:e37547579793faf5d81db478ffc83c2df9f4ba479d69c6e3566a5fbc1abbad5d,DurationMs:407000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_020_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/9a3d948a640fbebf5700699aab34cf2f6709f20042c63a6580a99b7a3b67ab94/00028946767825/gbbbb2014321/ogg/160/97000,ExternalUrn:urn:asset:universal:trackpreview/9a3d948a640fbebf5700699aab34cf2f6709f20042c63a6580a99b7a3b67ab94/00028946767825/gbbbb2014321/ogg/160/97000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014321,FileName:UMG_audtrk_00028946767825_01_021_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:1643495,MasterSha256Checksum:9a3d948a640fbebf5700699aab34cf2f6709f20042c63a6580a99b7a3b67ab94,DurationMs:97000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_021_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/9a3d948a640fbebf5700699aab34cf2f6709f20042c63a6580a99b7a3b67ab94/00028946767825/gbbbb2014321/mp3/320/97000,ExternalUrn:urn:asset:universal:trackproduct/9a3d948a640fbebf5700699aab34cf2f6709f20042c63a6580a99b7a3b67ab94/00028946767825/gbbbb2014321/mp3/320/97000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014321,FileName:UMG_audtrk_00028946767825_01_021_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:2674597,MasterSha256Checksum:9a3d948a640fbebf5700699aab34cf2f6709f20042c63a6580a99b7a3b67ab94,DurationMs:97000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_021_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/80f169143230bd5f69282b98a4f1d176883c33776e42a3510a09d2082057548e/00028946767825/gbbbb2014322/ogg/160/225000,ExternalUrn:urn:asset:universal:trackpreview/80f169143230bd5f69282b98a4f1d176883c33776e42a3510a09d2082057548e/00028946767825/gbbbb2014322/ogg/160/225000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014322,FileName:UMG_audtrk_00028946767825_01_022_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:3970352,MasterSha256Checksum:80f169143230bd5f69282b98a4f1d176883c33776e42a3510a09d2082057548e,DurationMs:225000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_022_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/80f169143230bd5f69282b98a4f1d176883c33776e42a3510a09d2082057548e/00028946767825/gbbbb2014322/mp3/320/225000,ExternalUrn:urn:asset:universal:trackproduct/80f169143230bd5f69282b98a4f1d176883c33776e42a3510a09d2082057548e/00028946767825/gbbbb2014322/mp3/320/225000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014322,FileName:UMG_audtrk_00028946767825_01_022_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:6397747,MasterSha256Checksum:80f169143230bd5f69282b98a4f1d176883c33776e42a3510a09d2082057548e,DurationMs:225000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_022_16.flac.mflow_320.mp3},{ExternalRef:trackpreview/56b82ef9e60ae760e60d56f5c9dde28ccb5ea916c0663271b2e6a22f27e63788/00028946767825/gbbbb2014323/ogg/160/226000,ExternalUrn:urn:asset:universal:trackpreview/56b82ef9e60ae760e60d56f5c9dde28ccb5ea916c0663271b2e6a22f27e63788/00028946767825/gbbbb2014323/ogg/160/226000,SupplierKeyName:universal,AssetType:TrackPreview,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014323,FileName:UMG_audtrk_00028946767825_01_023_16.flac.mflow_160.ogg,FileExtension:ogg,FileSizeBytes:4244125,MasterSha256Checksum:56b82ef9e60ae760e60d56f5c9dde28ccb5ea916c0663271b2e6a22f27e63788,DurationMs:226000,BitRateKbps:160,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_023_16.flac.mflow_160.ogg},{ExternalRef:trackproduct/56b82ef9e60ae760e60d56f5c9dde28ccb5ea916c0663271b2e6a22f27e63788/00028946767825/gbbbb2014323/mp3/320/226000,ExternalUrn:urn:asset:universal:trackproduct/56b82ef9e60ae760e60d56f5c9dde28ccb5ea916c0663271b2e6a22f27e63788/00028946767825/gbbbb2014323/mp3/320/226000,SupplierKeyName:universal,AssetType:TrackProduct,ExternalOwnerUrn:urn:track:universal:00028946767825/gbbbb2014323,FileName:UMG_audtrk_00028946767825_01_023_16.flac.mflow_320.mp3,FileExtension:mp3,FileSizeBytes:6409539,MasterSha256Checksum:56b82ef9e60ae760e60d56f5c9dde28ccb5ea916c0663271b2e6a22f27e63788,DurationMs:226000,BitRateKbps:320,TranscodedAssetPath:transcoded-i:///universal/2000000916259/00028946767825_2000000916259/UMG_audtrk_00028946767825_01_023_16.flac.mflow_320.mp3}]}"; - var dto = TypeSerializer.DeserializeFromString(dtoString); - } - - [Test] - public void Can_serialize_TrackUpdate_string() - { - const string dtoString = @"{Delete:False,ExternalRef:00028946767825/gbbbb2014307,ExternalUrn:urn:track:universal:00028946767825/gbbbb2014307,SupplierKeyName:universal,Name:""Act 3 [Rigoletto} - Lili Marlene"",NameExVersion:""Act 3 [Rigoletto} - Lili Marlene"",LabelText:Decca,ArtistText:""La Scala [Ensemble]"",ReleaseText:Captain Corelli's Mandolin -Original Motion Picture Soundtrack,Isrc:GBBBB2014307,DiscNumber:1,TrackNumber:7,DurationMs:188000,ExplicitType:Unknown,Copyright:2001 Decca Music Group Limited,Genres:[Classical],Participants:[{Name:""La Scala [Ensemble]"",Role:Main Artist,ExternalRef:lascalaensemble,ExternalUrn:urn:artist:universal:lascalaensemble},{Name:""Nuccio Siano [Vocalist]"",Role:Artist,ExternalRef:nucciosianovocalist,ExternalUrn:urn:artist:universal:nucciosianovocalist},{Name:""Nuccio Siano [Vocalist]"",Role:Performer,ExternalRef:nucciosianovocalist,ExternalUrn:urn:artist:universal:nucciosianovocalist},{Name:""Verdi, Giuseppe [Composer]"",Role:Composer,ExternalRef:verdigiuseppecomposer,ExternalUrn:urn:artist:universal:verdigiuseppecomposer},{Name:""Piave, Francesco Maria [Author]"",Role:Composer,ExternalRef:piavefrancescomariaauthor,ExternalUrn:urn:artist:universal:piavefrancescomariaauthor},{Name:Verdi,ExternalRef:verdi,ExternalUrn:urn:artist:universal:verdi},{Name:""La Scala [Ensemble]"",Role:Orchestra,ExternalRef:lascalaensemble,ExternalUrn:urn:artist:universal:lascalaensemble}],ExternalLabelUrn:urn:label:universal:decca,ExternalReleaseUrn:urn:album:universal:00028946767825,ExternalArtistUrns:{Partial:False,Items:[urn:artist:universal:lascalaensemble,urn:artist:universal:nucciosianovocalist]},Products:{Partial:False,Items:[{Delete:False,TerritoryCode:GB,Copyright:2001 Decca Music Group Limited,AllowDownload:True,AllowStreaming:True,AllowSubscription:False,CollectionOnly:False,DownloadStartDate:2010-01-08,CostPoints:[{StartDate:2010-01-08,CostCode:TCT7C}],StreamingStartDate:2010-01-08}]},AssetIds:[{AssetType:TrackPreview,ExternalUrn:urn:asset:universal:trackpreview/9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc/00028946767825/gbbbb2014307/ogg/160/188000},{AssetType:TrackProduct,ExternalUrn:urn:asset:universal:trackproduct/9e93fe68d2aa1aa6b3231a03c07cfb25f6b7fcd15b1e520aee6d4f27919aa5cc/00028946767825/gbbbb2014307/mp3/320/188000},{AssetType:MasterCoverArt,ExternalUrn:urn:asset:universal:mastercoverart/3ba1732101be3375835f24b1fbabf6eee75f0eef2573cc83398035fce31b46f4}]}"; - var toDto = TypeSerializer.DeserializeFromString(dtoString); - } - - [Test] - public void Can_serialize_TrackUpdate() - { - var dto = new TrackUpdate { - Delete = false, - ExternalRef = "00028946767825/gbbbb2014307", - ExternalUrn = "urn:track:universal:00028946767825/gbbbb2014307", - SupplierKeyName = "universal", - Name = "Act 3 [Rigoletto} - Lili Marlene", - NameExVersion = "Act 3 [Rigoletto} - Lili Marlene", - LabelText = "Decca", - ArtistText = "La Scala [Ensemble]", - ReleaseText = "Captain Corelli's Mandolin -Original Motion Picture Soundtrack", - Isrc = "GBBBB2014307", - DiscNumber = 1, - TrackNumber=7, - DurationMs = 188000, - ExplicitType = ExplicitType.Unknown, - Copyright = "Copyright:2001 Decca Music Group Limited", - Genres = new List { "Classical" }, - }; - - Serialize(dto); - } - - } -} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/DdnDtoTests.cs b/tests/ServiceStack.Text.Tests/DdnDtoTests.cs index 48a0a7280..8a39b2579 100644 --- a/tests/ServiceStack.Text.Tests/DdnDtoTests.cs +++ b/tests/ServiceStack.Text.Tests/DdnDtoTests.cs @@ -5,58 +5,60 @@ namespace ServiceStack.Text.Tests { - [TestFixture] - public class DdnDtoTests - : TestBase - { - const string UserViewString = - "{Id:f830c3fde66447fab09a7d06a9db4afd,Profile:{Id:f830c3fde66447fab09a7d06a9db4afd,UserType:Normal,UserName:yrstruely,FullName:Kerry Harris,Country:gb,LanguageCode:en,FlowPostCount:23,BuyCount:0,ClientTracksCount:0,ViewCount:29,FollowerUsers:[{Id:69593133bffb41869807756678207d87,UserType:Normal,UserName:mythz,FlowPostCount:0,ClientTracksCount:0,FollowingCount:1,FollowersCount:1,ViewCount:0,ActivationDate:0001-01-01}],FollowingUsers:[{Id:69593133bffb41869807756678207d87,UserType:Normal,UserName:mythz,FlowPostCount:0,ClientTracksCount:0,FollowingCount:1,FollowersCount:1,ViewCount:0,ActivationDate:0001-01-01}],UserFileTypes:[],AboutMe:},Posts:[{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/1,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/1,DateAdded:2010-02-18T12:10:29.804602Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:cf80f05ff3564676b3b66e2550bf4f3a,ContentUrn:urn:album:cf80f05ff3564676b3b66e2550bf4f3a,TrackUrns:[],Caption:woof,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/2,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/2,DateAdded:2010-02-18T12:12:58.358602Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:8806fa704e694ad7beedb31a06cd405e,ContentUrn:urn:track:8806fa704e694ad7beedb31a06cd405e,TrackUrns:[],Caption:sdgh,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/3,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/3,DateAdded:2010-02-18T12:28:56.089607Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:a246a69a1c5a4d0c8000a91519561eeb,ContentUrn:urn:album:a246a69a1c5a4d0c8000a91519561eeb,TrackUrns:[],Caption:killa!,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/4,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/4,DateAdded:2010-02-18T12:58:12.382604Z,CanPreviewFullLength:False,OriginUserId:69593133bffb41869807756678207d87,OriginUserName:mythz,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,ContentUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,TrackUrns:[],Caption:da bomb,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/5,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/5,DateAdded:2010-02-18T12:58:18.651604Z,CanPreviewFullLength:False,OriginUserId:69593133bffb41869807756678207d87,OriginUserName:mythz,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,ContentUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,TrackUrns:[],Caption:send it,CaptionUserId:69593133bffb41869807756678207d87,CaptionUserName:mythz,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/7,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/7,DateAdded:2010-02-18T16:28:24.642609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:a388179c38c64ae08e337d1d87018516,ContentUrn:urn:album:a388179c38c64ae08e337d1d87018516,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/8,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/8,DateAdded:2010-02-18T16:42:29.661609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:f9dede6b6fbc463ea66ffe885a7a9a85,ContentUrn:urn:track:f9dede6b6fbc463ea66ffe885a7a9a85,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/9,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/9,DateAdded:2010-02-18T16:44:54.476609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:0512382ee29f48b68eb7800f0be09eb6,ContentUrn:urn:track:0512382ee29f48b68eb7800f0be09eb6,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/10,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/10,DateAdded:2010-02-18T16:46:39.813609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:0512382ee29f48b68eb7800f0be09eb6,ContentUrn:urn:track:0512382ee29f48b68eb7800f0be09eb6,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/11,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/11,DateAdded:2010-02-18T16:49:09.140609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:855ebcb76a2b4915a7426028008a6b5a,ContentUrn:urn:album:855ebcb76a2b4915a7426028008a6b5a,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/12,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/12,DateAdded:2010-02-18T17:15:50.970609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:5cf1a7287dc74487b07f4c3af87a657b,ContentUrn:urn:track:5cf1a7287dc74487b07f4c3af87a657b,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/13,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/13,DateAdded:2010-02-18T17:16:39.922609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:118eef26eaad48cebe44a834a5ebafc5,ContentUrn:urn:track:118eef26eaad48cebe44a834a5ebafc5,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/14,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/14,DateAdded:2010-02-18T17:23:35.473609Z,CanPreviewFullLength:False,OriginUserId:69593133bffb41869807756678207d87,OriginUserName:mythz,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,ContentUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,TrackUrns:[],Caption:send it,CaptionUserId:69593133bffb41869807756678207d87,CaptionUserName:mythz,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/15,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/15,DateAdded:2010-02-18T22:12:35.205617Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:d76ec030a6234c96b00d9e9ae2493472,ContentUrn:urn:track:d76ec030a6234c96b00d9e9ae2493472,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/16,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/16,DateAdded:2010-02-18T22:16:13.85053Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:69f01aed61b548c7ace2df0955fa234d,ContentUrn:urn:album:69f01aed61b548c7ace2df0955fa234d,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/17,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/17,DateAdded:2010-02-18T22:16:18.566059Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:9dea76670a994bc78922694a05bf516c,ContentUrn:urn:track:9dea76670a994bc78922694a05bf516c,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/18,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/18,DateAdded:2010-02-18T22:16:21.55376Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:0e70ebf49de8418f9ef1fba91318476c,ContentUrn:urn:track:0e70ebf49de8418f9ef1fba91318476c,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/19,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/19,DateAdded:2010-02-18T22:17:31.905724Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:206bfb37082a4e859e383234078fedc6,ContentUrn:urn:album:206bfb37082a4e859e383234078fedc6,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/20,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/20,DateAdded:2010-02-18T22:17:41.520762Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:15fe23d6ba924316b5384625be2585f4,ContentUrn:urn:album:15fe23d6ba924316b5384625be2585f4,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/21,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/21,DateAdded:2010-02-18T22:21:29.706619Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:f479af815a204932b41c5de6beeffdf9,ContentUrn:urn:track:f479af815a204932b41c5de6beeffdf9,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content}]}"; + [TestFixture] + public class DdnDtoTests + : TestBase + { + const string UserViewString = + "{Id:f830c3fde66447fab09a7d06a9db4afd,Profile:{Id:f830c3fde66447fab09a7d06a9db4afd,UserType:Normal,UserName:yrstruely,FullName:Kerry Harris,Country:gb,LanguageCode:en,FlowPostCount:23,BuyCount:0,ClientTracksCount:0,ViewCount:29,FollowerUsers:[{Id:69593133bffb41869807756678207d87,UserType:Normal,UserName:mythz,FlowPostCount:0,ClientTracksCount:0,FollowingCount:1,FollowersCount:1,ViewCount:0,ActivationDate:0001-01-01}],FollowingUsers:[{Id:69593133bffb41869807756678207d87,UserType:Normal,UserName:mythz,FlowPostCount:0,ClientTracksCount:0,FollowingCount:1,FollowersCount:1,ViewCount:0,ActivationDate:0001-01-01}],UserFileTypes:[],AboutMe:},Posts:[{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/1,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/1,DateAdded:2010-02-18T12:10:29.804602Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:cf80f05ff3564676b3b66e2550bf4f3a,ContentUrn:urn:album:cf80f05ff3564676b3b66e2550bf4f3a,TrackUrns:[],Caption:woof,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/2,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/2,DateAdded:2010-02-18T12:12:58.358602Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:8806fa704e694ad7beedb31a06cd405e,ContentUrn:urn:track:8806fa704e694ad7beedb31a06cd405e,TrackUrns:[],Caption:sdgh,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/3,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/3,DateAdded:2010-02-18T12:28:56.089607Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:a246a69a1c5a4d0c8000a91519561eeb,ContentUrn:urn:album:a246a69a1c5a4d0c8000a91519561eeb,TrackUrns:[],Caption:killa!,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/4,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/4,DateAdded:2010-02-18T12:58:12.382604Z,CanPreviewFullLength:False,OriginUserId:69593133bffb41869807756678207d87,OriginUserName:mythz,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,ContentUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,TrackUrns:[],Caption:da bomb,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/5,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/5,DateAdded:2010-02-18T12:58:18.651604Z,CanPreviewFullLength:False,OriginUserId:69593133bffb41869807756678207d87,OriginUserName:mythz,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,ContentUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,TrackUrns:[],Caption:send it,CaptionUserId:69593133bffb41869807756678207d87,CaptionUserName:mythz,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/7,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/7,DateAdded:2010-02-18T16:28:24.642609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:a388179c38c64ae08e337d1d87018516,ContentUrn:urn:album:a388179c38c64ae08e337d1d87018516,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/8,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/8,DateAdded:2010-02-18T16:42:29.661609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:f9dede6b6fbc463ea66ffe885a7a9a85,ContentUrn:urn:track:f9dede6b6fbc463ea66ffe885a7a9a85,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/9,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/9,DateAdded:2010-02-18T16:44:54.476609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:0512382ee29f48b68eb7800f0be09eb6,ContentUrn:urn:track:0512382ee29f48b68eb7800f0be09eb6,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/10,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/10,DateAdded:2010-02-18T16:46:39.813609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:0512382ee29f48b68eb7800f0be09eb6,ContentUrn:urn:track:0512382ee29f48b68eb7800f0be09eb6,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/11,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/11,DateAdded:2010-02-18T16:49:09.140609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:855ebcb76a2b4915a7426028008a6b5a,ContentUrn:urn:album:855ebcb76a2b4915a7426028008a6b5a,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/12,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/12,DateAdded:2010-02-18T17:15:50.970609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:5cf1a7287dc74487b07f4c3af87a657b,ContentUrn:urn:track:5cf1a7287dc74487b07f4c3af87a657b,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/13,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/13,DateAdded:2010-02-18T17:16:39.922609Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:118eef26eaad48cebe44a834a5ebafc5,ContentUrn:urn:track:118eef26eaad48cebe44a834a5ebafc5,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/14,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/14,DateAdded:2010-02-18T17:23:35.473609Z,CanPreviewFullLength:False,OriginUserId:69593133bffb41869807756678207d87,OriginUserName:mythz,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,ContentUrn:urn:album:1dc1f6b2e81e4bc19fb3a8438468fa59,TrackUrns:[],Caption:send it,CaptionUserId:69593133bffb41869807756678207d87,CaptionUserName:mythz,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/15,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/15,DateAdded:2010-02-18T22:12:35.205617Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:d76ec030a6234c96b00d9e9ae2493472,ContentUrn:urn:track:d76ec030a6234c96b00d9e9ae2493472,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/16,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/16,DateAdded:2010-02-18T22:16:13.85053Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:69f01aed61b548c7ace2df0955fa234d,ContentUrn:urn:album:69f01aed61b548c7ace2df0955fa234d,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/17,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/17,DateAdded:2010-02-18T22:16:18.566059Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:9dea76670a994bc78922694a05bf516c,ContentUrn:urn:track:9dea76670a994bc78922694a05bf516c,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/18,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/18,DateAdded:2010-02-18T22:16:21.55376Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:0e70ebf49de8418f9ef1fba91318476c,ContentUrn:urn:track:0e70ebf49de8418f9ef1fba91318476c,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/19,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/19,DateAdded:2010-02-18T22:17:31.905724Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:206bfb37082a4e859e383234078fedc6,ContentUrn:urn:album:206bfb37082a4e859e383234078fedc6,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/20,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/20,DateAdded:2010-02-18T22:17:41.520762Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:album:15fe23d6ba924316b5384625be2585f4,ContentUrn:urn:album:15fe23d6ba924316b5384625be2585f4,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content},{Id:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/21,Urn:urn:post:f830c3fd-e664-47fa-b09a-7d06a9db4afd/21,DateAdded:2010-02-18T22:21:29.706619Z,CanPreviewFullLength:False,OriginUserId:f830c3fde66447fab09a7d06a9db4afd,OriginUserName:yrstruely,SourceUserId:f830c3fde66447fab09a7d06a9db4afd,SourceUserName:yrstruely,SubjectUrn:urn:track:f479af815a204932b41c5de6beeffdf9,ContentUrn:urn:track:f479af815a204932b41c5de6beeffdf9,TrackUrns:[],Caption:,CaptionUserId:f830c3fde66447fab09a7d06a9db4afd,CaptionUserName:yrstruely,PostType:Content}]}"; - [Test] - public void Can_serializer_UserPublicView() - { - var dto = TypeSerializer.DeserializeFromString(UserViewString); - Serialize(dto); - } - - [Test] - public void Can_serialize_ResponseStats() - { - var dto = new ResponseStatus { - ErrorCode = null - }; + [Test] + public void Can_serializer_UserPublicView() + { + var dto = TypeSerializer.DeserializeFromString(UserViewString); + Serialize(dto); + } + + [Test] + public void Can_serialize_ResponseStats() + { + var dto = new ResponseStatus + { + ErrorCode = null + }; - var dtoString = TypeSerializer.SerializeToString(dto); + var dtoString = TypeSerializer.SerializeToString(dto); - Assert.That(dtoString, Is.EqualTo("{Errors:[]}")); + Assert.That(dtoString, Is.EqualTo("{}")); - Console.WriteLine(dtoString); - } + Console.WriteLine(dtoString); + } - [Test] - public void Can_serialize_GetContentStatsResponse() - { - var dto = new GetContentStatsResponse { - CreatedDate = DateTime.UtcNow, - TopRecommenders = new List - { - CreateUserSearchResult(1), - CreateUserSearchResult(2), - }, - LatestPosts = new List { - CreatePost(1), - CreatePost(2) + [Test] + public void Can_serialize_GetContentStatsResponse() + { + var dto = new GetContentStatsResponse + { + CreatedDate = DateTime.UtcNow, + TopRecommenders = new List + { + CreateUserSearchResult(1), + CreateUserSearchResult(2), + }, + LatestPosts = new List { + CreatePost(1), + CreatePost(2) }, - }; + }; - Serialize(dto); - } + Serialize(dto); + } - [Test] - public void Can_serialize_ProUserPublicProfile() - { - var dtoString = - @"{Id:81ae7b3ae5404f5f827d0303949fcb2f,Alias:Mike Halliday,ProUserType:Celebrity,ProUserSalesType:OthersMusic,ProUserLink:{},ProUserLinkHtml:"""",SocialLinks:[],BannerImageBackgroundColor:#000000,ArtistImages:[],Genres:[],BiographyPageHtml:""