From 5c9cb480df0491281ef13a2b51a33509ebfd3d7e Mon Sep 17 00:00:00 2001 From: stdcion Date: Wed, 25 Oct 2023 17:28:27 +0300 Subject: [PATCH] Feature: bump Graal SDK (#43) * fix: codestyle settings * fix: some market events typo * feat: add utils tests * feat: add words to dictionary * feat: bump graal sdk * feat: add IpfType * fix: README.md * fix: abstract SafeHandle classes --------- Co-authored-by: Konstantin Ivaschenko --- .editorconfig | 6 +- README.md | 100 ++++++++-- StyleCop.xml | 6 +- dxfeed-graal-net-api.sln.DotSettings | 2 + .../Api/Osub/TimeSeriesSubscriptionSymbol.cs | 4 +- src/DxFeed.Graal.Net/DxFeed.Graal.Net.csproj | 2 +- .../Events/Market/OrderBase.cs | 2 +- .../Events/Market/SpreadOrder.cs | 2 +- src/DxFeed.Graal.Net/Ipf/InstrumentProfile.cs | 9 +- .../Ipf/InstrumentProfileReader.cs | 51 +++++- .../Ipf/InstrumentProfileType.cs | 171 ++++++++++++++++++ .../Endpoint/Handles/EndpointSafeHandle.cs | 6 +- .../Native/ErrorHandling/ErrorCheck.cs | 11 ++ src/DxFeed.Graal.Net/Native/ImportInfo.cs | 2 +- .../Native/Interop/JavaFinalizeSafeHandle.cs | 33 ++++ .../Native/Interop/JavaSafeHandle.cs | 70 +++++++ .../Native/Interop/JavaStringSafeHandle.cs | 41 +++++ .../Native/Interop/SafeHandleZeroIsInvalid.cs | 24 ++- .../InstrumentProfileCollectorHandle.cs | 74 ++++++++ .../Handles/InstrumentProfileReaderHandle.cs | 16 -- .../InstrumentProfileReaderSafeHandle.cs | 158 ---------------- .../InstrumentProfileUpdateListenerHandle.cs | 29 +++ .../Native/Ipf/Handles/IpfMapper.cs | 50 ----- .../Native/Ipf/Handles/IpfNative.cs | 45 ----- .../IterableInstrumentProfileHandle.cs | 49 +++++ .../Ipf/InstrumentProfileCollectorNative.cs | 20 ++ .../Native/Ipf/InstrumentProfileListNative.cs | 59 ++++++ .../Native/Ipf/InstrumentProfileMapper.cs | 48 +++++ .../Native/Ipf/InstrumentProfileNative.cs | 45 +++++ .../Ipf/InstrumentProfileReaderNative.cs | 100 ++++++---- .../Native/Subscription/SubscriptionNative.cs | 12 +- src/DxFeed.Graal.Net/Utils/AttributeUtil.cs | 42 ++++- src/DxFeed.Graal.Net/Utils/BitUtil.cs | 16 +- src/DxFeed.Graal.Net/Utils/DayUtil.cs | 72 +++++--- src/DxFeed.Graal.Net/Utils/EnumUtil.cs | 60 +++--- .../Api/DXEndpointTest.cs | 2 +- .../DxFeed.Graal.Net.Tests.csproj | 1 + .../Utils/AttributeUtilTest.cs | 79 ++++++++ .../Utils/DayUtilTest.cs | 79 ++++++++ 39 files changed, 1165 insertions(+), 433 deletions(-) create mode 100644 src/DxFeed.Graal.Net/Ipf/InstrumentProfileType.cs create mode 100644 src/DxFeed.Graal.Net/Native/Interop/JavaFinalizeSafeHandle.cs create mode 100644 src/DxFeed.Graal.Net/Native/Interop/JavaSafeHandle.cs create mode 100644 src/DxFeed.Graal.Net/Native/Interop/JavaStringSafeHandle.cs create mode 100644 src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileCollectorHandle.cs delete mode 100644 src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileReaderHandle.cs delete mode 100644 src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileReaderSafeHandle.cs create mode 100644 src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileUpdateListenerHandle.cs delete mode 100644 src/DxFeed.Graal.Net/Native/Ipf/Handles/IpfMapper.cs delete mode 100644 src/DxFeed.Graal.Net/Native/Ipf/Handles/IpfNative.cs create mode 100644 src/DxFeed.Graal.Net/Native/Ipf/Handles/IterableInstrumentProfileHandle.cs create mode 100644 src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileCollectorNative.cs create mode 100644 src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileListNative.cs create mode 100644 src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileMapper.cs create mode 100644 src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileNative.cs create mode 100644 tests/DxFeed.Graal.Net.Tests/Utils/AttributeUtilTest.cs create mode 100644 tests/DxFeed.Graal.Net.Tests/Utils/DayUtilTest.cs diff --git a/.editorconfig b/.editorconfig index 1ee5e8ea..0d897cc6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -313,7 +313,7 @@ dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_mod dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = none # No other public/protected/protected_internal fields are allowed # https://docs.microsoft.com/dotnet/standard/design-guidelines/field @@ -346,7 +346,7 @@ dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = none # No non-private instance fields are allowed # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md @@ -428,7 +428,7 @@ dotnet_naming_rule.parameters_rule.severity = warning ########################################## # Whitespace and control characters in string literals should be explicit. C#11 raw literals. dotnet_diagnostic.S2479.severity = none - +dotnet_diagnostic.CA1707.severity = none [OrderSource.cs] generated_code = true diff --git a/README.md b/README.md index 84c06d02..efab151e 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,10 @@ the [Overview](#overview) section.
* [Implementation Details](#implementation-details) * [Architectural Restrictions and Other Limitations in the Old Version](#architectural-restrictions-and-other-limitations-of-the-old-version) - [Documentation](#documentation) +- [Requirements](#requirements) - [Installation](#installation) - [Usage](#usage) +- [Tools](#tools) - [Samples](#samples) - [Current State](#current-state) @@ -58,7 +60,7 @@ prevent us from providing a state-of-the-art technological solution. Feature development has already stopped for the [old version](https://github.com/dxFeed/dxfeed-net-api) of dxFeed .NET API. -We expect the new repository to go into production in Q2’2023. +We expect the new repository to go into production in Q4’2023. At the same time, the old version will be considered deprecated, and at the end of 2024, we plan to end the service. If you’re already our customer and have difficulty with a future transition, please contact us via our [customer portal](https://jira.in.devexperts.com/servicedesk/customer/portal/1). @@ -96,18 +98,18 @@ ready to answer any questions and help with the transition. #### Sample Mapping -| # | Sample | Old Version | New Version | -|:--:|:----------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------| -| 1 | How to subscribe to `Quote`, `Trade`, `TradeETH`, `Order`, `SpreadOrder`, `AnalyticOrder`, `TimeAndSale` events | [dxf_events_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_events_sample) | [DxFeed.Graal.Net.Samples.DxFeedConnect](samples/DxFeedConnect) | -| 2 | How to subscribe to `Candle` event | [dxf_candle_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_candle_sample) | [DxFeed.Graal.Net.Samples.CandleSample](samples/CandleSample) | -| 3 | How to receive IPF data from URL or file | [dxf_instrument_profile_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_instrument_profile_sample) | *Q3’2023*, please see [TBD](#future-development) section | -| 4 | How to subscribe to IPF live updates | [dxf_instrument_profile_live_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_instrument_profile_live_sample) | *Q3’2023*, please see [TBD](#future-development) section | -| 5 | How to subscribe to `Order`, `SpreadOrder`, `Candle`, `TimeAndSale`, `Greeks`, `Series` snapshots | [dxf_snapshot_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_snapshot_sample) | *Q4’2023*, please see [TBD](#future-development) section | -| 6 | How to subscribe to depth of market | [dxf_price_level_book_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_price_level_book_sample) | *Q4’2023*, please see [TBD](#future-development) section | -| 7 | How to receive snapshots of `TimeAndSale`, `Candle`, `Series`, `Greeks` events on a given time interval without live subscription | [dxf_simple_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_simple_data_retrieving_sample) | *Q4’2023*, please see [TBD](#future-development) section | -| 8 | How to subscribe to order snapshot with incremental updates | [dxf_inc_order_snapshot_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_inc_order_snapshot_sample) | *Q4’2023*, please see [TBD](#future-development) section | -| 9 | How to retrieve `Candle` data from the candle web service | [dxf_candle_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_candle_data_retrieving_sample) | *Q4’2023*, please see [TBD](#future-development) section | -| 10 | How to retrieve `TimeAndSale` data from the candle web service | [dxf_tns_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_tns_data_retrieving_sample) | *Q4’2023*, please see [TBD](#future-development) section | +| # | Sample | Old Version | New Version | +|:--:|:----------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------| +| 1 | How to subscribe to `Quote`, `Trade`, `TradeETH`, `Order`, `SpreadOrder`, `AnalyticOrder`, `TimeAndSale` events | [dxf_events_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_events_sample) | [DxFeed.Graal.Net.Samples.DxFeedConnect](samples/DxFeedConnect) | +| 2 | How to subscribe to `Candle` event | [dxf_candle_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_candle_sample) | [DxFeed.Graal.Net.Samples.CandleSample](samples/CandleSample) | +| 3 | How to receive IPF data from URL or file | [dxf_instrument_profile_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_instrument_profile_sample) | [DxFeed.Graal.Net.Samples.DXFeedIpfConnect](samples/DXFeedIpfConnect) | +| 4 | How to subscribe to IPF live updates | [dxf_instrument_profile_live_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_instrument_profile_live_sample) | *Q4’2023*, please see [TBD](#future-development) section | +| 5 | How to subscribe to `Order`, `SpreadOrder`, `Candle`, `TimeAndSale`, `Greeks`, `Series` snapshots | [dxf_snapshot_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_snapshot_sample) | *Q2’2024*, please see [TBD](#future-development) section | +| 6 | How to subscribe to depth of market | [dxf_price_level_book_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_price_level_book_sample) | *Q2’2024*, please see [TBD](#future-development) section | +| 7 | How to receive snapshots of `TimeAndSale`, `Candle`, `Series`, `Greeks` events on a given time interval without live subscription | [dxf_simple_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_simple_data_retrieving_sample) | *Q2’2024*, please see [TBD](#future-development) section | +| 8 | How to subscribe to order snapshot with incremental updates | [dxf_inc_order_snapshot_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_inc_order_snapshot_sample) | *Q2’2024*, please see [TBD](#future-development) section | +| 9 | How to retrieve `Candle` data from the candle web service | [dxf_candle_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_candle_data_retrieving_sample) | *Q4’2024*, please see [TBD](#future-development) section | +| 10 | How to retrieve `TimeAndSale` data from the candle web service | [dxf_tns_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_tns_data_retrieving_sample) | *Q4’2024*, please see [TBD](#future-development) section | ### Implementation Details @@ -162,6 +164,72 @@ Find useful information in our self-service dxFeed Knowledge Base or .NET API do * [Order Book reconstruction](https://kb.dxfeed.com/en/data-model/dxfeed-order-book/order-book-reconstruction.html) * [Symbology Guide](https://kb.dxfeed.com/en/data-model/symbology-guide.html) +## Requirements + +### Windows + +Only x64 versions are supported. + +| OS | Version | Architectures | +|---------------------------------------|----------------|---------------| +| [Windows][Windows-client] | 8, 8.1 | x64 | +| [Windows 10][Windows-client] | Version 1607+ | x64 | +| [Windows 11][Windows-client] | Version 22000+ | x64 | +| [Windows Server][Windows-Server] | 2012+ | x64 | +| [Windows Server Core][Windows-Server] | 2012+ | x64 | +| [Nano Server][Nano-Server] | Version 1809+ | x64 | + +#### Requirements + +* [.NET 6][.NET 6] (not required for self-contained assemblies) +* [Visual C++ Redistributable for Visual Studio 2015][vc_redist] + +[Windows-client]: https://www.microsoft.com/windows/ + +[Windows-Server]: https://learn.microsoft.com/windows-server/ + +[Nano-Server]: https://learn.microsoft.com/windows-server/get-started/getting-started-with-nano-server + +[vc_redist]: [https://aka.ms/vs/17/release/vc_redist.x64.exe] + +### Linux + +Only x64 versions are supported. + +#### Requirements + +* [.NET 6][.NET 6] (not required for self-contained assemblies) + +#### Libc compatibility + +- [glibc][glibc]: 2.35+ (from Ubuntu 22.04) +- [musl][musl]: temporarily unsupported + +#### Libpthread compatibility + +A symlink on libpthread.so, libpthread.so.0, or libcoreclr.so must exist. + +[glibc]: https://www.gnu.org/software/libc/ + +[musl]: https://musl.libc.org/ + +### macOS + +| OS | Version | Architectures | +|----------------|---------|---------------| +| [macOS][macOS] | 10.15+ | x64 | +| [macOS][macOS] | 11+ | Arm64 | + +Is supported in the Rosetta 2 x64 emulator. + +[macOS]: https://support.apple.com/macos + +#### Requirements + +* [.NET 6][.NET 6] (not required for self-contained assemblies) + +[.NET 6]: [https://dotnet.microsoft.com/en-us/download/dotnet/6.0] + ## Installation Add this [package source](https://dxfeed.jfrog.io/artifactory/api/nuget/v3/nuget-open) to NuGet config. @@ -238,6 +306,12 @@ require .NET installation) * [LatencyTest](https://github.com/dxFeed/dxfeed-graal-net-api/blob/main/src/DxFeed.Graal.Net.Tools/LatencyTest/LatencyTestTool.cs) connects to the specified address(es) and calculates latency. +To run tools on macOs, it may be necessary to unquarantine them: + +``` +sudo /usr/bin/xattr -r -d com.apple.quarantine +``` + ## Samples * [DxFeed.Graal.Net.Samples.ConvertTapeFile](https://github.com/dxFeed/dxfeed-graal-net-api/blob/main/samples/ConvertTapeFile/Program.cs) diff --git a/StyleCop.xml b/StyleCop.xml index aa42e4d0..cd0b0130 100644 --- a/StyleCop.xml +++ b/StyleCop.xml @@ -7,7 +7,9 @@ - - + + + + diff --git a/dxfeed-graal-net-api.sln.DotSettings b/dxfeed-graal-net-api.sln.DotSettings index 9c6647f7..c098f2d8 100644 --- a/dxfeed-graal-net-api.sln.DotSettings +++ b/dxfeed-graal-net-api.sln.DotSettings @@ -23,6 +23,7 @@ True True True + True True True True @@ -65,6 +66,7 @@ True True True + True True True True diff --git a/src/DxFeed.Graal.Net/Api/Osub/TimeSeriesSubscriptionSymbol.cs b/src/DxFeed.Graal.Net/Api/Osub/TimeSeriesSubscriptionSymbol.cs index c2254836..8d9edab1 100644 --- a/src/DxFeed.Graal.Net/Api/Osub/TimeSeriesSubscriptionSymbol.cs +++ b/src/DxFeed.Graal.Net/Api/Osub/TimeSeriesSubscriptionSymbol.cs @@ -15,7 +15,7 @@ namespace DxFeed.Graal.Net.Api.Osub; /// /// Represents subscription to time-series of events. -/// Instances of this class can be used with {@link DXFeedSubscription} to specify subscription +/// Instances of this class can be used with to specify subscription /// for time series events from a specific time. By default, subscribing to time-series events by /// their event symbol object, the subscription is performed to a stream of new events as they happen only. ///
@@ -24,8 +24,6 @@ namespace DxFeed.Graal.Net.Api.Osub; /// The type of event symbol. public class TimeSeriesSubscriptionSymbol : IndexedEventSubscriptionSymbol { - // ToDo Add a constructor overload to pass fromTime as DateTimeOffset. - /// /// Initializes a new instance of the class /// with a specified event symbol and from time in milliseconds since Unix epoch. diff --git a/src/DxFeed.Graal.Net/DxFeed.Graal.Net.csproj b/src/DxFeed.Graal.Net/DxFeed.Graal.Net.csproj index a3637a76..93604867 100644 --- a/src/DxFeed.Graal.Net/DxFeed.Graal.Net.csproj +++ b/src/DxFeed.Graal.Net/DxFeed.Graal.Net.csproj @@ -17,7 +17,7 @@ - + all diff --git a/src/DxFeed.Graal.Net/Events/Market/OrderBase.cs b/src/DxFeed.Graal.Net/Events/Market/OrderBase.cs index 7dc89cb1..6666db78 100644 --- a/src/DxFeed.Graal.Net/Events/Market/OrderBase.cs +++ b/src/DxFeed.Graal.Net/Events/Market/OrderBase.cs @@ -217,7 +217,7 @@ public long TimeNanos public OrderAction Action { get => OrderActionExt.ValueOf(BitUtil.GetBits(Flags, ActionMask, ActionShift)); - set => BitUtil.SetBits(Flags, ActionMask, ActionShift, (int)value); + set => Flags = BitUtil.SetBits(Flags, ActionMask, ActionShift, (int)value); } /// diff --git a/src/DxFeed.Graal.Net/Events/Market/SpreadOrder.cs b/src/DxFeed.Graal.Net/Events/Market/SpreadOrder.cs index 1fbc199e..e03bb368 100644 --- a/src/DxFeed.Graal.Net/Events/Market/SpreadOrder.cs +++ b/src/DxFeed.Graal.Net/Events/Market/SpreadOrder.cs @@ -51,7 +51,7 @@ public SpreadOrder(string? eventSymbol) /// /// The string representation. public override string ToString() => - "Order{" + BaseFieldsToString() + + "SpreadOrder{" + BaseFieldsToString() + ", spreadSymbol='" + StringUtil.EncodeNullableString(SpreadSymbol) + "'" + "}"; } diff --git a/src/DxFeed.Graal.Net/Ipf/InstrumentProfile.cs b/src/DxFeed.Graal.Net/Ipf/InstrumentProfile.cs index 83efbab4..ca22d877 100644 --- a/src/DxFeed.Graal.Net/Ipf/InstrumentProfile.cs +++ b/src/DxFeed.Graal.Net/Ipf/InstrumentProfile.cs @@ -198,12 +198,12 @@ public string ExchangeData /// Gets or sets list of exchanges where instrument is quoted or traded. /// Its shall use the following format: /// - /// <VALUE> ::= <empty> | <LIST> - /// <IST> ::= <MIC> | <MIC> <semicolon> + /// <VALUE> ::= <empty> | <LIST> + /// <IST> ::= <MIC> | <MIC> <semicolon> /// /// <LIST> the list shall be sorted by MIC. /// - /// "ARCX;CBSX ;XNAS;XNYS". + /// "ARCX;CBSX;XNAS;XNYS". public string Exchanges { get => exchanges; @@ -460,6 +460,7 @@ public string SettlementStyle /// /// the list shall be sorted by <UPPER_LIMIT>. ///
+ /// "0.25", "0.01 3; 0.05". public string PriceIncrements { get => priceIncrements; @@ -480,5 +481,5 @@ public string TradingHours /// /// The string representation. public override string ToString() => - $"{type} {symbol}"; + $"{Type} {Symbol}"; } diff --git a/src/DxFeed.Graal.Net/Ipf/InstrumentProfileReader.cs b/src/DxFeed.Graal.Net/Ipf/InstrumentProfileReader.cs index 0544008d..31de375c 100644 --- a/src/DxFeed.Graal.Net/Ipf/InstrumentProfileReader.cs +++ b/src/DxFeed.Graal.Net/Ipf/InstrumentProfileReader.cs @@ -13,11 +13,36 @@ namespace DxFeed.Graal.Net.Ipf; /// Reads instrument profiles from the stream using Instrument Profile Format (IPF). /// Please see Instrument Profile Format documentation for complete description. /// This reader automatically uses data formats as specified in the stream. +///
/// This reader is intended for "one time only" usage: create new instances for new IPF reads. +///
+/// For backward compatibility reader can be configured with system property "com.dxfeed.ipf.complete" to control +/// the strategy for missing "##COMPLETE" tag when reading IPF, possible values are: +///
    +///
  • warn - show warning in the log (default)
  • +///
  • error - throw exception (future default)
  • +///
  • ignore - do nothing (for backward compatibility)
  • +///
/// public class InstrumentProfileReader { - private readonly InstrumentProfileReaderNative ipf = InstrumentProfileReaderNative.Create(); + private readonly InstrumentProfileReaderNative ipfReaderNative = InstrumentProfileReaderNative.Create(); + + /// + /// Resolves the given address specification into its corresponding URL format, particularly transforming + /// simple "host:port" address specifications into a full HTTP URL. + /// + /// + /// If the provided address appears to be a "host:port" pattern without any forward slashes, + /// this method will attempt to convert it to a standardized HTTP URL format targeting an + /// "ipf/all.ipf.gz" endpoint. If the address contains a query parameter (e.g., "host:port?param=value"), + /// the parameter will be preserved in the resulting URL. Addresses that do not fit the "host:port" + /// pattern, or that have invalid port numbers, are returned unchanged. + /// + /// The address specification to be resolved into a URL. + /// The resolved URL corresponding to the specified address. + public static string? ResolveSourceUrl(string address) => + InstrumentProfileReaderNative.ResolveSourceUrl(address); /// /// Returns last modification time (in milliseconds) from last operation @@ -25,14 +50,14 @@ public class InstrumentProfileReader /// /// The last modification time. public long GetLastModified() => - ipf.GetLastModified(); + ipfReaderNative.GetLastModified(); /// - /// Returns {@code true} if IPF was fully read on last operation. + /// Returns true if IPF was fully read on last operation. /// /// true if IPF was fully read; otherwise, false. public bool WasComplete() => - ipf.WasComplete(); + ipfReaderNative.WasComplete(); /// /// Reads and returns instrument profiles from specified file. @@ -51,6 +76,22 @@ public bool WasComplete() => public List ReadFromFile(string address) => ReadFromFile(address, null, null); + /// + /// Reads and returns instrument profiles from specified file. + /// This method recognizes data compression formats "zip" and "gzip" automatically. + /// In case of zip the first file entry will be read and parsed as a plain data stream. + /// In case of gzip compressed content will be read and processed. + /// In other cases data considered uncompressed and will be parsed as is. + ///
+ /// Specified user and password take precedence over authentication information that is supplied to this method + /// as part of URL user info like "http://user:password@host:port/path/file.ipf". + ///
+ /// This operation updates and . + ///
+ /// The URL of file to read from. + /// The user name (may be null). + /// The password (may be null). + /// The list of instrument profiles. public List ReadFromFile(string address, string? user, string? password) => - ipf.ReadFromFile(address, user, password); + ipfReaderNative.ReadFromFile(address, user, password); } diff --git a/src/DxFeed.Graal.Net/Ipf/InstrumentProfileType.cs b/src/DxFeed.Graal.Net/Ipf/InstrumentProfileType.cs new file mode 100644 index 00000000..49229115 --- /dev/null +++ b/src/DxFeed.Graal.Net/Ipf/InstrumentProfileType.cs @@ -0,0 +1,171 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; +using System.Collections.Concurrent; + +namespace DxFeed.Graal.Net.Ipf; + +/// +/// Defines standard types of . Note that other (unknown) types +/// can be used without listing in this class - use it for convenience only. +/// Please see Instrument Profile Format documentation for complete description. +/// +public class InstrumentProfileType +{ + private static readonly ConcurrentDictionary ByName = new(); + + // Elements Must Be Ordered By Access. Ignored, it is necessary to observe the order of initialization. + // For avoid creating static ctor. +#pragma warning disable SA1202 + /// + /// The currency type. + /// + public static readonly InstrumentProfileType CURRENCY = new("CURRENCY"); + + /// + /// Foreign exchange market or cryptocurrency. + /// + public static readonly InstrumentProfileType FOREX = new("FOREX"); + + /// + /// Debt instruments, excluding money market funds. + /// + public static readonly InstrumentProfileType BOND = new("BOND"); + + /// + /// Non-tradable market performance indicators. + /// + public static readonly InstrumentProfileType INDEX = new("INDEX"); + + /// + /// Tradable equities, excluding ETFs and mutual funds. + /// + public static readonly InstrumentProfileType STOCK = new("STOCK"); + + /// + /// Exchange-traded fund. + /// + public static readonly InstrumentProfileType ETF = new("ETF"); + + /// + /// Investment funds, excluding ETFs and money market funds. + /// + public static readonly InstrumentProfileType MUTUAL_FUND = new("MUTUAL_FUND"); + + /// + /// Funds that invest in short-term debt instruments. + /// + public static readonly InstrumentProfileType MONEY_MARKET_FUND = new("MONEY_MARKET_FUND"); + + /// + /// Grouping instrument for futures, aka futures product. + /// + public static readonly InstrumentProfileType PRODUCT = new("PRODUCT"); + + /// + /// Futures contract, derivative instrument. + /// + public static readonly InstrumentProfileType FUTURE = new("FUTURE"); + + /// + /// Option contract, derivative instrument. + /// + public static readonly InstrumentProfileType OPTION = new("OPTION"); + + /// + /// Derivative that gives the right, but not the obligation, + /// to buy or sell a security at a certain price before expiration. + /// + public static readonly InstrumentProfileType WARRANT = new("WARRANT"); + + /// + /// Contract for differences, an arrangement where the differences + /// in the settlement between the open and closing trade prices are cash-settled. + /// + public static readonly InstrumentProfileType CFD = new("CFD"); + + /// + /// Composite virtual instrument consisting of two or several individual instruments that represent multileg order. + /// + public static readonly InstrumentProfileType SPREAD = new("SPREAD"); + + /// + /// Non-tradable miscellaneous instruments. + /// + public static readonly InstrumentProfileType OTHER = new("OTHER"); + + /// + /// Special instrument type indicating instrument removal. + /// + public static readonly InstrumentProfileType REMOVED = new("REMOVED"); +#pragma warning restore SA1202 + + private InstrumentProfileType(string name) + { + Name = name; + if (!ByName.TryAdd(Name, this)) + { + throw new ArgumentException($"Duplicate value: {Name}", nameof(name)); + } + } + + /// + /// Gets full name this instance. + /// + public string Name { get; } + + /// + /// Retrieves the corresponding for the given name. + /// + /// The name of the to be retrieved. + /// + /// The associated if found, otherwise returns null. + /// + public static InstrumentProfileType? Find(string name) => + ByName.TryGetValue(name, out var value) ? value : null; + + /// + /// Compares two specified instrument profile types for order. + /// + /// + /// This method returns a negative integer if the first type is less than the second, zero if they are equal, + /// or a positive integer if the first type is greater than the second. + ///

+ /// Unlike the natural ordering of the class itself, this method supports + /// unknown types, ordering them alphabetically after the recognized types. It's designed primarily for + /// arranging data representation in a file and is not intended for business logic evaluations. + /// + /// The first instrument profile type to compare. + /// The second instrument profile type to compare. + /// + /// A negative integer if is less than , + /// zero if they are equal, or a positive integer if is greater than . + /// If an unknown type is encountered, it's ordered alphabetically after recognized types. + /// + public static int CompareTypes(string type1, string type2) + { + var t1 = Find(type1); + var t2 = Find(type2); + + if (t1 == null && t2 == null) + { + return string.Compare(type1, type2, StringComparison.Ordinal); + } + + if (t1 == null) + { + return 1; + } + + if (t2 == null) + { + return -1; + } + + return string.Compare(t1.Name, t2.Name, StringComparison.Ordinal); + } +} diff --git a/src/DxFeed.Graal.Net/Native/Endpoint/Handles/EndpointSafeHandle.cs b/src/DxFeed.Graal.Net/Native/Endpoint/Handles/EndpointSafeHandle.cs index 70a99a96..ccde1f36 100644 --- a/src/DxFeed.Graal.Net/Native/Endpoint/Handles/EndpointSafeHandle.cs +++ b/src/DxFeed.Graal.Net/Native/Endpoint/Handles/EndpointSafeHandle.cs @@ -97,7 +97,7 @@ public void AddStateChangeListener(StateChangeListenerSafeHandle stateChangeList var thread = Isolate.CurrentThread; ErrorCheck.NativeCall( thread, - NativeAddStateChangeListener(thread, this, stateChangeListenerHandle, 0, 0)); + NativeAddStateChangeListener(thread, this, stateChangeListenerHandle)); } public FeedHandle* GetFeed() @@ -258,9 +258,7 @@ private static extern int NativeGetState( private static extern int NativeAddStateChangeListener( nint thread, EndpointHandle* endpointHandle, - StateChangeListenerHandle* listenerHandle, - nint endpointFinalize, - nint userData); + StateChangeListenerHandle* listenerHandle); [DllImport( ImportInfo.DllName, diff --git a/src/DxFeed.Graal.Net/Native/ErrorHandling/ErrorCheck.cs b/src/DxFeed.Graal.Net/Native/ErrorHandling/ErrorCheck.cs index 7d4d4ce0..a32dc554 100644 --- a/src/DxFeed.Graal.Net/Native/ErrorHandling/ErrorCheck.cs +++ b/src/DxFeed.Graal.Net/Native/ErrorHandling/ErrorCheck.cs @@ -52,6 +52,17 @@ public static long NativeCall(nint thread, long result) return result; } + public static T NativeCall(nint thread, T result) + where T : SafeHandle + { + if (result.IsInvalid) + { + ThrowThreadJavaExceptionIfExist(thread); + } + + return result; + } + ///

/// Checks for a call to a native function that returns an T* (unmanaged pointer type). /// Throws if error occured. diff --git a/src/DxFeed.Graal.Net/Native/ImportInfo.cs b/src/DxFeed.Graal.Net/Native/ImportInfo.cs index baf7a1ea..84842d2b 100644 --- a/src/DxFeed.Graal.Net/Native/ImportInfo.cs +++ b/src/DxFeed.Graal.Net/Native/ImportInfo.cs @@ -14,5 +14,5 @@ internal static class ImportInfo /// /// Path to imported DLL. /// - public const string DllName = "DxFeedGraalNativeApi"; + public const string DllName = "DxFeedGraalNativeSdk"; } diff --git a/src/DxFeed.Graal.Net/Native/Interop/JavaFinalizeSafeHandle.cs b/src/DxFeed.Graal.Net/Native/Interop/JavaFinalizeSafeHandle.cs new file mode 100644 index 00000000..30878609 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Interop/JavaFinalizeSafeHandle.cs @@ -0,0 +1,33 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using DxFeed.Graal.Net.Native.ErrorHandling; + +namespace DxFeed.Graal.Net.Native.Interop; + +internal abstract class JavaFinalizeSafeHandle : JavaSafeHandle +{ + public void RegisterFinalize(object o) => + ErrorCheck.NativeCall( + CurrentThread, + NativeObjectFinalize(CurrentThread, handle, GCHandle.Alloc(o, GCHandleType.Weak))); + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + private static void OnFinalize(nint thread, nint self) => + GCHandle.FromIntPtr(self).Free(); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_Object_finalize")] + private static extern int NativeObjectFinalize( + nint thread, + nint handle, + GCHandle userData); +} diff --git a/src/DxFeed.Graal.Net/Native/Interop/JavaSafeHandle.cs b/src/DxFeed.Graal.Net/Native/Interop/JavaSafeHandle.cs new file mode 100644 index 00000000..7a0eaf86 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Interop/JavaSafeHandle.cs @@ -0,0 +1,70 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; +using System.Runtime.InteropServices; +using DxFeed.Graal.Net.Native.ErrorHandling; +using DxFeed.Graal.Net.Native.Graal; + +namespace DxFeed.Graal.Net.Native.Interop; + +/// +/// Provides an abstract base class for Java-related handles within a .NET interop environment. +/// It is designed to manage Java object handles and ensure their proper cleanup and disposal, +/// especially within the context of the GraalVM Native Image. +///
+/// Inheritors must override the method if special cleanup logic is required. +/// The common cleanup logic is described in the method. +///
+/// +/// This class is designed to help manage Java objects when working with native interop scenarios. +/// The derived classes should implement specific logic related to their respective Java object types, +/// and can rely on this base class for basic handle lifecycle management. +/// +internal abstract class JavaSafeHandle : SafeHandleZeroIsInvalid +{ + /// + protected static IsolateThread CurrentThread => + IsolateThread.CurrentThread; + + /// + /// Releases the handle ensuring associated Java resources are properly disposed of. + /// + /// true if the handle was successfully released; otherwise, false. + protected override bool ReleaseHandle() + { + try + { + Release(); + SetHandle(IntPtr.Zero); + return true; + } + catch (Exception e) + { + // ToDo Add a log entry. + Console.Error.WriteLine($"Exception in {GetType().Name} when releasing resource: {e}"); + } + + return false; + } + + /// + /// Releases the associated resources, performing error checks against the native call. + /// This method can be overridden by derived classes to provide specialized release logic. + /// + /// If error occured. + protected virtual void Release() => + ErrorCheck.NativeCall(CurrentThread, NativeRelease(CurrentThread, handle)); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_JavaObjectHandler_release")] + private static extern int NativeRelease( + nint thread, + nint handle); +} diff --git a/src/DxFeed.Graal.Net/Native/Interop/JavaStringSafeHandle.cs b/src/DxFeed.Graal.Net/Native/Interop/JavaStringSafeHandle.cs new file mode 100644 index 00000000..d1087ccf --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Interop/JavaStringSafeHandle.cs @@ -0,0 +1,41 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System.Runtime.InteropServices; +using DxFeed.Graal.Net.Native.ErrorHandling; + +namespace DxFeed.Graal.Net.Native.Interop; + +/// +/// Represents a specialized safe handle for managing Java string objects within a .NET interop environment. +/// This class is responsible for ensuring the proper cleanup and +/// disposal of Java strings when dealing with native interop scenarios. +/// +/// +/// The class encapsulates a Java string handle +/// and provides mechanisms for retrieving its content as a .NET string and for releasing its native resources. +/// +internal abstract class JavaStringSafeHandle : JavaSafeHandle +{ + /// + /// Returns the Java string represented by the handle, converted to a .NET string. + /// + /// A .NET string representation of the Java string, or null if the handle is invalid. + public override string? ToString() => + Marshal.PtrToStringUTF8(handle); + + protected override void Release() => + ErrorCheck.NativeCall(CurrentThread, NativeRelease(CurrentThread, handle)); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_String_release")] + private static extern int NativeRelease( + nint thread, + nint handle); +} diff --git a/src/DxFeed.Graal.Net/Native/Interop/SafeHandleZeroIsInvalid.cs b/src/DxFeed.Graal.Net/Native/Interop/SafeHandleZeroIsInvalid.cs index 682c645d..5107067f 100644 --- a/src/DxFeed.Graal.Net/Native/Interop/SafeHandleZeroIsInvalid.cs +++ b/src/DxFeed.Graal.Net/Native/Interop/SafeHandleZeroIsInvalid.cs @@ -4,26 +4,38 @@ // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // +using System; using System.Runtime.InteropServices; namespace DxFeed.Graal.Net.Native.Interop; /// -/// Provides a base class for SafeHandle implementations in which the value of zero an invalid handle. +/// Represents an abstract base class for classes that interact with native handles. +/// In the context of this class, a handle with a value of zero is considered invalid or uninitialized. +/// The validity of the handle can be determined using the property. /// internal abstract class SafeHandleZeroIsInvalid : SafeHandle { + /// + /// Initializes a new instance of the class. + /// By default, this class assumes ownership of the handle. + /// protected SafeHandleZeroIsInvalid() - : base((nint)0, true) + : base(IntPtr.Zero, true) { } - protected SafeHandleZeroIsInvalid(bool ownsHandle) - : base((nint)0, ownsHandle) + protected SafeHandleZeroIsInvalid(bool ownHandle) + : base(IntPtr.Zero, ownHandle) { } - /// + /// + /// Gets a value indicating whether the handle is invalid. + /// + /// + /// true if the handle is considered invalid (i.e., has a value of zero); otherwise, false. + /// public override bool IsInvalid => - handle == (nint)0; + handle == IntPtr.Zero; } diff --git a/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileCollectorHandle.cs b/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileCollectorHandle.cs new file mode 100644 index 00000000..62cd64d3 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileCollectorHandle.cs @@ -0,0 +1,74 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System.Runtime.InteropServices; +using DxFeed.Graal.Net.Native.ErrorHandling; +using DxFeed.Graal.Net.Native.Interop; + +namespace DxFeed.Graal.Net.Native.Ipf.Handles; + +internal class InstrumentProfileCollectorHandle : JavaSafeHandle +{ + public static InstrumentProfileCollectorHandle Create() => + ErrorCheck.NativeCall(CurrentThread, NativeCreate(CurrentThread)); + + public long GetLastUpdateTime() => + ErrorCheck.NativeCall(CurrentThread, NativeGetLastUpdateTime(CurrentThread, this)); + + public IterableInstrumentProfileHandle View() => + ErrorCheck.NativeCall(CurrentThread, NativeView(CurrentThread, this)); + + public void AddUpdateListener(InstrumentProfileUpdateListenerHandle listener) => + ErrorCheck.NativeCall(CurrentThread, NativeAddUpdateListener(CurrentThread, this, listener)); + + public void RemoveUpdateListener(InstrumentProfileUpdateListenerHandle listener) => + ErrorCheck.NativeCall(CurrentThread, NativeRemoveUpdateListener(CurrentThread, this, listener)); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_InstrumentProfileCollector_new")] + private static extern InstrumentProfileCollectorHandle NativeCreate(nint thread); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_InstrumentProfileCollector_getLastUpdateTime")] + private static extern long NativeGetLastUpdateTime( + nint thread, + InstrumentProfileCollectorHandle collector); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_InstrumentProfileCollector_view")] + private static extern IterableInstrumentProfileHandle NativeView( + nint thread, + InstrumentProfileCollectorHandle collector); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_InstrumentProfileCollector_addUpdateListener")] + private static extern int NativeAddUpdateListener( + nint thread, + InstrumentProfileCollectorHandle collector, + InstrumentProfileUpdateListenerHandle listener); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_InstrumentProfileCollector_removeUpdateListener")] + private static extern int NativeRemoveUpdateListener( + nint thread, + InstrumentProfileCollectorHandle collector, + InstrumentProfileUpdateListenerHandle listener); +} diff --git a/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileReaderHandle.cs b/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileReaderHandle.cs deleted file mode 100644 index 577c6561..00000000 --- a/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileReaderHandle.cs +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright © 2022 Devexperts LLC. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. -// - -using System.Runtime.InteropServices; - -namespace DxFeed.Graal.Net.Native.Ipf.Handles; - -[StructLayout(LayoutKind.Sequential)] -internal readonly struct InstrumentProfileReaderHandle -{ - // ReSharper disable once MemberCanBePrivate.Global - public readonly JavaObjectHandle Handle; -} diff --git a/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileReaderSafeHandle.cs b/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileReaderSafeHandle.cs deleted file mode 100644 index 3798bfe8..00000000 --- a/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileReaderSafeHandle.cs +++ /dev/null @@ -1,158 +0,0 @@ -// -// Copyright © 2022 Devexperts LLC. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. -// - -using System; -using System.Runtime.InteropServices; -using DxFeed.Graal.Net.Native.Endpoint.Handles; -using DxFeed.Graal.Net.Native.ErrorHandling; -using DxFeed.Graal.Net.Native.Graal; -using DxFeed.Graal.Net.Native.Interop; - -namespace DxFeed.Graal.Net.Native.Ipf.Handles; - -internal sealed unsafe class InstrumentProfileReaderSafeHandle : SafeHandleZeroIsInvalid -{ - - private InstrumentProfileReaderSafeHandle(InstrumentProfileReaderHandle* handle) => - SetHandle((nint)handle); - - public static implicit operator InstrumentProfileReaderHandle*(InstrumentProfileReaderSafeHandle value) => - (InstrumentProfileReaderHandle*)value.handle; - - public static InstrumentProfileReaderSafeHandle Create() - { - var thread = Isolate.CurrentThread; - return new(ErrorCheck.NativeCall(thread, NativeCreate(thread))); - } - - public long GetLastModify() - { - var thread = Isolate.CurrentThread; - return ErrorCheck.NativeCall(thread, NativeGetLastModified(thread, this)); - } - - public bool WasComplete() - { - var thread = Isolate.CurrentThread; - return ErrorCheck.NativeCall(thread, NativeWasComplete(thread, this)) != 0; - } - - public ListNative* ReadFromFile(string address, string user, string password) - { - var thread = Isolate.CurrentThread; - return ErrorCheck.NativeCall(thread, ReadFromFile(thread, this, address, user, password)); - } - - public void IpfRelease(ListNative* ipf) - { - var thread = Isolate.CurrentThread; - ErrorCheck.NativeCall(thread, IpfRelease(thread, ipf)); - } - - protected override bool ReleaseHandle() - { - try - { - var thread = Isolate.CurrentThread; - ErrorCheck.NativeCall(thread, NativeRelease(thread, (InstrumentProfileReaderHandle*)handle)); - handle = (IntPtr)0; - return true; - } - catch (Exception e) - { - // ToDo Add a log entry. - Console.Error.WriteLine($"Exception in {GetType().Name} when releasing resource: {e}"); - } - - return false; - } - - [DllImport( - ImportInfo.DllName, - CallingConvention = CallingConvention.Cdecl, - CharSet = CharSet.Ansi, - EntryPoint = "dxfg_InstrumentProfileReader_new")] - private static extern InstrumentProfileReaderHandle* NativeCreate(nint thread); - - [DllImport( - ImportInfo.DllName, - CallingConvention = CallingConvention.Cdecl, - CharSet = CharSet.Ansi, - EntryPoint = "dxfg_JavaObjectHandler_release")] - private static extern int NativeRelease( - nint thread, - InstrumentProfileReaderHandle* handle); - - [DllImport( - ImportInfo.DllName, - CallingConvention = CallingConvention.Cdecl, - CharSet = CharSet.Ansi, - EntryPoint = "dxfg_InstrumentProfileReader_getLastModified")] - private static extern long NativeGetLastModified( - nint thread, - InstrumentProfileReaderHandle* handle); - - [DllImport( - ImportInfo.DllName, - CallingConvention = CallingConvention.Cdecl, - CharSet = CharSet.Ansi, - EntryPoint = "dxfg_InstrumentProfileReader_wasComplete")] - private static extern int NativeWasComplete( - nint thread, - InstrumentProfileReaderHandle* handle); - - [DllImport( - ImportInfo.DllName, - CallingConvention = CallingConvention.Cdecl, - CharSet = CharSet.Ansi, - ExactSpelling = true, - BestFitMapping = false, - ThrowOnUnmappableChar = true, - EntryPoint = "dxfg_InstrumentProfileReader_readFromFile")] - private static extern ListNative* ReadFromFile( - nint thread, - InstrumentProfileReaderHandle* handle, - [MarshalAs(UnmanagedType.LPUTF8Str)] string address); - - [DllImport( - ImportInfo.DllName, - CallingConvention = CallingConvention.Cdecl, - CharSet = CharSet.Ansi, - ExactSpelling = true, - BestFitMapping = false, - ThrowOnUnmappableChar = true, - EntryPoint = "dxfg_InstrumentProfileReader_readFromFile2")] - private static extern ListNative* ReadFromFile( - nint thread, - InstrumentProfileReaderHandle* handle, - [MarshalAs(UnmanagedType.LPUTF8Str)] string address, - [MarshalAs(UnmanagedType.LPUTF8Str)] string user, - [MarshalAs(UnmanagedType.LPUTF8Str)] string password); - - [DllImport( - ImportInfo.DllName, - CallingConvention = CallingConvention.Cdecl, - CharSet = CharSet.Ansi, - ExactSpelling = true, - BestFitMapping = false, - ThrowOnUnmappableChar = true, - EntryPoint = "dxfg_InstrumentProfileReader_resolveSourceURL")] - private static extern string ResolveSourceUrl( - nint thread, - [MarshalAs(UnmanagedType.LPUTF8Str)] string address); - - [DllImport( - ImportInfo.DllName, - CallingConvention = CallingConvention.Cdecl, - CharSet = CharSet.Ansi, - ExactSpelling = true, - BestFitMapping = false, - ThrowOnUnmappableChar = true, - EntryPoint = "dxfg_CList_InstrumentProfile_release")] - private static extern int IpfRelease( - nint thread, - ListNative* ipf); -} diff --git a/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileUpdateListenerHandle.cs b/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileUpdateListenerHandle.cs new file mode 100644 index 00000000..1114faa4 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Ipf/Handles/InstrumentProfileUpdateListenerHandle.cs @@ -0,0 +1,29 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System.Runtime.InteropServices; +using DxFeed.Graal.Net.Native.ErrorHandling; +using DxFeed.Graal.Net.Native.Interop; + +namespace DxFeed.Graal.Net.Native.Ipf.Handles; + +internal abstract unsafe class InstrumentProfileUpdateListenerHandle : JavaFinalizeSafeHandle +{ + public static InstrumentProfileUpdateListenerHandle Create( + delegate* unmanaged[Cdecl] listener, + GCHandle handle) => + ErrorCheck.NativeCall(CurrentThread, NativeCreate(CurrentThread, listener, GCHandle.ToIntPtr(handle))); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_InstrumentProfileUpdateListener_new")] + private static extern InstrumentProfileUpdateListenerHandle NativeCreate( + nint thread, + delegate* unmanaged[Cdecl] listener, + nint handle); +} diff --git a/src/DxFeed.Graal.Net/Native/Ipf/Handles/IpfMapper.cs b/src/DxFeed.Graal.Net/Native/Ipf/Handles/IpfMapper.cs deleted file mode 100644 index 7ad04ac3..00000000 --- a/src/DxFeed.Graal.Net/Native/Ipf/Handles/IpfMapper.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright © 2022 Devexperts LLC. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. -// - -using DxFeed.Graal.Net.Ipf; - -namespace DxFeed.Graal.Net.Native.Ipf.Handles; - -internal class IpfMapper -{ - public static unsafe InstrumentProfile Convert(IpfNative* eventType) - { - var ipf = new InstrumentProfile(); - ipf.Type = eventType->type; - ipf.Symbol = eventType->symbol; - ipf.Description = eventType->description; - ipf.LocalSymbol = eventType->localSymbol; - ipf.LocalDescription = eventType->localDescription; - ipf.Country = eventType->country; - ipf.OPOL = eventType->opol; - ipf.ExchangeData = eventType->exchangeData; - ipf.Exchanges = eventType->exchanges; - ipf.Currency = eventType->currency; - ipf.BaseCurrency = eventType->baseCurrency; - ipf.CFI = eventType->cfi; - ipf.ISIN = eventType->isin; - ipf.SEDOL = eventType->sedol; - ipf.CUSIP = eventType->cusip; - ipf.ICB = eventType->icb; - ipf.SIC = eventType->sic; - ipf.Multiplier = eventType->multiplier; - ipf.Product = eventType->product; - ipf.Underlying = eventType->underlying; - ipf.SPC = eventType->spc; - ipf.AdditionalUnderlyings = eventType->additionalUnderlyings; - ipf.MMY = eventType->mmy; - ipf.Expiration = eventType->expiration; - ipf.LastTrade = eventType->lastTrade; - ipf.Strike = eventType->strike; - ipf.OptionType = eventType->optionType; - ipf.ExpirationStyle = eventType->expirationStyle; - ipf.SettlementStyle = eventType->settlementStyle; - ipf.PriceIncrements = eventType->priceIncrements; - ipf.TradingHours = eventType->tradingHours; - return ipf; - } - -} diff --git a/src/DxFeed.Graal.Net/Native/Ipf/Handles/IpfNative.cs b/src/DxFeed.Graal.Net/Native/Ipf/Handles/IpfNative.cs deleted file mode 100644 index 1d3f0c36..00000000 --- a/src/DxFeed.Graal.Net/Native/Ipf/Handles/IpfNative.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright © 2022 Devexperts LLC. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. -// - -using System.Runtime.InteropServices; -using DxFeed.Graal.Net.Native.Events.Market; -using DxFeed.Graal.Net.Native.Interop; - -namespace DxFeed.Graal.Net.Native.Ipf.Handles; - -[StructLayout(LayoutKind.Sequential)] -internal readonly record struct IpfNative( - StringNative type, - StringNative symbol, - StringNative description, - StringNative localSymbol, - StringNative localDescription, - StringNative country, - StringNative opol, - StringNative exchangeData, - StringNative exchanges, - StringNative currency, - StringNative baseCurrency, - StringNative cfi, - StringNative isin, - StringNative sedol, - StringNative cusip, - int icb, - int sic, - double multiplier, - StringNative product, - StringNative underlying, - double spc, - StringNative additionalUnderlyings, - StringNative mmy, - int expiration, - int lastTrade, - double strike, - StringNative optionType, - StringNative expirationStyle, - StringNative settlementStyle, - StringNative priceIncrements, - StringNative tradingHours); diff --git a/src/DxFeed.Graal.Net/Native/Ipf/Handles/IterableInstrumentProfileHandle.cs b/src/DxFeed.Graal.Net/Native/Ipf/Handles/IterableInstrumentProfileHandle.cs new file mode 100644 index 00000000..dd93e5c9 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Ipf/Handles/IterableInstrumentProfileHandle.cs @@ -0,0 +1,49 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; +using System.Runtime.InteropServices; +using DxFeed.Graal.Net.Ipf; +using DxFeed.Graal.Net.Native.ErrorHandling; +using DxFeed.Graal.Net.Native.Interop; + +namespace DxFeed.Graal.Net.Native.Ipf.Handles; + +internal abstract unsafe class IterableInstrumentProfileHandle : JavaSafeHandle +{ + public bool HasNext() => + ErrorCheck.NativeCall(CurrentThread, NativeHasNext(CurrentThread, this)) != 0; + + public InstrumentProfile? Next() + { + var i = NativeNext(CurrentThread, this); + return i != null ? InstrumentProfileMapper.Convert(i) : null; + } + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + ExactSpelling = true, + BestFitMapping = false, + ThrowOnUnmappableChar = true, + EntryPoint = "dxfg_Iterable_InstrumentProfile_hasNext")] + private static extern int NativeHasNext( + nint thread, + IterableInstrumentProfileHandle iterable); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + ExactSpelling = true, + BestFitMapping = false, + ThrowOnUnmappableChar = true, + EntryPoint = "dxfg_Iterable_InstrumentProfile_next")] + private static extern InstrumentProfileNative* NativeNext( + nint thread, + IterableInstrumentProfileHandle iterable); +} diff --git a/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileCollectorNative.cs b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileCollectorNative.cs new file mode 100644 index 00000000..5dfdebee --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileCollectorNative.cs @@ -0,0 +1,20 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using DxFeed.Graal.Net.Native.Ipf.Handles; + +namespace DxFeed.Graal.Net.Native.Ipf; + +internal class InstrumentProfileCollectorNative +{ + private readonly InstrumentProfileCollectorHandle handle; + + private InstrumentProfileCollectorNative(InstrumentProfileCollectorHandle builderHandle) => + handle = builderHandle; + + public static InstrumentProfileCollectorNative Create() => + new(InstrumentProfileCollectorHandle.Create()); +} diff --git a/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileListNative.cs b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileListNative.cs new file mode 100644 index 00000000..a8c48f20 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileListNative.cs @@ -0,0 +1,59 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using DxFeed.Graal.Net.Ipf; +using DxFeed.Graal.Net.Native.ErrorHandling; +using DxFeed.Graal.Net.Native.Interop; + +namespace DxFeed.Graal.Net.Native.Ipf; + +internal class InstrumentProfileListNative : JavaSafeHandle +{ + public unsafe List ToList() + { + var result = new List(); + var profiles = (ListNative*)handle; + for (var i = 0; i < profiles->Size; i++) + { + result.Add(InstrumentProfileMapper.Convert(profiles->Elements[i])); + } + + return result; + } + + protected override bool ReleaseHandle() + { + try + { + ErrorCheck.NativeCall(CurrentThread, NativeRelease(CurrentThread, handle)); + handle = (IntPtr)0; + return true; + } + catch (Exception e) + { + // ToDo Add a log entry. + Console.Error.WriteLine($"Exception in {GetType().Name} when releasing resource: {e}"); + } + + return false; + } + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + ExactSpelling = true, + BestFitMapping = false, + ThrowOnUnmappableChar = true, + EntryPoint = "dxfg_CList_InstrumentProfile_release")] + private static extern int NativeRelease( + nint thread, + nint instrumentProfileList); +} diff --git a/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileMapper.cs b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileMapper.cs new file mode 100644 index 00000000..a0fb5c38 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileMapper.cs @@ -0,0 +1,48 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using DxFeed.Graal.Net.Ipf; + +namespace DxFeed.Graal.Net.Native.Ipf; + +internal static class InstrumentProfileMapper +{ + public static unsafe InstrumentProfile Convert(InstrumentProfileNative* eventType) => + new() + { + Type = eventType->Type!, + Symbol = eventType->Symbol!, + Description = eventType->Description!, + LocalSymbol = eventType->LocalSymbol!, + LocalDescription = eventType->LocalDescription!, + Country = eventType->Country!, + OPOL = eventType->OPOL!, + ExchangeData = eventType->ExchangeData!, + Exchanges = eventType->Exchanges!, + Currency = eventType->Currency!, + BaseCurrency = eventType->BaseCurrency!, + CFI = eventType->CFI!, + ISIN = eventType->ISIN!, + SEDOL = eventType->SEDOL!, + CUSIP = eventType->CUSIP!, + ICB = eventType->ICB, + SIC = eventType->SIC, + Multiplier = eventType->Multiplier, + Product = eventType->Product!, + Underlying = eventType->Underlying!, + SPC = eventType->SPC, + AdditionalUnderlyings = eventType->AdditionalUnderlyings!, + MMY = eventType->MMY!, + Expiration = eventType->Expiration, + LastTrade = eventType->LastTrade, + Strike = eventType->Strike, + OptionType = eventType->OptionType!, + ExpirationStyle = eventType->ExpirationStyle!, + SettlementStyle = eventType->SettlementStyle!, + PriceIncrements = eventType->PriceIncrements!, + TradingHours = eventType->TradingHours!, + }; +} diff --git a/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileNative.cs b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileNative.cs new file mode 100644 index 00000000..6cd3cc31 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileNative.cs @@ -0,0 +1,45 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System.Runtime.InteropServices; +using DxFeed.Graal.Net.Ipf; +using DxFeed.Graal.Net.Native.Interop; + +namespace DxFeed.Graal.Net.Native.Ipf; + +[StructLayout(LayoutKind.Sequential)] +internal readonly record struct InstrumentProfileNative( + StringNative Type, + StringNative Symbol, + StringNative Description, + StringNative LocalSymbol, + StringNative LocalDescription, + StringNative Country, + StringNative OPOL, + StringNative ExchangeData, + StringNative Exchanges, + StringNative Currency, + StringNative BaseCurrency, + StringNative CFI, + StringNative ISIN, + StringNative SEDOL, + StringNative CUSIP, + int ICB, + int SIC, + double Multiplier, + StringNative Product, + StringNative Underlying, + double SPC, + StringNative AdditionalUnderlyings, + StringNative MMY, + int Expiration, + int LastTrade, + double Strike, + StringNative OptionType, + StringNative ExpirationStyle, + StringNative SettlementStyle, + StringNative PriceIncrements, + StringNative TradingHours); diff --git a/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileReaderNative.cs b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileReaderNative.cs index 9a203d5f..1eefb938 100644 --- a/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileReaderNative.cs +++ b/src/DxFeed.Graal.Net/Native/Ipf/InstrumentProfileReaderNative.cs @@ -4,53 +4,89 @@ // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // -using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Threading; using DxFeed.Graal.Net.Ipf; using DxFeed.Graal.Net.Native.Interop; -using DxFeed.Graal.Net.Native.Ipf.Handles; +using Microsoft.Win32.SafeHandles; +using static DxFeed.Graal.Net.Native.ErrorHandling.ErrorCheck; namespace DxFeed.Graal.Net.Native.Ipf; -public class InstrumentProfileReaderNative : IDisposable +internal class InstrumentProfileReaderNative : JavaSafeHandle { - private readonly InstrumentProfileReaderSafeHandle handle; - - private InstrumentProfileReaderNative(InstrumentProfileReaderSafeHandle builderHandle) => - handle = builderHandle; + public static string? ResolveSourceUrl(string address) + { + using var str = NativeCall(CurrentThread, NativeResolveSourceUrl(CurrentThread, address)); + return str.ToString(); + } public static InstrumentProfileReaderNative Create() => - new(InstrumentProfileReaderSafeHandle.Create()); + NativeCall(CurrentThread, NativeCreate(CurrentThread)); public long GetLastModified() => - handle.GetLastModify(); + NativeCall(CurrentThread, NativeGetLastModified(CurrentThread, this)); public bool WasComplete() => - handle.WasComplete(); + NativeCall(CurrentThread, NativeWasComplete(CurrentThread, this)) != 0; - public List ReadFromFile(string address, string user, string password) + public List ReadFromFile(string address, string? user, string? password) { - var result = new List(); - unsafe - { - ListNative* profiles = null; - try - { - profiles = handle.ReadFromFile(address, user, password); - for (var i = 0; i < profiles->Size; i++) - { - result.Add(IpfMapper.Convert(profiles->Elements[i])); - } - } - finally - { - handle.IpfRelease(profiles); - } - } - - return result; + using var result = NativeCall(CurrentThread, NativeReadFromFile(CurrentThread, this, address, user, password)); + return result.ToList(); } - public void Dispose() => - handle.Dispose(); + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + ExactSpelling = true, + BestFitMapping = false, + ThrowOnUnmappableChar = true, + EntryPoint = "dxfg_InstrumentProfileReader_resolveSourceURL")] + private static extern JavaStringSafeHandle NativeResolveSourceUrl( + nint thread, + [MarshalAs(UnmanagedType.LPUTF8Str)] string address); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_InstrumentProfileReader_new")] + private static extern InstrumentProfileReaderNative NativeCreate(nint thread); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_InstrumentProfileReader_getLastModified")] + private static extern long NativeGetLastModified( + nint thread, + InstrumentProfileReaderNative reader); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_InstrumentProfileReader_wasComplete")] + private static extern int NativeWasComplete( + nint thread, + InstrumentProfileReaderNative reader); + + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + ExactSpelling = true, + BestFitMapping = false, + ThrowOnUnmappableChar = true, + EntryPoint = "dxfg_InstrumentProfileReader_readFromFile2")] + private static extern InstrumentProfileListNative NativeReadFromFile( + nint thread, + InstrumentProfileReaderNative reader, + [MarshalAs(UnmanagedType.LPUTF8Str)] string address, + [MarshalAs(UnmanagedType.LPUTF8Str)] string? user, + [MarshalAs(UnmanagedType.LPUTF8Str)] string? password); } diff --git a/src/DxFeed.Graal.Net/Native/Subscription/SubscriptionNative.cs b/src/DxFeed.Graal.Net/Native/Subscription/SubscriptionNative.cs index fb69db02..e7c43e79 100644 --- a/src/DxFeed.Graal.Net/Native/Subscription/SubscriptionNative.cs +++ b/src/DxFeed.Graal.Net/Native/Subscription/SubscriptionNative.cs @@ -23,7 +23,6 @@ namespace DxFeed.Graal.Net.Native.Subscription; ///
internal sealed unsafe class SubscriptionNative : IDisposable { - private static readonly SubFinalizeFunc OnSubFinalize = Finalize; private readonly object _eventListenerHandleLock = new(); private EventListenerHandle* _eventListenerHandle; private SubscriptionHandle* _subHandle; @@ -134,11 +133,6 @@ public void Dispose() private static nint GetCurrentThread() => IsolateThread.CurrentThread; - private static void Finalize(nint isolate, nint userData) - { - // ToDo Implement finalize callback. - } - private void ReleaseUnmanagedResources() { try @@ -197,7 +191,7 @@ public static void AddEventListener( nint thread, SubscriptionHandle* subHandle, EventListenerHandle* eventListenerHandle) => - ErrorCheck.NativeCall(thread, NativeAddEventListener(thread, subHandle, eventListenerHandle, OnSubFinalize, 0)); + ErrorCheck.NativeCall(thread, NativeAddEventListener(thread, subHandle, eventListenerHandle)); public static void RemoveEventListener( nint thread, @@ -336,9 +330,7 @@ private static extern int NativeReleaseEventListener( private static extern int NativeAddEventListener( nint thread, SubscriptionHandle* subHandle, - EventListenerHandle* listenerHandle, - SubFinalizeFunc endpointFinalize, - nint userData); + EventListenerHandle* listenerHandle); [DllImport( ImportInfo.DllName, diff --git a/src/DxFeed.Graal.Net/Utils/AttributeUtil.cs b/src/DxFeed.Graal.Net/Utils/AttributeUtil.cs index aeb67c89..751e79e3 100644 --- a/src/DxFeed.Graal.Net/Utils/AttributeUtil.cs +++ b/src/DxFeed.Graal.Net/Utils/AttributeUtil.cs @@ -10,26 +10,48 @@ namespace DxFeed.Graal.Net.Utils; /// -/// Provides utility methods for manipulating an . +/// Provides utility methods for retrieving custom . /// public static class AttributeUtil { /// - /// Generic version of . - /// Retrieves a custom attribute applied to a member of a type. - /// Parameters specify the member, and the type of the custom attribute to search for. + /// A generic version of for . + /// Retrieves a custom attribute applied to the type. + /// Parameters specify the type with the custom attribute, the type of the custom attribute to search for. /// Doesn't look up the element's ancestors for custom attributes. /// - /// - /// An object derived from the class that describes - /// a constructor, event, field, method, or property member of a class. + /// The type to which the custom attribute is applied. + /// The type, or a base type, of the custom attribute to search for. + /// + /// A reference to the single custom attribute of type T that is applied to element, + /// or null if there is no such attribute. + /// + /// type is null. + /// More than one of the requested attributes was found. + /// A custom attribute type cannot be loaded. + public static T? GetCustomAttribute(Type type) + where T : Attribute => + GetCustomAttribute(type, false); + + /// + /// A generic version of for . + /// Retrieves a custom attribute applied to the type. + /// Parameters specify the type with the custom attribute, the type of the custom attribute to search for, + /// and whether to search ancestors of the type. + /// + /// The type to which the custom attribute is applied. + /// + /// If true, specifies to also search the ancestors of element for custom attributes. /// /// The type, or a base type, of the custom attribute to search for. /// - /// A reference to the single custom attribute of type T that is applied to element, + /// A reference to the single custom attribute of type T that is applied to element, /// or null if there is no such attribute. /// - public static T? GetCustomAttribute(Type member) + /// type is null. + /// More than one of the requested attributes was found. + /// A custom attribute type cannot be loaded. + public static T? GetCustomAttribute(Type type, bool inherit) where T : Attribute => - (T?)Attribute.GetCustomAttribute(member, typeof(T), false); + (T?)Attribute.GetCustomAttribute(type, typeof(T), inherit); } diff --git a/src/DxFeed.Graal.Net/Utils/BitUtil.cs b/src/DxFeed.Graal.Net/Utils/BitUtil.cs index 03af016e..b42bcfb9 100644 --- a/src/DxFeed.Graal.Net/Utils/BitUtil.cs +++ b/src/DxFeed.Graal.Net/Utils/BitUtil.cs @@ -9,28 +9,28 @@ namespace DxFeed.Graal.Net.Utils; /// /// A collection of utility methods for bitwise operations. ///
-/// Porting Java class com.dxfeed.event.market.Util. +/// Ports the Java class com.dxfeed.event.market.Util. ///
public static class BitUtil { /// /// Extracts bits from the specified value. /// - /// The specified value. + /// The specified value from which bits are extracted. /// The bit mask. - /// The bit shift. + /// The number of positions to shift the value. /// The extracted bits. public static int GetBits(int value, int mask, int shift) => (value >> shift) & mask; /// - /// Sets bits to the specified value. + /// Sets bits in the specified value. /// - /// The specified value. + /// The specified value in which bits are to be set. /// The bit mask. - /// The bit shift. - /// The bits set. - /// Returns a value with bits set. + /// The number of positions to shift the value. + /// The bits to be set. + /// A value with the specified bits set. public static int SetBits(int value, int mask, int shift, int bits) => (value & ~(mask << shift)) | ((bits & mask) << shift); } diff --git a/src/DxFeed.Graal.Net/Utils/DayUtil.cs b/src/DxFeed.Graal.Net/Utils/DayUtil.cs index bd77e20e..a085ae9e 100644 --- a/src/DxFeed.Graal.Net/Utils/DayUtil.cs +++ b/src/DxFeed.Graal.Net/Utils/DayUtil.cs @@ -5,9 +5,7 @@ // using System; - -// Arithmetic Expressions Must Declare Precedence. False positive. -#pragma warning disable SA1407 +using System.Diagnostics.CodeAnalysis; namespace DxFeed.Graal.Net.Utils; @@ -15,14 +13,26 @@ namespace DxFeed.Graal.Net.Utils; /// A collection of static utility methods for manipulation of day id, /// that is the number of days since Unix epoch of January 1, 1970. ///
-/// Porting Java class com.devexperts.util.DayUtil. +/// Ports the Java class com.devexperts.util.DayUtil. ///
public static class DayUtil { - private static readonly int[] DayOfYear = { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; + /// + /// Represent the cumulative number of days that have elapsed in a year up to the beginning of each month, + /// considering a non-leap year. + ///
    + ///
  • [0] - a placeholder since month indices usually start from 1 for January.
  • + ///
  • [1] - January 1st, 0 days have passed before January.
  • + ///
  • [2] - February 1st, 31 days (January) have passed.
  • + ///
  • [3] - March 1st, 59 days (January + February) have passed.
  • + ///
  • ...
  • + ///
  • [13] - represents the total number of days in a non-leap year up to the end of December.
  • + ///
+ ///
+ private static readonly int[] DAY_OF_YEAR = { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; /// - /// Returns day identifier for specified year, month and day in Gregorian calendar. + /// Gets the day identifier for specified year, month and day in Gregorian calendar. /// The day identifier is defined as the number of days since Unix epoch of January 1, 1970. /// Month must be between 1 and 12 inclusive. /// Year and day might take arbitrary values assuming proleptic Gregorian calendar. @@ -33,9 +43,9 @@ public static class DayUtil /// /// The year. /// The month between 1 and 12 inclusive. - /// The dat. + /// The day. /// The day id. - /// f the month is less than 1 or greater than 12. + /// If the month is less than 1 or greater than 12. public static int GetDayIdByYearMonthDay(int year, int month, int day) { if (month is < 1 or > 12) @@ -43,9 +53,10 @@ public static int GetDayIdByYearMonthDay(int year, int month, int day) throw new ArgumentException($"Invalid month {month}", nameof(month)); } - var dayOfYear = DayOfYear[month] + day - 1; + var dayOfYear = DAY_OF_YEAR[month] + day - 1; if (month > 2 && year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) { + // Leap year. dayOfYear++; } @@ -57,7 +68,7 @@ public static int GetDayIdByYearMonthDay(int year, int month, int day) } /// - /// Returns day identifier for specified yyyymmdd integer in Gregorian calendar. + /// Gets the day identifier for specified yyyymmdd integer in Gregorian calendar. /// The day identifier is defined as the number of days since Unix epoch of January 1, 1970. /// The yyyymmdd integer is equal to yearSign * (abs(year) * 10000 + month * 100 + day), /// where year, month, and day are in Gregorian calendar, @@ -73,13 +84,15 @@ public static int GetDayIdByYearMonthDay(int year, int month, int day) /// DayUtil.GetDayIdByYearMonthDay(19700102) == 1 /// /// + /// If the month is less than 1 or greater than 12. + [SuppressMessage("ReSharper", "ArrangeRedundantParentheses", Justification = "Readability")] public static int GetDayIdByYearMonthDay(int yyyymmdd) => yyyymmdd >= 0 - ? GetDayIdByYearMonthDay(yyyymmdd / 10000, yyyymmdd / 100 % 100, yyyymmdd % 100) - : GetDayIdByYearMonthDay(-(-yyyymmdd / 10000), -yyyymmdd / 100 % 100, -yyyymmdd % 100); + ? GetDayIdByYearMonthDay(yyyymmdd / 10000, (yyyymmdd / 100) % 100, yyyymmdd % 100) + : GetDayIdByYearMonthDay(-(-yyyymmdd / 10000), (-yyyymmdd / 100) % 100, -yyyymmdd % 100); /// - /// Gets yyyymmdd integer in Gregorian calendar for a specified day identifier. + /// Gets the integer yyyymmdd in Gregorian calendar for a specified day identifier. /// The day identifier is defined as the number of days since Unix epoch of January 1, 1970. /// The result is equal to: /// yearSign * (abs(year) * 10000 + month * 100 + day) @@ -95,31 +108,32 @@ public static int GetDayIdByYearMonthDay(int yyyymmdd) => /// DayUtil.GetYearMonthDayByDayId(1) == 19700102 /// /// + [SuppressMessage("ReSharper", "SuggestVarOrType_BuiltInTypes", Justification = "Readability")] public static int GetYearMonthDayByDayId(int dayId) { // This shifts the epoch back to astronomical year -4800. - var j = dayId + 2472632; - var g = MathUtil.Div(j, 146097); - var dg = j - (g * 146097); - var c = ((dg / 36524) + 1) * 3 / 4; - var dc = dg - (c * 36524); - var b = dc / 1461; - var db = dc - (b * 1461); - var a = ((db / 365) + 1) * 3 / 4; - var da = db - (a * 365); + int j = dayId + 2472632; + int g = MathUtil.Div(j, 146097); + int dg = j - (g * 146097); + int c = ((dg / 36524) + 1) * 3 / 4; + int dc = dg - (c * 36524); + int b = dc / 1461; + int db = dc - (b * 1461); + int a = ((db / 365) + 1) * 3 / 4; + int da = db - (a * 365); // This is the integer number of full years elapsed since March 1, 4801 BC at 00:00 UTC. - var y = (g * 400) + (c * 100) + (b * 4) + a; + int y = (g * 400) + (c * 100) + (b * 4) + a; // This is the integer number of full months elapsed since the last March 1 at 00:00 UTC. - var m = (((da * 5) + 308) / 153) - 2; + int m = (((da * 5) + 308) / 153) - 2; // This is the number of days elapsed since day 1 of the month at 00:00 UTC. - var d = da - ((m + 4) * 153 / 5) + 122; - var yyyy = y - 4800 + ((m + 2) / 12); - var mm = ((m + 2) % 12) + 1; - var dd = d + 1; - var yyyymmdd = (MathUtil.Abs(yyyy) * 10000) + (mm * 100) + dd; + int d = da - ((m + 4) * 153 / 5) + 122; + int yyyy = y - 4800 + ((m + 2) / 12); + int mm = ((m + 2) % 12) + 1; + int dd = d + 1; + int yyyymmdd = (MathUtil.Abs(yyyy) * 10000) + (mm * 100) + dd; return yyyy >= 0 ? yyyymmdd : -yyyymmdd; } diff --git a/src/DxFeed.Graal.Net/Utils/EnumUtil.cs b/src/DxFeed.Graal.Net/Utils/EnumUtil.cs index 06f055b2..5c3363a1 100644 --- a/src/DxFeed.Graal.Net/Utils/EnumUtil.cs +++ b/src/DxFeed.Graal.Net/Utils/EnumUtil.cs @@ -10,7 +10,7 @@ namespace DxFeed.Graal.Net.Utils; /// -/// Provides utility methods for manipulating . +/// Provides utility methods for manipulating enumerations. /// public static class EnumUtil { @@ -33,7 +33,7 @@ public static T ValueOf(T value) return value; } - throw new ArgumentException($"{typeof(T)} has no value({value})", nameof(value)); + throw new ArgumentException($"{typeof(T)} has no value: {value}", nameof(value)); } /// @@ -62,7 +62,34 @@ public static int GetCountValues() Enum.GetValues(typeof(T)).Length; /// - /// Creates an array containing elements of the specified enum type T, of the specified length. + /// Creates an array containing elements of the specified enum type T, + /// where the length of the array is rounded to the nearest power of two, + /// which is greater than or equal to the number of enum values. + /// If the calculated length is greater than the number of enum values, + /// the remaining elements are filled with a default value. + /// The idea is to quickly convert an int value to an enum value by using the array index. + /// However, the size of the array is limited by a bit mask. If the number of enum values + /// isn't a power of two, the array is expanded and the additional elements are filled with the default value. + /// + /// + /// The default value that will fill the elements of an array + /// if its size is greater than the number of enum values. + /// + /// The specified enum type. + /// The created array. + /// + /// The elements of the array are sorted by the binary values of the enumeration constants + /// (that is, by their unsigned magnitude). + /// + /// + public static T[] CreateEnumBitMaskArrayByValue(T defaultValue) + where T : struct, Enum => + CreateEnumArrayByValue( + defaultValue, + (int)BitOperations.RoundUpToPowerOf2((uint)GetCountValues())); + + /// + /// Creates an array containing elements of the specified enum type T, of the specified length. /// If the length is greater than the number of enum values, /// the remaining elements are filled with a default value, otherwise array are truncated. /// @@ -91,31 +118,4 @@ public static T[] CreateEnumArrayByValue(T defaultValue, int length) Array.Copy(values, result, Math.Min(values.Length, length)); return result; } - - /// - /// Creates an array containing elements of the specified enum type T, - /// where the length of the array is rounded to the nearest power of two, - /// which is greater than or equal to the number of enum values. - /// If the calculated length is greater than the number of enum values, - /// the remaining elements are filled with a default value. - /// The idea is to quickly convert an int value to an enum value, simply by array index. - /// But the size of the array is limited by a bit mask, so if the number of enum values - /// is not a multiple of a power of two, you need to expand the array and fill in new elements with a default value. - /// - /// - /// The default value that will fill the elements of an array - /// if its size is greater than the number of enum values. - /// - /// The specified enum type. - /// The created array. - /// - /// The elements of the array are sorted by the binary values of the enumeration constants - /// (that is, by their unsigned magnitude). - /// - /// - public static T[] CreateEnumBitMaskArrayByValue(T defaultValue) - where T : struct, Enum => - CreateEnumArrayByValue( - defaultValue, - (int)BitOperations.RoundUpToPowerOf2((uint)GetCountValues())); } diff --git a/tests/DxFeed.Graal.Net.Tests/Api/DXEndpointTest.cs b/tests/DxFeed.Graal.Net.Tests/Api/DXEndpointTest.cs index 4b9b61d0..a1e6426a 100644 --- a/tests/DxFeed.Graal.Net.Tests/Api/DXEndpointTest.cs +++ b/tests/DxFeed.Graal.Net.Tests/Api/DXEndpointTest.cs @@ -62,7 +62,7 @@ public void GetFeedReturnsSameObject() } [Test] - public void GetGePublisherReturnsSameObject() + public void GetPublisherReturnsSameObject() { var publisher = GetInstance().GetPublisher(); Assert.That(publisher, Is.EqualTo(GetInstance().GetPublisher())); diff --git a/tests/DxFeed.Graal.Net.Tests/DxFeed.Graal.Net.Tests.csproj b/tests/DxFeed.Graal.Net.Tests/DxFeed.Graal.Net.Tests.csproj index d2014998..7096e06d 100644 --- a/tests/DxFeed.Graal.Net.Tests/DxFeed.Graal.Net.Tests.csproj +++ b/tests/DxFeed.Graal.Net.Tests/DxFeed.Graal.Net.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/tests/DxFeed.Graal.Net.Tests/Utils/AttributeUtilTest.cs b/tests/DxFeed.Graal.Net.Tests/Utils/AttributeUtilTest.cs new file mode 100644 index 00000000..a9cf4065 --- /dev/null +++ b/tests/DxFeed.Graal.Net.Tests/Utils/AttributeUtilTest.cs @@ -0,0 +1,79 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using DxFeed.Graal.Net.Utils; + +namespace DxFeed.Graal.Net.Tests.Utils; + +// Classes should not be empty. It's a test class. +#pragma warning disable S2094 + +// Unused private types or members should be removed. False positive. +#pragma warning disable S1144 + +[TestFixture] +public class AttributeUtilTests +{ + [AttributeUsage(AttributeTargets.Class, Inherited = true)] + private class DummyAttribute : Attribute + { + public DummyAttribute(string value) => + Value = value; + + public string Value { get; } + } + + [Dummy("BaseDummyClass")] + private class BaseDummyClass + { + } + + private class DummyInheritedClass : BaseDummyClass + { + } + + private class DummyNonAttributedClass + { + } + + [Test] + public void ReturnsAttributeForAttributedClass() + { + var attr = AttributeUtil.GetCustomAttribute(typeof(BaseDummyClass)); + Assert.That(attr, Is.Not.Null); + Assert.That(attr?.Value, Is.EqualTo("BaseDummyClass")); + } + + [Test] + public void AttributeWithInheritFlagReturnsAttributeFromBase() + { + var attr = AttributeUtil.GetCustomAttribute(typeof(DummyInheritedClass), true); + Assert.That(attr, Is.Not.Null); + Assert.That(attr?.Value, Is.EqualTo("BaseDummyClass")); + } + + [Test] + public void AttributeWithoutInheritFlagReturnsNullForInherited() + { + var attr = AttributeUtil.GetCustomAttribute(typeof(DummyInheritedClass), false); + Assert.That(attr, Is.Null); + + // Inherit flag is false by default. + attr = AttributeUtil.GetCustomAttribute(typeof(DummyInheritedClass)); + Assert.That(attr, Is.Null); + } + + [Test] + public void NonAttributedClassReturnsNull() + { + var attr = AttributeUtil.GetCustomAttribute(typeof(DummyNonAttributedClass)); + Assert.That(attr, Is.Null); + } + + [Test] + public void WhenTypeIsNullThrowsArgumentNullException() => + Assert.Throws(() => AttributeUtil.GetCustomAttribute(null!)); +} diff --git a/tests/DxFeed.Graal.Net.Tests/Utils/DayUtilTest.cs b/tests/DxFeed.Graal.Net.Tests/Utils/DayUtilTest.cs new file mode 100644 index 00000000..1f44344d --- /dev/null +++ b/tests/DxFeed.Graal.Net.Tests/Utils/DayUtilTest.cs @@ -0,0 +1,79 @@ +// +// Copyright © 2022 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using DxFeed.Graal.Net.Utils; +using NodaTime; + +namespace DxFeed.Graal.Net.Tests.Utils; + +[TestFixture] +public class DayUtilTest +{ + [Test] + public void CheckEpoch() => + Assert.Multiple(() => + { + Assert.That(DayUtil.GetDayIdByYearMonthDay(1970, 1, 1), Is.EqualTo(0)); + Assert.That(DayUtil.GetDayIdByYearMonthDay(19700101), Is.EqualTo(0)); + Assert.That(DayUtil.GetYearMonthDayByDayId(0), Is.EqualTo(19700101)); + }); + + [Test] + public void CheckRandomGregorianDates() + { + var minYear = CalendarSystem.Gregorian.MinYear; + var maxYear = CalendarSystem.Gregorian.MaxYear; + var r = new Random(1); + for (var i = 0; i < 100000; ++i) + { + var year = minYear + r.Next(maxYear - minYear + 1); + var month = 1 + r.Next(12); + var day = 1 + r.Next(CalendarSystem.Gregorian.GetDaysInMonth(year, month)); + var yyyymmdd = (year < 0 ? -1 : 1) * ((MathUtil.Abs(year) * 10000) + (month * 100) + day); + var dayId = Period.DaysBetween( + new LocalDate(1970, 1, 1, CalendarSystem.Gregorian), + new LocalDate(year, month, day, CalendarSystem.Gregorian)); + Assert.Multiple(() => + { + Assert.That(DayUtil.GetDayIdByYearMonthDay(year, month, day), Is.EqualTo(dayId)); + Assert.That(DayUtil.GetDayIdByYearMonthDay(yyyymmdd), Is.EqualTo(dayId)); + Assert.That(DayUtil.GetYearMonthDayByDayId(dayId), Is.EqualTo(yyyymmdd)); + }); + } + } + + [Test] + public void CheckLeapYear() + { + const int month = 2; + const int day = 29; + for (var year = CalendarSystem.Gregorian.MinYear; year <= CalendarSystem.Gregorian.MaxYear; ++year) + { + if (!CalendarSystem.Gregorian.IsLeapYear(year)) + { + continue; + } + + var dayId = DayUtil.GetDayIdByYearMonthDay(year, month, day); + var date = new ZonedDateTime(Instant.FromUnixTimeSeconds(0), DateTimeZone.Utc, CalendarSystem.Gregorian); + date += Duration.FromDays(dayId); + Assert.Multiple(() => + { + Assert.That(date.Year, Is.EqualTo(year)); + Assert.That(date.Month, Is.EqualTo(month)); + Assert.That(date.Day, Is.EqualTo(day)); + }); + } + } + + [Test] + public void WhenInvalidMonthThrowsArgumentException() + { + Assert.Throws(() => DayUtil.GetDayIdByYearMonthDay(1970, -1, 1)); + Assert.Throws(() => DayUtil.GetDayIdByYearMonthDay(1970, 0, 1)); + Assert.Throws(() => DayUtil.GetDayIdByYearMonthDay(1970, 13, 1)); + } +}