+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dxfeed-api/pom.xml b/dxfeed-api/pom.xml
new file mode 100644
index 000000000..b5e173a8f
--- /dev/null
+++ b/dxfeed-api/pom.xml
@@ -0,0 +1,160 @@
+
+
+
+
+ QD
+ com.devexperts.qd
+ 3.250
+ ../pom.xml
+
+ 4.0.0
+
+ dxfeed-api
+ jar
+ dxFeed API classes
+
+
+ ${project.artifactId}-${project.version}-javadoc.jar
+ dxfeed-javadoc
+ false
+ false
+
+
+
+
+ com.devexperts.qd
+ dxlib
+ ${project.version}
+
+
+ com.devexperts.qd
+ dxfeed-promise
+ ${project.version}
+
+
+
+
+
+
+ org.codehaus.mojo
+ jaxb2-maven-plugin
+
+
+ schemagen
+ compile
+
+ schemagen
+
+
+
+
+
+
+ http://schema.dxfeed.com/event
+ e
+ dxfeed-event.xsd
+
+
+
+ com/dxfeed/event/market/package-info.java
+ com/dxfeed/event/market/Quote.java
+ com/dxfeed/event/market/Trade.java
+ com/dxfeed/event/market/TradeETH.java
+ com/dxfeed/event/market/Summary.java
+ com/dxfeed/event/market/Profile.java
+ com/dxfeed/event/market/TimeAndSale.java
+ com/dxfeed/event/market/Order.java
+ com/dxfeed/event/market/SpreadOrder.java
+ com/dxfeed/event/candle/package-info.java
+ com/dxfeed/event/candle/Candle.java
+ com/dxfeed/event/misc/package-info.java
+ com/dxfeed/event/misc/Configuration.java
+ com/dxfeed/event/misc/Message.java
+ com/dxfeed/event/option/package-info.java
+ com/dxfeed/event/option/Greeks.java
+ com/dxfeed/event/option/Series.java
+ com/dxfeed/event/option/TheoPrice.java
+ com/dxfeed/event/option/Underlying.java
+
+
+
+
+ maven-assembly-plugin
+
+
+ package
+
+ single
+
+
+
+
+ xsd-assembly.xml
+
+
+
+ maven-shade-plugin
+
+
+ package
+
+ shade
+
+
+ true
+ true
+ false
+
+
+
+
+ dxlib.jar
+
+
+
+
+
+ com.devexperts.qd:dxfeed-promise
+
+
+
+
+
+
+
+ maven-javadoc-plugin
+
+ dxFeed API ${project.version}
+
+ http://docs.dxfeed.com/dxlib/api/
+
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+ true
+
+
+
+ com.devexperts.qd:dxfeed-promise
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dxfeed-api/src/main/java/com/dxfeed/annotation/ClassValueMapping.java b/dxfeed-api/src/main/java/com/dxfeed/annotation/ClassValueMapping.java
new file mode 100644
index 000000000..fe83d87c3
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/annotation/ClassValueMapping.java
@@ -0,0 +1,25 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation for methods and constructors used to map reference types to int QD field. This annotation may be used only for:
+ *
+ * Constructors with 1 int parameter;
+ * Instance methods without parameters returning int;
+ * Static methods with 1 int parameter returning target reference type;
+ * Static methods with 1 target type parameter returning int.
+ *
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface ClassValueMapping {
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldMapping.java b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldMapping.java
new file mode 100644
index 000000000..bcaa27d08
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldMapping.java
@@ -0,0 +1,38 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation for {@code getXXX()} methods on properties that need custom mapping to QD fields.
+ * Non-annotated pairs of {@code getXXX/setXXX} methods are mapped automatically by default.
+ * Overridden methods are not mapped implicitly.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.METHOD)
+public @interface EventFieldMapping {
+ /**
+ * Returns QD field name where this event class field maps to.
+ * @return QD field name where this event class field maps to.
+ */
+ String fieldName() default "";
+
+ /**
+ * Returns mapping type.
+ * @return mapping type.
+ */
+ EventFieldType type() default EventFieldType.DEFAULT;
+
+ /**
+ * Returns true if field is optional, false otherwise.
+ * @return true if field is optional, false otherwise.
+ */
+ boolean optional() default false;
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java
new file mode 100644
index 000000000..b3767def7
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java
@@ -0,0 +1,71 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.annotation;
+
+/**
+ * Semantic type of event class field. It is used to instruct the annotation processor to use appropriate serialized form.
+ */
+public enum EventFieldType {
+ /**
+ * Auto-detect type.
+ */
+ DEFAULT,
+
+ /**
+ * This field is not mapped to QD.
+ */
+ TRANSIENT,
+
+ /**
+ * This type can be used for {@code long} properties that store time in milliseconds since Java Epoch.
+ * It will be mapped to a QD field that keeps seconds since Java Epoch (milliseconds will be lost).
+ */
+ TIME,
+
+ /**
+ * This type can be used for {@code int} properties that store number of days since Java Epoch.
+ */
+ DATE,
+
+ /**
+ * This type can be used for all primitive properties and reference types using {@link ClassValueMapping} annotaiton.
+ * It will be mapped to an integer QD field.
+ */
+ INT,
+
+ /**
+ * This type can be used for all primitive properties.
+ * It will be mapped to a decimal QD field.
+ */
+ DECIMAL,
+
+ /**
+ * This type can be used for {@code char} properties.
+ * It will be mapped to a char QD field.
+ */
+ CHAR,
+
+ /**
+ * This type can be used for {@link String} properties.
+ * It will be mapped to a short-string QD field that can store up to four ASCII characters.
+ */
+ SHORT_STRING,
+
+ /**
+ * This type can be used for {@link String} properties.
+ * It will be mapped to a string QD field.
+ */
+ STRING,
+
+ /**
+ * This type can be used for all reference properties.
+ * It will be mapped to a serial object QD field.
+ */
+ MARSHALLED
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/annotation/EventTypeMapping.java b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventTypeMapping.java
new file mode 100644
index 000000000..93d643672
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventTypeMapping.java
@@ -0,0 +1,24 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation for classes that are part of custom data scheme.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface EventTypeMapping {
+ /**
+ * Returns QD record name where this event class maps to.
+ * @return QD record name where this event class maps to.
+ */
+ String recordName() default "";
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/annotation/package.html b/dxfeed-api/src/main/java/com/dxfeed/annotation/package.html
new file mode 100644
index 000000000..26156c22c
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/annotation/package.html
@@ -0,0 +1,13 @@
+
+
+
+Main package for dxFeed API mapping annotations.
+
+
\ No newline at end of file
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/DXEndpoint.java b/dxfeed-api/src/main/java/com/dxfeed/api/DXEndpoint.java
new file mode 100644
index 000000000..9b4826822
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/DXEndpoint.java
@@ -0,0 +1,813 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api;
+
+import java.beans.PropertyChangeListener;
+import java.util.*;
+import java.util.concurrent.Executor;
+
+import com.devexperts.services.Service;
+import com.devexperts.services.Services;
+import com.dxfeed.api.osub.WildcardSymbol;
+import com.dxfeed.event.EventType;
+import com.dxfeed.event.market.Quote;
+import com.dxfeed.ondemand.OnDemandService;
+
+/**
+ * Manages network connections to {@link DXFeed feed} or
+ * {@link DXPublisher publisher}. There are per-JVM ready-to-use singleton instances
+ * that are available with
+ * {@link #getInstance()} and
+ * {@link #getInstance(Role)} methods
+ * as well as
+ * factory methods {@link #create()} and
+ * {@link #create(Role)}, and a number of configuration methods. Advanced
+ * properties can be configured using
+ * {@link #newBuilder() newBuilder()}.{@link Builder#withProperty(String, String) withProperty(key, value)}.{@link Builder#build() build()}.
+ *
+ * Sample usage
+ *
+ * The following code creates new feed instance that is connected to DXFeed demo server.
+ *
+ * DXFeed feed = {@link DXEndpoint DXEndpoint}.{@link DXEndpoint#create() create}()
+ * .{@link #user(String) user}("demo").{@link #password(String) password}("demo")
+ * .{@link DXEndpoint#connect(String) connect}("demo.dxfeed.com:7300")
+ * .{@link DXEndpoint#getFeed() getFeed}();
+ *
+ * See {@link DXFeed} for details on how to subscribe to symbols and receive events.
+ *
+ * Endpoint role
+ *
+ * Each endpoint has a role that is specified on its creation and cannot be changed afterwards.
+ * The default factory method {@link #create()} creates an endpoint with a {@link Role#FEED FEED} role.
+ * Endpoints with other roles are created with {@link #create(Role)} factory method. Endpoint role is
+ * represented by {@link Role DXEndpoint.Role} enumeration.
+ *
+ * Endpoint role defines the behavior of its {@link #connect(String) connect} method:
+ *
+ * {@link Role#FEED FEED} connects to the remote data feed provider and is optimized for real-time or
+ * delayed data processing (this is a default role ).
+ * {@link #getFeed()} method returns
+ * a feed object that subscribes to this remote data feed provider and receives events from it.
+ * When event processing threads cannot keep up (don't have enough CPU time), data is dynamically conflated to
+ * minimize latency between received events and their processing time.
+ * For example:
+ *
+ * {@code DXEndpoint.create().connect("demo.dxfeed.com:7300").getFeed()} returns a
+ * demo feed from dxFeed with sample market quotes.
+ * {@code DXEndpoint.create().connect("localhost:7400").getFeed()} returns a feed
+ * that is connected to a publisher that is running on the same host. See example
+ * below.
+ * {@code DXEndpoint.create().connect("file:demo-sample.data").getFeed()} returns a feed
+ * that is connected to a "demo-sample.data" file and plays back it as if it was received in real time.
+ * File playback is supported only when optional "qds-file.jar " is present in the classpath.
+ *
+ * This endpoint is automatically connected to the configured data feed as explained in
+ * default properties section .
+ *
+ *
+ * {@link Role#ON_DEMAND_FEED ON_DEMAND_FEED} is similar to {@link Role#FEED FEED}, but it is designed to be used with
+ * {@link OnDemandService} for historical data replay only. It is configured with
+ * default properties , but is not connected automatically
+ * to the data provider until {@link OnDemandService#replay(Date, double) OnDemandService.replay}
+ * method is invoked.
+ *
+ * {@link Role#STREAM_FEED STREAM_FEED} is similar to {@link Role#FEED FEED} and also
+ * connects to the remote data feed provider, but is designed for bulk
+ * parsing of data from files. {@link DXEndpoint#getFeed()} method
+ * returns feed object that subscribes to the data from the opened files and receives events from them.
+ * Events from the files are not conflated and are processed as fast as possible.
+ * Note, that in this role, {@link DXFeed#getLastEvent} method does not work and
+ * time-series subscription is not supported.
+ * File playback is supported only when optional "qds-file.jar " is present in the classpath.
+ * For example:
+ *
+ * DXEndpoint endpoint = DXEndpoint.create(DXEndpoint.Role.{@link Role#STREAM_FEED STREAM_FEED});
+ * {@link DXFeed DXFeed} feed = endpoint.{@link DXEndpoint#getFeed() getFeed}();
+ * creates a feed that is ready to read data from file as soon as the following code is invoked:
+ *
+ * endpoint.{@link #connect(String) connect}("file:demo-sample.data[speed=max]");
+ * "[speed=max]" clause forces to the file reader to play back all
+ * the data from "demo-sample.data" file as fast as data subscribers are processing it.
+ *
+ * {@link Role#PUBLISHER PUBLISHER} connects to the remote publisher hub (also known as multiplexor) or
+ * creates a publisher on the local host. {@link #getPublisher()} method returns
+ * a publisher object that publishes events to all connected feeds. For example:
+ *
+ * {@code DXEndpoint.create(DXEndpoint.Role.PUBLISHER).connect(":7400").getPublisher()} returns a
+ * publisher that is waiting for connections on TCP/IP port 7400. The published events will be
+ * delivered to all feeds that are connected to this publisher.
+ *
+ * This endpoint is automatically connected to the configured data feed as explained in
+ * default properties section .
+ *
+ * {@link Role#LOCAL_HUB LOCAL_HUB} creates a local hub without ability to establish network connections.
+ * Events that are published via {@link #getPublisher() publisher} are delivered to local
+ * {@link #getFeed() feed} only.
+ *
+ *
+ *
+ * Endpoint state
+ *
+ * Each endpoint has a state that can be retrieved with {@link #getState() getState} method.
+ * When endpoint is created with any role and default address is not specified in
+ * default properties , then
+ * it is not connected to any remote endpoint.
+ * Its state is {@link State#NOT_CONNECTED NOT_CONNECTED}.
+ *
+ * {@link Role#FEED Feed} and {@link Role#PUBLISHER publisher} endpoints can connect to remote endpoints
+ * of the opposite role. Connection is initiated by {@link #connect(String) connect} method.
+ * The endpoint state becomes {@link State#CONNECTING CONNECTING}.
+ *
+ *
When the actual connection to the remote endpoint is established, the endpoint state becomes
+ * {@link State#CONNECTED CONNECTED}.
+ *
+ *
Network connections can temporarily break and return endpoint back into {@link State#CONNECTING CONNECTING} state.
+ * File connections can be completed and return endpoint into {@link State#NOT_CONNECTED NOT_CONNECTED} state.
+ *
+ *
Connection to the remote endpoint can be terminated with {@link #disconnect() disconnect} method.
+ * The endpoint state becomes {@link State#NOT_CONNECTED NOT_CONNECTED}.
+ *
+ *
Endpoint can be closed with {@link #close() close} method. The endpoint state
+ * becomes {@link State#CLOSED CLOSED}. This is a final state. All connection are terminated and
+ * all internal resources that are held by this endpoint are freed.
+ * No further connections can be initiated.
+ *
+ *
Event times
+ *
+ * The {@link EventType#getEventTime() EventType.getEventTime} on received events is available only when the
+ * endpoint is created with with {@link #DXENDPOINT_EVENT_TIME_PROPERTY DXENDPOINT_EVENT_TIME_PROPERTY} property and
+ * the data source has embedded event times. This is typically true only for data events
+ * that are read from historical tape files (see above) and from {@link OnDemandService OnDemandService}.
+ * Events that are coming from a network connections do not have an embedded event time information and
+ * event time is not available for them anyway.
+ *
+ *
+ *
+ * Default properties are loaded from "dxfeed.properties" or "dxpublisher.properties" file depending on
+ * the {@link Role role} of created endpoint. "dxfeed.properties" is used for
+ * {@link Role#FEED FEED} and {@link Role#ON_DEMAND_FEED ON_DEMAND_FEED},
+ * "dxpublisher.properties" is used for
+ * {@link Role#PUBLISHER PUBLISHER}.
+ * {@link Role#STREAM_FEED STREAM_FEED} and {@link Role#LOCAL_HUB LOCAL_HUB} do not support properties file.
+ *
+ * The location of this file can be specified using
+ * {@link Builder#withProperty(String, String) withProperty}({@link #DXFEED_PROPERTIES_PROPERTY}, path) or
+ * {@link Builder#withProperty(String, String) withProperty}({@link #DXPUBLISHER_PROPERTIES_PROPERTY}, path)
+ * correspondingly. When the location of this file is not explicitly specified using
+ * {@link Builder#withProperty(String, String) withProperty} method, then the file path is taken from a system
+ * property with the corresponding name.
+ *
+ *
When the path to the above properties file is not provided, then a resource named "dxfeed.properties" or
+ * "dxpublisher.properties" is loaded from classpath. When classpath is set to "." (current directory),
+ * it means that the corresponding file can be placed into the current directory with any need to specify additional
+ * properties.
+ *
+ *
Defaults for individual properties can be also provided using system properties when they are not specified
+ * in the configuration file. System properties override configuration loaded from classpath resource, but don't
+ * override configuration from the user-specified configuration file.
+ *
+ *
The {@link #NAME_PROPERTY} is the exception from the above rule. It is never loaded from system properties.
+ * It can be only specified in configuration file or programmatically. There is a convenience
+ * {@link Builder#withName(String) Builder.withName} method for it. It is recommended to assign short and
+ * meaningful endpoint names when multiple endpoints are used in the same JVM. The name of the endpoint shall
+ * describe its role in the particular application.
+ *
+ *
Note, that individual properties that are programmatically set using {@link Builder#withProperty(String, String) withProperty}
+ * method always take precedence.
+ *
+ *
{@link Role#FEED FEED} and {@link Role#PUBLISHER PUBLISHER} automatically establish connection on creation
+ * when the corresponding {@link #DXFEED_ADDRESS_PROPERTY} or {@link #DXPUBLISHER_ADDRESS_PROPERTY} is specified.
+ *
+ *
Permanent subscription
+ *
+ * Endpoint properties can define permanent subscription for specific sets of symbols and event types in
+ * the data feed, so that {@link DXFeed} methods like {@link DXFeed#getLastEventIfSubscribed getLastEventIfSubscribed},
+ * {@link DXFeed#getIndexedEventsIfSubscribed getIndexedEventsIfSubscribed}, and
+ * {@link DXFeed#getTimeSeriesIfSubscribed getTimeSeriesIfSubscribed} can be used without a need to create a
+ * separate {@link DXFeedSubscription DXFeedSubscription} object. Please, contact dxFeed support for details
+ * on the required configuration.
+ *
+ * Threads and locks
+ *
+ * This class is thread-safe and can be used concurrently from multiple threads without external synchronization.
+ *
+ * Implementation details
+ *
+ * dxFeed API is implemented on top of QDS. dxFeed API classes itself are in "dxfeed-api.jar ", but
+ * their implementation is in "qds.jar ". You need to have "qds.jar " in your classpath
+ * in order to use dxFeed API.
+ */
+public abstract class DXEndpoint {
+ /**
+ * Defines property for endpoint name that is used to distinguish multiple endpoints
+ * in the same JVM in logs and in other diagnostic means.
+ * Use {@link Builder#withProperty(String, String)} method.
+ * This property is also changed by {@link Builder#withName(String)} method.
+ */
+ public static final String NAME_PROPERTY = "name";
+
+ /**
+ * Defines path to a file with properties for an endpoint with role
+ * {@link Role#FEED FEED} or {@link Role#ON_DEMAND_FEED ON_DEMAND_FEED}.
+ * By default, properties a loaded from a classpath resource named "dxfeed.properties".
+ * @see Builder#withProperty(String, String) Builder.withProperty
+ */
+ public static final String DXFEED_PROPERTIES_PROPERTY = "dxfeed.properties";
+
+ /**
+ * Defines default connection address for an endpoint with role
+ * {@link Role#FEED FEED} or {@link Role#ON_DEMAND_FEED ON_DEMAND_FEED}.
+ * Connection is established to this address by role {@link Role#FEED FEED} as soon as endpoint is created,
+ * while role {@link Role#ON_DEMAND_FEED ON_DEMAND_FEED} waits until
+ * {@link OnDemandService#replay(Date, double) OnDemandService.replay} is invoked before connecting.
+ *
+ * By default, without this property, connection is not established until
+ * {@link #connect(String) connect(address)} is invoked.
+ *
+ * Credentials for access to premium services may be configured with
+ * {@link #DXFEED_USER_PROPERTY} and {@link #DXFEED_PASSWORD_PROPERTY}.
+ *
+ * @see Builder#withProperty(String, String) Builder.withProperty
+ */
+ public static final String DXFEED_ADDRESS_PROPERTY = "dxfeed.address";
+
+ /**
+ * Defines default user name for an endpoint with role
+ * {@link Role#FEED FEED} or {@link Role#ON_DEMAND_FEED ON_DEMAND_FEED}.
+ * @see #user(String)
+ */
+ public static final String DXFEED_USER_PROPERTY = "dxfeed.user";
+
+ /**
+ * Defines default password for an endpoint with role
+ * {@link Role#FEED FEED} or {@link Role#ON_DEMAND_FEED ON_DEMAND_FEED}.
+ * @see #password(String)
+ */
+ public static final String DXFEED_PASSWORD_PROPERTY = "dxfeed.password";
+
+ /**
+ * Defines thread pool size for an endpoint with role {@link Role#FEED FEED}.
+ * By default, the thread pool size is equal to the number of available processors.
+ * @see Builder#withProperty(String, String) Builder.withProperty
+ */
+ public static final String DXFEED_THREAD_POOL_SIZE_PROPERTY = "dxfeed.threadPoolSize";
+
+ /**
+ * Defines data aggregation period an endpoint with role {@link Role#FEED FEED} that
+ * limits the rate of data notifications. For example, setting the value of this property
+ * to "0.1s" limits notification to once every 100ms (at most 10 per second).
+ * @see Builder#withProperty(String, String) Builder.withProperty
+ */
+ public static final String DXFEED_AGGREGATION_PERIOD_PROPERTY = "dxfeed.aggregationPeriod";
+
+ /**
+ * Set this property to {@code true} to turns on wildcard support.
+ * By default, the endpoint does not support wildcards. This property is needed for
+ * {@link WildcardSymbol} support and for the use of "tape:..." address in {@link DXPublisher}.
+ */
+ public static final String DXFEED_WILDCARD_ENABLE_PROPERTY = "dxfeed.wildcard.enable";
+
+ /**
+ * Defines path to a file with properties for an endpoint with role {@link Role#PUBLISHER PUBLISHER}.
+ * By default, properties a loaded from a classpath resource named "dxpublisher.properties".
+ * @see Builder#withProperty(String, String) Builder.withProperty
+ */
+ public static final String DXPUBLISHER_PROPERTIES_PROPERTY = "dxpublisher.properties";
+
+ /**
+ * Defines default connection address for an endpoint with role {@link Role#PUBLISHER PUBLISHER}.
+ * Connection is established to this address as soon as endpoint is created.
+ * By default, connection is not established until {@link #connect(String) connect(address)} is invoked.
+ * @see Builder#withProperty(String, String) Builder.withProperty
+ */
+ public static final String DXPUBLISHER_ADDRESS_PROPERTY = "dxpublisher.address";
+
+ /**
+ * Defines thread pool size for an endpoint with role {@link Role#PUBLISHER PUBLISHER}.
+ * By default, the thread pool size is equal to the number of available processors.
+ * @see Builder#withProperty(String, String) Builder.withProperty
+ */
+ public static final String DXPUBLISHER_THREAD_POOL_SIZE_PROPERTY = "dxpublisher.threadPoolSize";
+
+ /**
+ * Set this property to {@code true} to enable {@link EventType#getEventTime() event time} support.
+ * By default, the endpoint does not support event time.
+ *
+ *
The event time is available only when the corresponding {@link DXEndpoint} is created
+ * with this property and
+ * the data source has embedded event times. This is typically true only for data events
+ * that are read from historical tape files and from {@link OnDemandService OnDemandService}.
+ * Events that are coming from a network connections do not have an embedded event time information and
+ * event time is not available for them anyway.
+ *
+ *
Use this property if you need to work with historical data coming from files
+ * or from {@link OnDemandService OnDemandService} or writing data with times to file via
+ * {@link DXPublisher} using "tape:..." address.
+ *
+ */
+ public static final String DXENDPOINT_EVENT_TIME_PROPERTY = "dxendpoint.eventTime";
+
+ /**
+ * Set this property to to store all {@link com.dxfeed.event.LastingEvent lasting}
+ * and {@link com.dxfeed.event.IndexedEvent indexed} events even when there is no subscription on them.
+ * By default, the endpoint stores only events from subscriptions. It works in the same way both
+ * for {@link DXFeed} and {@link DXPublisher}.
+ *
+ *
Use this property with extreme care,
+ * since API does not currently provide any means to remove those events from the storage and there might
+ * be an effective memory leak if the spaces of symbols on which events are published grows without bound.
+ */
+ public static final String DXENDPOINT_STORE_EVERYTHING_PROPERTY = "dxendpoint.storeEverything";
+
+ /**
+ * Set this property to {@code true} to turn on nanoseconds precision business time.
+ * By default, this feature is turned off.
+ * Business time in most events is available with
+ * millisecond precision by default, while {@link Quote Quote} events business
+ * {@link Quote#getTime() time} is available with seconds precision.
+ *
+ *
This method provides a higher-level control than turning on individual properties that are responsible
+ * for nano-time via {@link #DXSCHEME_ENABLED_PROPERTY_PREFIX}. The later can be used to override of fine-time
+ * nano-time support for individual fields. Setting this property to {@code true} is essentially
+ * equivalent to setting
+ *
+ * dxscheme.enabled.Sequence=*
+ * dxscheme.enabled.TimeNanoPart=*
+ *
+ */
+ public static final String DXSCHEME_NANO_TIME_PROPERTY = "dxscheme.nanoTime";
+
+ /**
+ * Defines whether a specified field from the scheme should be enabled instead of it's default behaviour.
+ * Use it according to following format:
+ *
+ * {@code dxscheme.enabled.=}
+ *
+ * For example, {@code dxscheme.enabled.TimeNanoPart=Trade} enables {@code NanoTimePart} internal field
+ * only in {@link com.dxfeed.event.market.Trade Trade} events.
+ *
+ *
There is a shortcut for turning on nano-time support using {@link #DXSCHEME_NANO_TIME_PROPERTY}.
+ */
+ public static final String DXSCHEME_ENABLED_PROPERTY_PREFIX = "dxscheme.enabled.";
+
+ /**
+ * Represents the role of endpoint that was specified during its {@link DXEndpoint#create() creation}.
+ *
+ * @see DXEndpoint
+ */
+ public enum Role {
+ /**
+ * {@code FEED} endpoint connects to the remote data feed provider and is optimized for real-time or
+ * delayed data processing (this is a default role ). {@link DXEndpoint#getFeed()} method
+ * returns feed object that subscribes to the remote data feed provider and receives events from it.
+ * When event processing threads cannot keep up (don't have enough CPU time), data is dynamically conflated to
+ * minimize latency between received events and their processing time.
+ *
+ *
This endpoint is automatically connected to the configured data feed as explained in
+ * default properties section .
+ */
+ FEED,
+
+ /**
+ * {@code ON_DEMAND_FEED} endpoint is similar to {@link #FEED}, but it is designed to be used with
+ * {@link OnDemandService} for historical data replay only. It is configured with
+ * default properties , but is not connected automatically
+ * to the data provider until {@link OnDemandService#replay(Date, double) OnDemandService.replay}
+ * method is invoked.
+ *
+ *
{@code ON_DEMAND_FEED} endpoint cannot be connected to an ordinary data feed at all.
+ * {@link OnDemandService#stopAndResume()} will have a similar effect to {@link OnDemandService#stopAndClear()}.
+ *
+ * @see OnDemandService
+ */
+ ON_DEMAND_FEED,
+
+ /**
+ * {@code STREAM_FEED} endpoint is similar to {@link #FEED} and also connects to the remote data feed provider,
+ * but is designed for bulk parsing of data from files. {@link DXEndpoint#getFeed()} method
+ * returns feed object that subscribes to the data from the opened files and receives events from them.
+ * Events from the files are not conflated and are processed as fast as possible.
+ * Note, that in this role, {@link DXFeed#getLastEvent} method does not work.
+ */
+ STREAM_FEED,
+
+ /**
+ * {@code PUBLISHER} endpoint connects to the remote publisher hub (also known as multiplexor) or
+ * creates a publisher on the local host. {@link DXEndpoint#getPublisher()} method returns
+ * a publisher object that publishes events to all connected feeds.
+ * Note, that in this role, {@link DXFeed#getLastEvent} method does not work and
+ * time-series subscription is not supported.
+ *
+ *
This endpoint is automatically connected to the configured data feed as explained in
+ * default properties section .
+ */
+ PUBLISHER,
+
+ /**
+ * {@code LOCAL_HUB} endpoint is a local hub without ability to establish network connections.
+ * Events that are published via {@link DXEndpoint#getPublisher() publisher} are delivered to local
+ * {@link DXEndpoint#getFeed() feed} only.
+ */
+ LOCAL_HUB
+ }
+
+ /**
+ * Represents the current state of endpoint.
+ *
+ * @see DXEndpoint
+ */
+ public enum State {
+ /**
+ * Endpoint was created by is not connected to remote endpoints.
+ */
+ NOT_CONNECTED,
+
+ /**
+ * The {@link DXEndpoint#connect(String) connect} method was called to establish connection to remove endpoint,
+ * but connection is not actually established yet or was lost.
+ */
+ CONNECTING,
+
+ /**
+ * The connection to remote endpoint is established.
+ */
+ CONNECTED,
+
+ /**
+ * Endpoint was {@link DXEndpoint#close() closed}.
+ */
+ CLOSED
+ }
+
+ /**
+ * Protected constructor for implementations of {@code DXEndpoint}.
+ */
+ protected DXEndpoint() {}
+
+ private static final EnumMap INSTANCES = new EnumMap<>(Role.class);
+
+ /**
+ * Returns a default application-wide singleton instance of DXEndpoint with a {@link Role#FEED FEED} role.
+ * Most applications use only a single data-source and should rely on this method to get one.
+ * This method creates an endpoint on the first use with a default
+ * configuration as explained in
+ * default properties section of {@link DXEndpoint} class documentation.
+ * You can provide configuration via classpath or via system properties as explained there.
+ *
+ * This is a shortcut to
+ * {@link #getInstance(Role) getInstance}({@link DXEndpoint}.{@link DXEndpoint.Role Role}.{@link DXEndpoint.Role#FEED FEED}).
+ * @see #getInstance(Role)
+ */
+ public static DXEndpoint getInstance() {
+ return getInstance(Role.FEED);
+ }
+
+ /**
+ * Returns a default application-wide singleton instance of DXEndpoint for a specific role.
+ * Most applications use only a single data-source and should rely on this method to get one.
+ * This method creates an endpoint with the corresponding role on the first use with a default
+ * configuration as explained in
+ * default properties section of {@link DXEndpoint} class documentation.
+ * You can provide configuration via classpath or via system properties as explained there.
+ *
+ *
The configuration does not have to include an address. You can use {@link #connect(String) connect(addresss)}
+ * and {@link #disconnect()} methods on the instance that is returned by this method to programmatically
+ * establish and tear-down connection to a user-provided address.
+ *
+ *
If you need a fully programmatic configuration and/or multiple endpoints of the same role in your
+ * application, then create a custom instance of {@link DXEndpoint} with
+ * {@link DXEndpoint#newBuilder() DXEndoint.newBuilder()} method, configure it,
+ * and use {@link Builder#build() build()} method.
+ * @throws NullPointerException if role is null.
+ */
+ public static DXEndpoint getInstance(Role role) {
+ synchronized (INSTANCES) {
+ DXEndpoint instance = INSTANCES.get(role);
+ if (instance == null) {
+ instance = newBuilder().withRole(role).build();
+ INSTANCES.put(role, instance);
+ }
+ return instance;
+ }
+ }
+
+ /**
+ * Creates new {@link Builder} instance.
+ * Use {@link Builder#build()} to build an instance of {@link DXEndpoint} when
+ * all configuration properties were set.
+ *
+ * @return the created endpoint builder.
+ */
+ public static Builder newBuilder() {
+ Builder builder = Services.createService(Builder.class, null, null);
+ if (builder == null)
+ throw new IllegalStateException("There is no " + Builder.class + " implementation service in class path");
+ return builder;
+ }
+
+ /**
+ * Creates an endpoint with {@link Role#FEED FEED} role.
+ * The result of this method is the same as {@code create(DXEndpoint.Role.FEED)}.
+ * This is a shortcut to
+ * {@link #newBuilder() newBuilder()}.{@link Builder#build() build()}
+ *
+ * @return the created endpoint.
+ */
+ public static DXEndpoint create() {
+ return newBuilder().build();
+ }
+
+ /**
+ * Creates an endpoint with a specified role.
+ * This is a shortcut to
+ * {@link #newBuilder() newBuilder()}.{@link Builder#withRole(DXEndpoint.Role) withRole(role)}.{@link Builder#build() build()}
+ *
+ * @param role the role.
+ * @return the created endpoint.
+ */
+ public static DXEndpoint create(Role role) {
+ return newBuilder().withRole(role).build();
+ }
+
+ /**
+ * Returns the role of this endpoint.
+ *
+ * @return the role.
+ *
+ * @see DXEndpoint
+ */
+ public abstract Role getRole();
+
+ /**
+ * Returns the state of this endpoint.
+ *
+ * @return the state.
+ *
+ * @see DXEndpoint
+ */
+ public abstract State getState();
+
+ /**
+ * Adds listener that is notified about changes in {@link #getState() state} property.
+ * Notification will be performed using this endpoint's {@link #executor(Executor) executor}.
+ *
+ *
Installed listener can be removed with
+ * {@link #removeStateChangeListener(PropertyChangeListener) removeStateChangeListener} method.
+ *
+ * @param listener the listener to add.
+ */
+ public abstract void addStateChangeListener(PropertyChangeListener listener);
+
+ /**
+ * Removes listener that is notified about changes in {@link #getState() state} property.
+ * It removes the listener that was previously installed with
+ * {@link #addStateChangeListener(PropertyChangeListener) addStateChangeListener} method.
+ * @param listener the listener to remove.
+ */
+ public abstract void removeStateChangeListener(PropertyChangeListener listener);
+
+ /**
+ * Changes executor that is used for notifications.
+ * By default, the thread pool with the size equal to the number of available processors is used.
+ * The number of threads in the default pool can be configured using
+ * {@link #DXFEED_THREAD_POOL_SIZE_PROPERTY DXFEED_THREAD_POOL_SIZE_PROPERTY}
+ * for endpoints with role
+ * {@link Role#FEED FEED} and {@link Role#ON_DEMAND_FEED ON_DEMAND_FEED} and
+ * with {@link #DXPUBLISHER_THREAD_POOL_SIZE_PROPERTY DXPUBLISHER_THREAD_POOL_SIZE_PROPERTY}
+ * for endpoints with role
+ * {@link Role#PUBLISHER PUBLISHER}.
+ * See also default properties section .
+ *
+ * @param executor the executor.
+ * @return this {@code DXEndpoint}.
+ * @throws NullPointerException if executor is null.
+ */
+ public abstract DXEndpoint executor(Executor executor);
+
+ /**
+ * Changes user name for this endpoint.
+ * This method shall be called before {@link #connect(String) connect} together
+ * with {@link #password(String) password} to configure service access credentials.
+ *
+ * @param user user name.
+ * @return this {@code DXEndpoint}.
+ * @throws NullPointerException if user is null.
+ */
+ public abstract DXEndpoint user(String user);
+
+ /**
+ * Changes password for this endpoint.
+ * This method shall be called before {@link #connect(String) connect} together
+ * with {@link #user(String) user} to configure service access credentials.
+ *
+ * @param password password.
+ * @return this {@code DXEndpoint}.
+ * @throws NullPointerException if password is null.
+ */
+ public abstract DXEndpoint password(String password);
+
+ /**
+ * Connects to the specified remove address. Previously established connections are closed if
+ * the new address is different from the old one.
+ * This method does nothing if address does not change or if this endpoint is {@link State#CLOSED CLOSED}.
+ * The endpoint {@link #getState() state} immediately becomes {@link State#CONNECTING CONNECTING} otherwise.
+ *
+ *
The address string is provided with the market data vendor agreement.
+ * Use "demo.dxfeed.com:7300" for a demo quote feed.
+ *
+ *
The simplest address strings have the following format:
+ *
+ * {@code host:port} to establish a TCP/IP connection.
+ * {@code :port} to listen for a TCP/IP connection with a plain socket connector (good for up to a
+ * few hundred of connections).
+ *
+ *
+ * For premium services access credentials must be configured before invocation of {@code connect} method
+ * using {@link #user(String) user} and {@link #password(String) password} methods.
+ *
+ *
More information on address strings is available via the command-line QDS help tool.
+ * Use the following command line to retrieve it:
+ *
java -jar qds-tools.jar help address
+ *
+ * This method does not wait until connection actually gets established . The actual connection establishment
+ * happens asynchronously after the invocation of this method. However, this method waits until notification
+ * about state transition from {@link State#NOT_CONNECTED State.NOT_CONNECTED} to {@link State#CONNECTING State.CONNECTING}
+ * gets processed by all {@link PropertyChangeListener listeners} that were installed via
+ * {@link #addStateChangeListener(PropertyChangeListener) addStateChangeListener} method.
+ *
+ * @param address the data source address.
+ * @return this {@code DXEndpoint}.
+ * @throws NullPointerException if address is null.
+ * @throws IllegalArgumentException if address string is malformed.
+ */
+ public abstract DXEndpoint connect(String address);
+
+ /**
+ * Terminates all remote network connections.
+ * This method does nothing if this endpoint is {@link State#CLOSED CLOSED}.
+ * The endpoint {@link #getState() state} immediately becomes {@link State#NOT_CONNECTED NOT_CONNECTED} otherwise.
+ *
+ *
This method does not release all resources that are associated with this endpoint.
+ * Use {@link #close()} method to release all resources.
+ */
+ public abstract void disconnect();
+
+ /**
+ * Terminates all remote network connections and clears stored data.
+ * This method does nothing if this endpoint is {@link State#CLOSED CLOSED}.
+ * The endpoint {@link #getState() state} immediately becomes {@link State#NOT_CONNECTED NOT_CONNECTED} otherwise.
+ *
+ *
This method does not release all resources that are associated with this endpoint.
+ * Use {@link #close()} method to release all resources.
+ */
+ public abstract void disconnectAndClear();
+
+ /**
+ * Closes this endpoint. All network connection are terminated as with
+ * {@link #disconnect() disconnect} method and no further connections
+ * can be established.
+ * The endpoint {@link #getState() state} immediately becomes {@link State#CLOSED CLOSED}.
+ * All resources associated with this endpoint are released.
+ */
+ public abstract void close();
+
+ /**
+ * Waits while this endpoint {@link #getState() state} becomes {@link State#NOT_CONNECTED NOT_CONNECTED} or
+ * {@link State#CLOSED CLOSED}. It is a signal that any files that were opened with
+ * {@link #connect(String) connect("file:...")} method were finished reading, but not necessary were completely
+ * processed by the corresponding subscription listeners. Use {@link #closeAndAwaitTermination()} after
+ * this method returns to make sure that all processing has completed.
+ *
+ *
This method is blocking. When using a single-threaded {@link #executor(Executor) executor}
+ * with endpoint, don't invoke this method from the executor thread — it will wait forever, blocking
+ * the same thread that is needed to complete the action that is being waited for.
+ *
+ * @throws InterruptedException if interrupted while waiting
+ */
+ public abstract void awaitNotConnected() throws InterruptedException;
+
+ /**
+ * Waits until this endpoint stops processing data (becomes quescient).
+ * This is important when writing data to file via "tape:..." connector to make sure that
+ * all published data was written before closing this endpoint.
+ *
+ * @throws InterruptedException if interrupted while waiting
+ */
+ public abstract void awaitProcessed() throws InterruptedException;
+
+ /**
+ * Closes this endpoint and wait until all pending data processing tasks are completed.
+ * This method performs the same actions as close {@link #close()}, but also awaits
+ * termination of all outstanding data processing tasks. It is designed to be used
+ * with {@link Role#STREAM_FEED STREAM_FEED} role after {@link #awaitNotConnected()} method returns
+ * to make sure that file was completely processed.
+ *
+ *
This method is blocking. When using a single-threaded {@link #executor(Executor) executor}
+ * with endpoint, don't invoke this method from the executor thread — it will wait forever, blocking
+ * the same thread that is needed to complete the action that is being waited for.
+ *
+ * @throws InterruptedException if interrupted while waiting
+ */
+ public abstract void closeAndAwaitTermination() throws InterruptedException;
+
+ /**
+ * Returns a set of all event types supported by this endpoint. The resulting set cannot be modified.
+ */
+ public abstract Set>> getEventTypes();
+
+ /**
+ * Returns feed that is associated with this endpoint.
+ *
+ * @return the feed.
+ */
+ public abstract DXFeed getFeed();
+
+ /**
+ * Returns publisher that is associated with this endpoint.
+ *
+ * @return the publisher.
+ */
+ public abstract DXPublisher getPublisher();
+
+ /**
+ * Builder class for {@link DXEndpoint} that supports additional configuration properties.
+ */
+ @Service
+ public abstract static class Builder {
+
+ /**
+ * Current role for implementations of {@link Builder}.
+ */
+ protected Role role = Role.FEED;
+
+ /**
+ * Protected constructor for implementations of {@link Builder}.
+ */
+ protected Builder() {}
+
+ /**
+ * Changes name that is used to distinguish multiple endpoints
+ * in the same JVM in logs and in other diagnostic means.
+ * This is a shortcut for
+ * {@link #withProperty withProperty}({@link #NAME_PROPERTY NAME_PROPERTY},{@code name})
+ */
+ public final Builder withName(String name) {
+ return withProperty(NAME_PROPERTY, name);
+ }
+
+ /**
+ * Sets role for the created {@link DXEndpoint}.
+ * Default role is {@link Role#FEED FEED}.
+ *
+ * @return {@code this} endpoint builder.
+ */
+ public Builder withRole(Role role) {
+ if (role == null)
+ throw new NullPointerException();
+ this.role = role;
+ return this;
+ }
+
+ /**
+ * Sets the specified property. Unsupported properties are ignored.
+ *
+ * @return {@code this} endpoint builder.
+ * @see #supportsProperty(String)
+ */
+ public abstract Builder withProperty(String key, String value);
+
+ /**
+ * Sets all supported properties from the provided properties object.
+ *
+ * @return {@code this} endpoint builder.
+ * @see #withProperty(String, String)
+ */
+ public Builder withProperties(Properties props) {
+ for (Map.Entry entry : props.entrySet()) {
+ String key = (String)entry.getKey();
+ withProperty(key, (String)entry.getValue());
+ }
+ return this;
+ }
+
+ /**
+ * Returns true if the corresponding property key is supported.
+ * @see #withProperty(String, String)
+ */
+ public abstract boolean supportsProperty(String key);
+
+ /**
+ * Builds {@link DXEndpoint} instance.
+ *
+ * @return the created endpoint.
+ */
+ public abstract DXEndpoint build();
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/DXFeed.java b/dxfeed-api/src/main/java/com/dxfeed/api/DXFeed.java
new file mode 100644
index 000000000..2e7c3e6ce
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/DXFeed.java
@@ -0,0 +1,659 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api;
+
+import java.util.*;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import com.dxfeed.api.osub.*;
+import com.dxfeed.event.*;
+import com.dxfeed.event.market.*;
+import com.dxfeed.event.option.Series;
+import com.dxfeed.model.IndexedEventModel;
+import com.dxfeed.model.TimeSeriesEventModel;
+import com.dxfeed.promise.*;
+
+/**
+ * Main entry class for dxFeed API (read it first ).
+ *
+ * Sample usage
+ *
+ * This section gives sample usage scenarios.
+ *
+ * Default singleton instance
+ *
+ * There is a singleton instance of the feed that is returned by {@link #getInstance()} method.
+ * It is created on the first use with default configuration properties that are explained in detail in
+ * documentation for {@link DXEndpoint} class in the
+ * "Default properties "
+ *
+ * In particular,
+ * you can provide a default address to connect and credentials using
+ * "{@link DXEndpoint#DXFEED_ADDRESS_PROPERTY dxfeed.address}",
+ * "{@link DXEndpoint#DXFEED_USER_PROPERTY dxfeed.user}", and
+ * "{@link DXEndpoint#DXFEED_PASSWORD_PROPERTY dxfeed.password}"
+ * system properties or by putting them into
+ * "{@link DXEndpoint#DXFEED_PROPERTIES_PROPERTY dxfeed.properties}"
+ * file on JVM classpath. dxFeed API samples come with a ready-to-use "dxfeed.properties "
+ * file that contains an address of dxFeed demo feed at "demo.dxfeed.com:7300 " and
+ * demo access credentials.
+ *
+ *
Subscribe for single event type
+ *
+ * The following code creates listener that prints mid price for each quote
+ * and subscribes for quotes on SPDR S&P 500 ETF symbol:
+ *
+ * {@link DXFeedSubscription DXFeedSubscription}<{@link Quote Quote}> sub = {@link DXFeed DXFeed}.{@link #getInstance() getInstance}().{@link #createSubscription(Class) createSubscription}({@link Quote Quote.class});
+ * sub.{@link DXFeedSubscription#addEventListener addEventListener}(new {@link DXFeedEventListener DXFeedEventListener}<{@link Quote Quote}>() {
+ * public void eventsReceived({@link List List}<{@link Quote Quote}> quotes) {
+ * for ({@link Quote Quote} quote : quotes)
+ * System.out.println("Mid = " + (quote.{@link Quote#getBidPrice getBidPrice}() + quote.{@link Quote#getAskPrice getAskPrice}()) / 2);
+ * }
+ * });
+ * sub.{@link DXFeedSubscription#addSymbols(Object...) addSymbols}("SPY");
+ *
+ * Note, that order of calls is important here. By attaching listeners first and then setting
+ * subscription we ensure that the current quote gets received by the listener. See
+ * {@link DXFeedSubscription#addSymbols(Object...) DXFeedSubscription.addSymbols} for details.
+ * If a set of symbols is changed first, then {@link DXFeedSubscription#addEventListener(DXFeedEventListener) sub.addEventListener}
+ * raises an {@link IllegalStateException} to protected from hard-to-catch bugs with potentially missed events.
+ *
+ * Subscribe for multiple event types
+ *
+ * The following code creates listener that prints each received event and
+ * subscribes for quotes and trades on SPDR S&P 500 ETF symbol:
+ *
+ * {@link DXFeedSubscription DXFeedSubscription}<{@link MarketEvent MarketEvent}> sub = {@link DXFeed DXFeed}.{@link #getInstance() getInstance}().<{@link MarketEvent MarketEvent}>{@link #createSubscription(Class[]) createSubscription}({@link Quote Quote.class}, {@link Trade Trade.class});
+ * sub.{@link DXFeedSubscription#addEventListener addEventListener}(new {@link DXFeedEventListener DXFeedEventListener}<{@link MarketEvent MarketEvent}>() {
+ * public void eventsReceived({@link List List}<{@link MarketEvent MarketEvent}> events) {
+ * for ({@link MarketEvent MarketEvent} event : events)
+ * System.out.println(event);
+ * }
+ * });
+ * sub.{@link DXFeedSubscription#addSymbols(Object...) addSymbols}("SPY");
+ *
+ * Subscribe for event and query periodically its last value
+ *
+ * The following code subscribes for trades on SPDR S&P 500 ETF symbol and
+ * prints last trade every second.
+ *
+ *
+ * {@link DXFeedSubscription DXFeedSubscription}<{@link Trade Trade}> sub = {@link DXFeed DXFeed}.{@link #getInstance() getInstance}().{@link #createSubscription(Class) createSubscription}({@link Trade Trade.class});
+ * sub.{@link DXFeedSubscription#addSymbols(Object...) addSymbols}("SPY");
+ * while (true) {
+ * System.out.println(feed.{@link #getLastEvent getLastEvent}(new Trade("SPY")));
+ * Thread.sleep(1000);
+ * }
+ *
+ * Threads and locks
+ *
+ * This class is thread-safe and can be used concurrently from multiple threads without external synchronization.
+ *
+ * Implementation details
+ *
+ * dxFeed API is implemented on top of QDS. dxFeed API classes itself are in "dxfeed-api.jar ", but
+ * their implementation is in "qds.jar ". You need have "qds.jar " in your classpath
+ * in order to use dxFeed API.
+ */
+public abstract class DXFeed {
+ /**
+ * Protected constructor for implementations of this class only.
+ */
+ protected DXFeed() {}
+
+ /**
+ * Returns a default application-wide singleton instance of feed. Most applications use only a single
+ * data-source and should rely on this method to get one. This is a shortcut to
+ * {@link DXEndpoint DXEndpoint}.{@link DXEndpoint#getInstance() getInstance}().{@link DXEndpoint#getFeed() getFeed}().
+ */
+ public static DXFeed getInstance() {
+ return DXEndpoint.getInstance().getFeed();
+ }
+
+ /**
+ * Creates new subscription for a single event type that is attached to this feed.
+ * For multiple event types in one subscription use
+ * {@link #createSubscription(Class[])} createSubscription(Class... eventTypes)}
+ * This method creates new {@link DXFeedSubscription} and invokes {@link #attachSubscription}.
+ *
+ * @param eventType the class of event types.
+ * @param the type of events.
+ *
+ * @see DXFeedSubscription#DXFeedSubscription(Class)
+ * @see #attachSubscription(DXFeedSubscription)
+ */
+ public final DXFeedSubscription createSubscription(Class extends E> eventType) {
+ DXFeedSubscription subscription = new DXFeedSubscription<>(eventType);
+ attachSubscription(subscription);
+ return subscription;
+ }
+
+ /**
+ * Creates new subscription for multiple event types that is attached to this feed.
+ * For a single event type use {@link #createSubscription(Class) createSubscrtiption(Class eventType)}
+ * This method creates new {@link DXFeedSubscription} and invokes {@link #attachSubscription}.
+ *
+ * @param eventTypes the classes of event types.
+ * @param the type of events.
+ *
+ * @see DXFeedSubscription#DXFeedSubscription(Class[])
+ * @see #attachSubscription(DXFeedSubscription)
+ */
+ @SafeVarargs
+ public final DXFeedSubscription createSubscription(Class extends E>... eventTypes) {
+ DXFeedSubscription subscription = new DXFeedSubscription<>(eventTypes);
+ attachSubscription(subscription);
+ return subscription;
+ }
+
+ /**
+ * Creates new time series subscription for a single event type that is attached to this feed.
+ * For multiple event types in one subscription use
+ * {@link #createTimeSeriesSubscription(Class[])} createTimeSeriesSubscription(Class... eventTypes)}
+ * This method creates new {@link DXFeedTimeSeriesSubscription} and invokes {@link #attachSubscription}.
+ *
+ * @param eventType the class of event types.
+ * @param the type of events.
+ *
+ * @see DXFeedTimeSeriesSubscription#DXFeedTimeSeriesSubscription(Class)
+ * @see #attachSubscription(DXFeedSubscription)
+ */
+ public final > DXFeedTimeSeriesSubscription createTimeSeriesSubscription(Class extends E> eventType) {
+ DXFeedTimeSeriesSubscription subscription = new DXFeedTimeSeriesSubscription<>(eventType);
+ attachSubscription(subscription);
+ return subscription;
+ }
+
+ /**
+ * Creates new time series subscription for multiple event types that is attached to this feed.
+ * For a single event type use {@link #createTimeSeriesSubscription(Class) createTimeSeriesSubscription(Class eventType)}
+ * This method creates new {@link DXFeedTimeSeriesSubscription} and invokes {@link #attachSubscription}.
+ *
+ * @param eventTypes the classes of event types.
+ * @param the type of events.
+ *
+ * @see DXFeedTimeSeriesSubscription#DXFeedTimeSeriesSubscription(Class[])
+ * @see #attachSubscription(DXFeedSubscription)
+ */
+ @SafeVarargs
+ public final > DXFeedTimeSeriesSubscription createTimeSeriesSubscription(Class extends E>... eventTypes) {
+ DXFeedTimeSeriesSubscription subscription = new DXFeedTimeSeriesSubscription<>(eventTypes);
+ attachSubscription(subscription);
+ return subscription;
+ }
+
+ /**
+ * Attaches the given subscription to this feed. This method does nothing if the
+ * corresponding subscription is already attached to this feed.
+ *
+ * This feed publishes data to the attached subscription.
+ * Application can attach {@link DXFeedEventListener} via
+ * {@link DXFeedSubscription#addEventListener} to get notified about data changes
+ * and can change its data subscription via {@link DXFeedSubscription} methods.
+ *
+ *
Implementation notes
+ *
+ * This method adds a non-serializable {@link ObservableSubscriptionChangeListener} for the given subscription
+ * via {@link DXFeedSubscription#addChangeListener} method.
+ * Attachment is lost when subscription is serialized and deserialized.
+ *
+ * @param subscription the subscription.
+ * @throws NullPointerException if the subscription is null.
+ *
+ * @see DXFeedSubscription
+ */
+ public abstract void attachSubscription(DXFeedSubscription> subscription);
+
+ /**
+ * Detaches the given subscription from this feed. This method does nothing if the
+ * corresponding subscription is not attached to this feed.
+ *
+ * Implementation notes
+ *
+ * This method removes {@link ObservableSubscriptionChangeListener} from the given subscription
+ * via {@link DXFeedSubscription#removeChangeListener} method.
+ *
+ * @param subscription the subscription.
+ * @throws NullPointerException if the subscription is null.
+ *
+ * @see DXFeedSubscription
+ */
+ public abstract void detachSubscription(DXFeedSubscription> subscription);
+
+ /**
+ * Detaches the given subscription from this feed and clears data delivered to this subscription
+ * by publishing empty events. This method does nothing if the
+ * corresponding subscription is not attached to this feed.
+ *
+ * @param subscription the subscription.
+ * @throws NullPointerException if the subscription is null.
+ *
+ * @see #detachSubscription(DXFeedSubscription)
+ */
+ public abstract void detachSubscriptionAndClear(DXFeedSubscription> subscription);
+
+ /**
+ * Returns the last event for the specified event instance.
+ * This method works only for event types that implement {@link LastingEvent} marker interface.
+ * This method does not make any remote calls to the uplink data provider.
+ * It just retrieves last received event from the local cache of this feed.
+ * The events are stored in the cache only if there is some
+ * attached {@link DXFeedSubscription} that is subscribed to the corresponding symbol and event type.
+ * {@link WildcardSymbol#ALL WildcardSymbol.ALL} subscription does not count for that purpose.
+ *
+ * Use {@link #getLastEventPromise getLastEventPromise} method
+ * if an event needs to be requested in the absence of subscription.
+ *
+ *
This method fills in the values for the last event into the {@code event} argument.
+ * If the last event is not available for any reason (no subscription, no connection to uplink, etc).
+ * then the event object is not changed.
+ * This method always returns the same {@code event} instance that is passed to it as an argument.
+ *
+ *
This method provides no way to distinguish a case when there is no subscription from the case when
+ * there is a subscription, but the event data have not arrived yet. It is recommened to use
+ * {@link #getLastEventIfSubscribed(Class, Object) getLastEventIfSubscribed} method
+ * instead of this {@code getLastEvent} method to fail-fast in case when the subscription was supposed to be
+ * set by the logic of the code, since {@link #getLastEventIfSubscribed(Class, Object) getLastEventIfSubscribed}
+ * method returns null when there is no subscription.
+ *
+ *
Note, that this method does not work when {@link DXEndpoint} was created with
+ * {@link DXEndpoint.Role#STREAM_FEED STREAM_FEED} role (never fills in the event).
+ *
+ * @param event the event.
+ * @param the type of event.
+ * @return the same event.
+ * @throws NullPointerException if the event is null.
+ */
+ public abstract > E getLastEvent(E event);
+
+ /**
+ * Returns the last events for the specified list of event instances.
+ * This is a bulk version of {@link #getLastEvent(LastingEvent) getLastEvent} method.
+ *
+ * Note, that this method does not work when {@link DXEndpoint} was created with
+ * {@link DXEndpoint.Role#STREAM_FEED STREAM_FEED} role.
+ *
+ * @param events the collection of events.
+ * @param the type of event.
+ * @return the same collection of events.
+ * @throws NullPointerException if the collection or any event in it is null.
+ */
+ public > Collection getLastEvents(Collection events) {
+ events.forEach(this::getLastEvent);
+ return events;
+ }
+
+ /**
+ * Requests the last event for the specified event type and symbol.
+ * This method works only for event types that implement {@link LastingEvent} marker interface.
+ * This method requests the data from the the uplink data provider,
+ * creates new event of the specified {@code eventType},
+ * and {@link Promise#complete(Object) completes} the resulting promise with this event.
+ *
+ * This method is designed for retrieval of a snapshot only.
+ * Use {@link DXFeedSubscription} if you need event updates in real time.
+ *
+ *
The promise is {@link Promise#cancel() cancelled} when the the underlying {@link DXEndpoint} is
+ * {@link DXEndpoint#close() closed}.
+ * If the event is not available for any transient reason (no subscription, no connection to uplink, etc),
+ * then the resulting promise completes when the issue is resolved, which may involve an arbitrarily long wait.
+ * Use {@link Promise#await(long, TimeUnit)} method to specify timeout while waiting for promise to complete.
+ * If the event is permanently not available (not supported), then the promise
+ * {@link Promise#completeExceptionally(Throwable) completes exceptionally} with {@link IllegalArgumentException}.
+ *
+ *
Use the following pattern of code to acquire multiple events (either for multiple symbols and/or multiple
+ * events) and wait with a single timeout for all of them:
+ *
+ * {@link List List}<{@link Promise Promise}<?>> promises = new {@link ArrayList ArrayList}<{@link Promise Promise}<?>>();
+ * // iterate the following line for all events and/or symbols that are needed
+ * promises.{@link List#add add}(feed.getLastEventPromise (eventType, symbol));
+ * // combine the list of promises into one with Promises utility method and wait
+ * {@link Promises Promises}.{@link Promises#allOf(Collection) allOf}(promises).{@link Promise#awaitWithoutException(long, TimeUnit) awaitWithoutException}(timeout, unit);
+ * // now iterate the promises to retrieve results
+ * for ({@link Promise Promise}<?> promise : promises)
+ * doSomethingWith(promise.{@link Promise#getResult() getResult}()); // result is null if this event was not found
+ *
+ *
+ * There is a bulk version of this method that works much faster for a single event type and multiple symbols.
+ * See {@link #getLastEventsPromises(Class, Collection) getLastEventsPromises}.
+ *
+ *
Note, that this method does not work when {@link DXEndpoint} was created with
+ * {@link DXEndpoint.Role#STREAM_FEED STREAM_FEED} role (promise completes exceptionally).
+ *
+ *
Threads
+ *
+ * Use {@link Promise#whenDone(PromiseHandler) Promise.whenDone} method on the resulting promise to receive
+ * notification when the promise becomes {@link Promise#isDone() done}. This notification is invoked
+ * from inside this {@link DXEndpoint DXEndpoint} {@link DXEndpoint#executor(Executor) executor} thread.
+ *
+ * @param eventType the event type.
+ * @param symbol the symbol.
+ * @param the type of event.
+ * @return the promise for the result of the request.
+ * @throws NullPointerException if the eventType or symbol are null.
+ */
+ public abstract > Promise getLastEventPromise(Class eventType, Object symbol);
+
+ /**
+ * Returns the last event for the specified event type and symbol if there is a subscription for it.
+ * This method works only for event types that implement {@link LastingEvent} marker interface.
+ * This method does not make any remote calls to the uplink data provider.
+ * It just retrieves last received event from the local cache of this feed.
+ * The events are stored in the cache only if there is some
+ * attached {@link DXFeedSubscription} that is subscribed to the corresponding event type and symbol.
+ * The subscription can also be permanently defined using {@link DXEndpoint DXEndpoint} properties.
+ * {@link WildcardSymbol#ALL WildcardSymbol.ALL} subscription does not count for that purpose.
+ * If there is no subscription, then this method returns null.
+ *
+ * If there is a subscription, but the event has not arrived from the uplink data provider,
+ * this method returns an non-initialized event object: its {@link EventType#getEventSymbol() eventSymbol}
+ * property is set to the requested symbol, but all the other properties have their default values.
+ *
+ *
Use {@link #getLastEventPromise getLastEventPromise} method
+ * if an event needs to be requested in the absence of subscription.
+ *
+ *
Note, that this method does not work when {@link DXEndpoint} was created with
+ * {@link DXEndpoint.Role#STREAM_FEED STREAM_FEED} role (always returns null).
+ *
+ * @param eventType the event type.
+ * @param symbol the symbol.
+ * @param the type of event.
+ * @return the event or null if there is no subscription for the specified event type and symbol.
+ * @throws NullPointerException if the event type or symbol are null.
+ */
+ public abstract > E getLastEventIfSubscribed(Class eventType, Object symbol);
+
+ /**
+ * Requests the last events for the specified event type and a collection of symbols.
+ * This method works only for event types that implement {@link LastingEvent} marker interface.
+ * This method requests the data from the the uplink data provider,
+ * creates new events of the specified {@code eventType},
+ * and {@link Promise#complete(Object) completes} the resulting promises with these events.
+ *
+ * This is a bulk version of {@link #getLastEventPromise(Class, Object) getLastEventPromise(eventType, symbol)} method.
+ *
+ *
The promise is {@link Promise#cancel() cancelled} when the the underlying {@link DXEndpoint} is
+ * {@link DXEndpoint#close() closed}.
+ * If the event is not available for any transient reason (no subscription, no connection to uplink, etc),
+ * then the resulting promise completes when the issue is resolved, which may involve an arbitrarily long wait.
+ * Use {@link Promise#await(long, TimeUnit)} method to specify timeout while waiting for promise to complete.
+ * If the event is permanently not available (not supported), then the promise
+ * {@link Promise#completeExceptionally(Throwable) completes exceptionally} with {@link IllegalArgumentException}.
+ *
+ *
Use the following pattern of code to acquire multiple events (either for multiple symbols and/or multiple
+ * events) and wait with a single timeout for all of them:
+ *
+ * {@link List List}<{@link Promise Promise}<?>> promises = feed.getLastEventsPromises (eventType, symbols);
+ * // combine the list of promises into one with Promises utility method and wait
+ * {@link Promises Promises}.{@link Promises#allOf(Collection) allOf}(promises).{@link Promise#awaitWithoutException(long, TimeUnit) awaitWithoutException}(timeout, unit);
+ * // now iterate the promises to retrieve results
+ * for ({@link Promise Promise}<?> promise : promises)
+ * doSomethingWith(promise.{@link Promise#getResult() getResult}()); // result is null if this event was not found
+ *
+ *
+ * Note, that this method does not work when {@link DXEndpoint} was created with
+ * {@link DXEndpoint.Role#STREAM_FEED STREAM_FEED} role (promise completes exceptionally).
+ *
+ *
Threads
+ *
+ * Use {@link Promise#whenDone(PromiseHandler) Promise.whenDone} method on the resulting promises to receive
+ * notification when the promise becomes {@link Promise#isDone() done}. This notification is invoked
+ * from inside this {@link DXEndpoint DXEndpoint} {@link DXEndpoint#executor(Executor) executor} thread.
+ *
+ * @param eventType the event type.
+ * @param symbols the collection of symbols.
+ * @param the type of event.
+ * @return the list of promises for the result of the requests, one item in list per symbol.
+ * @throws NullPointerException if the eventType or symbols are null.
+ */
+ public abstract > List> getLastEventsPromises(Class eventType, Collection> symbols);
+
+ /**
+ * Requests a list of indexed events for the specified event type, symbol, and source.
+ * This method works only for event types that implement {@link IndexedEvent} interface.
+ * This method requests the data from the the uplink data provider,
+ * creates a list of events of the specified {@code eventType},
+ * and {@link Promise#complete(Object) completes} the resulting promise with this list.
+ * The events are ordered by {@link IndexedEvent#getIndex() index} in the list.
+ *
+ * This method is designed for retrieval of a snapshot only.
+ * Use {@link IndexedEventModel} if you need a list of indexed events that updates in real time.
+ *
+ *
The promise is {@link Promise#cancel() cancelled} when the the underlying {@link DXEndpoint} is
+ * {@link DXEndpoint#close() closed}.
+ * If the events are not available for any transient reason (no subscription, no connection to uplink, etc),
+ * then the resulting promise completes when the issue is resolved, which may involve an arbitrarily long wait.
+ * Use {@link Promise#await(long, TimeUnit)} method to specify timeout while waiting for promise to complete.
+ * If the events are permanently not available (not supported), then the promise
+ * {@link Promise#completeExceptionally(Throwable) completes exceptionally} with {@link IllegalArgumentException}.
+ *
+ *
Note, that this method does not work when {@link DXEndpoint} was created with
+ * {@link DXEndpoint.Role#STREAM_FEED STREAM_FEED} role (promise completes exceptionally).
+ *
+ *
Event source
+ *
+ * Use the {@link IndexedEventSource#DEFAULT DEFAULT} value for {@code source} with events that do not
+ * have multiple sources (like {@link Series}). For events with multiple sources (like {@link Order}
+ * and {@link SpreadOrder}), use an even-specific source class (for example, {@link OrderSource}).
+ * This method does not support synthetic sources of orders (orders that are automatically
+ * generated from {@link Quote} events).
+ *
+ * This method does not accept an instance of {@link IndexedEventSubscriptionSymbol} as a {@code symbol}.
+ * The later class is designed for use with {@link DXFeedSubscription} and to observe source-specific subscription
+ * in {@link DXPublisher}.
+ *
+ *
Event flags and consistent snapshot
+ *
+ * This method completes promise only when a consistent snapshot of indexed events has been received from
+ * the data feed. The {@link IndexedEvent#getEventFlags() eventFlags} property of the events in the resulting list
+ * is always zero.
+ *
+ * Note, that the resulting list should not be used with
+ * {@link DXPublisher#publishEvents(Collection) DXPublisher.publishEvents} method, because the later expects
+ * events in a different order and with an appropriate flags set. See documentation on a specific event class
+ * for details on how they should be published.
+ *
+ *
Threads
+ *
+ * Use {@link Promise#whenDone(PromiseHandler) Promise.whenDone} method on the resulting promise to receive
+ * notification when the promise becomes {@link Promise#isDone() done}. This notification is invoked
+ * from inside this {@link DXEndpoint DXEndpoint} {@link DXEndpoint#executor(Executor) executor} thread.
+ *
+ * @param eventType the event type.
+ * @param symbol the symbol.
+ * @param source the source.
+ * @param the type of event.
+ * @return the promise for the result of the request.
+ * @throws NullPointerException if the eventType or symbol are null.
+ */
+ public abstract > Promise> getIndexedEventsPromise(Class eventType,
+ Object symbol, IndexedEventSource source);
+
+ /**
+ * Returns a list of indexed events for the specified event type, symbol, and source
+ * if there is a subscription for it.
+ * This method works only for event types that implement {@link IndexedEvent} interface.
+ * This method does not make any remote calls to the uplink data provider.
+ * It just retrieves last received events from the local cache of this feed.
+ * The events are stored in the cache only if there is some
+ * attached {@link DXFeedSubscription} that is subscribed to the corresponding event type, symbol, and source.
+ * The subscription can also be permanently defined using {@link DXEndpoint DXEndpoint} properties.
+ * If there is no subscription, then this method returns null.
+ * Otherwise, it creates a list of events of the specified {@code eventType} and returns it.
+ * The events are ordered by {@link IndexedEvent#getIndex() index} in the list.
+ *
+ * If there is a subscription, but the events have not arrived from the uplink data provider,
+ * this method returns an empty list.
+ *
+ *
Use {@link #getIndexedEventsPromise getIndexedEventsPromise} method
+ * if events need to be requested in the absence of subscription.
+ *
+ *
Note, that this method does not work when {@link DXEndpoint} was created with
+ * {@link DXEndpoint.Role#STREAM_FEED STREAM_FEED} role (always returns null).
+ *
+ *
Event source
+ *
+ * Use the {@link IndexedEventSource#DEFAULT DEFAULT} value for {@code source} with events that do not
+ * have multiple sources (like {@link Series}). For events with multiple sources (like {@link Order}
+ * and {@link SpreadOrder}), use an even-specific source class (for example, {@link OrderSource}).
+ * This method does not support synthetic sources of orders (orders that are automatically
+ * generated from {@link Quote} events).
+ *
+ * This method does not accept an instance of {@link IndexedEventSubscriptionSymbol} as a {@code symbol}.
+ * The later class is designed for use with {@link DXFeedSubscription} and to observe source-specific subscription
+ * in {@link DXPublisher}.
+ *
+ *
Event flags and consistent snapshot
+ *
+ * This method returns a list of events that are currently in the cache without any wait or delay
+ * and it does not guarantee that a consistent snapshot of events is returned.
+ * See {@link IndexedEvent} documentation for details.
+ * The {@link IndexedEvent#getEventFlags() eventFlags} property of the events in the resulting list
+ * is always zero regardless. Use {@link #getIndexedEventsPromise getIndexedEventsPromise} method
+ * if a consistent snapshot of events needs to be requested.
+ *
+ * Note, that the resulting list should not be used with
+ * {@link DXPublisher#publishEvents(Collection) DXPublisher.publishEvents} method, because the later expects
+ * events in a different order and with an appropriate flags set. See documentation on a specific event class
+ * for details on how they should be published.
+ *
+ * @param eventType the event type.
+ * @param symbol the symbol.
+ * @param source the source.
+ * @param the type of event.
+ * @return the list of events or null if there is no subscription for the specified event type, symbol, and source.
+ * @throws NullPointerException if the eventType or symbol are null.
+ */
+ public abstract > List getIndexedEventsIfSubscribed(Class eventType,
+ Object symbol, IndexedEventSource source);
+
+ /**
+ * Requests time series of events for the specified event type, symbol, and a range of time.
+ * This method works only for event types that implement {@link TimeSeriesEvent} interface.
+ * This method requests the data from the the uplink data provider,
+ * creates a list of events of the specified {@code eventType},
+ * and {@link Promise#complete(Object) completes} the resulting promise with this list.
+ * The events are ordered by {@link TimeSeriesEvent#getTime() time} in the list.
+ *
+ * This method is designed for retrieval of a snapshot only.
+ * Use {@link TimeSeriesEventModel} if you need a list of time-series events that updates in real time.
+ *
+ *
The range and depth of events that are available with this service is typically constrained by
+ * upstream data provider.
+ *
+ *
The promise is {@link Promise#cancel() cancelled} when the the underlying {@link DXEndpoint} is
+ * {@link DXEndpoint#close() closed}.
+ * If events are not available for any transient reason (no subscription, no connection to uplink, etc),
+ * then the resulting promise completes when the issue is resolved, which may involve an arbitrarily long wait.
+ * Use {@link Promise#await(long, TimeUnit)} method to specify timeout while waiting for promise to complete.
+ * If events are permanently not available (not supported), then the promise
+ * {@link Promise#completeExceptionally(Throwable) completes exceptionally} with {@link IllegalArgumentException}.
+ *
+ *
Note, that this method does not work when {@link DXEndpoint} was created with
+ * {@link DXEndpoint.Role#STREAM_FEED STREAM_FEED} role (promise completes exceptionally).
+ *
+ *
This method does not accept an instance of {@link TimeSeriesSubscriptionSymbol} as a {@code symbol}.
+ * The later class is designed for use with {@link DXFeedSubscription} and to observe time-series subscription
+ * in {@link DXPublisher}.
+ *
+ *
Event flags
+ *
+ * This method completes promise only when a consistent snapshot of time series has been received from
+ * the data feed. The {@link IndexedEvent#getEventFlags() eventFlags} property of the events in the resulting list
+ * is always zero.
+ *
+ * Note, that the resulting list should not be used with
+ * {@link DXPublisher#publishEvents(Collection) DXPublisher.publishEvents} method, because the later expects
+ * events in a different order and with an appropriate flags set. See documentation on a specific event class
+ * for details on how they should be published.
+ *
+ *
Threads
+ *
+ * Use {@link Promise#whenDone(PromiseHandler) Promise.whenDone} method on the resulting promise to receive
+ * notification when the promise becomes {@link Promise#isDone() done}. This notification is invoked
+ * from inside this {@link DXEndpoint DXEndpoint} {@link DXEndpoint#executor(Executor) executor} thread.
+ *
+ * @param eventType the event type.
+ * @param symbol the symbol
+ * @param fromTime the time, inclusive, to request events from (see {@link TimeSeriesEvent#getTime() TimeSeriesEvent.getTime}).
+ * @param toTime the time, inclusive, to request events to (see {@link TimeSeriesEvent#getTime() TimeSeriesEvent.getTime}).
+ * Use {@link Long#MAX_VALUE Long.MAX_VALUE} to retrieve events without an upper limit on time.
+ * @param the type of event.
+ * @return the promise for the result of the request.
+ * @throws NullPointerException if the eventType or symbol are null.
+ */
+ public abstract > Promise> getTimeSeriesPromise(Class eventType,
+ Object symbol, long fromTime, long toTime);
+
+ /**
+ * Returns time series of events for the specified event type, symbol, and a range of time
+ * if there is a subscription for it.
+ * This method does not make any remote calls to the uplink data provider.
+ * It just retrieves last received events from the local cache of this feed.
+ * The events are stored in the cache only if there is some
+ * attached {@link DXFeedSubscription} that is subscribed to the corresponding event type, symbol, and time.
+ * The subscription can also be permanently defined using {@link DXEndpoint DXEndpoint} properties.
+ * If there is no subscription, then this method returns null.
+ * Otherwise, it creates a list of events of the specified {@code eventType} and returns it.
+ * The events are ordered by {@link TimeSeriesEvent#getTime() time} in the list.
+ *
+ * If there is a subscription, but the events have not arrived from the uplink data provider,
+ * this method returns an empty list.
+ *
+ *
Use {@link #getTimeSeriesPromise getTimeSeriesPromise} method
+ * if events need to be requested in the absence of subscription.
+ *
+ *
Note, that this method does not work when {@link DXEndpoint} was created with
+ * {@link DXEndpoint.Role#STREAM_FEED STREAM_FEED} role (always returns null).
+ *
+ *
This method does not accept an instance of {@link TimeSeriesSubscriptionSymbol} as a {@code symbol}.
+ * The later class is designed for use with {@link DXFeedSubscription} and to observe time-series subscription
+ * in {@link DXPublisher}.
+ *
+ *
Event flags and consistent snapshot
+ *
+ * This method returns a list of events that are currently in the cache without any wait or delay
+ * and it does not guarantee that a consistent snapshot of events is returned.
+ * See {@link IndexedEvent} documentation for details.
+ * The {@link IndexedEvent#getEventFlags() eventFlags} property of the events in the resulting list
+ * is always zero regardless. Use {@link #getTimeSeriesPromise getTimeSeriesPromise} method
+ * if a consistent snapshot of events needs to be requested.
+ *
+ * Note, that the resulting list should not be used with
+ * {@link DXPublisher#publishEvents(Collection) DXPublisher.publishEvents} method, because the later expects
+ * events in a different order and with an appropriate flags set. See documentation on a specific event class
+ * for details on how they should be published.
+ *
+ * @param eventType the event type.
+ * @param symbol the symbol
+ * @param fromTime the time, inclusive, to return events from (see {@link TimeSeriesEvent#getTime() TimeSeriesEvent.getTime}).
+ * @param toTime the time, inclusive, to return events to (see {@link TimeSeriesEvent#getTime() TimeSeriesEvent.getTime}).
+ * Use {@link Long#MAX_VALUE Long.MAX_VALUE} to retrieve events without an upper limit on time.
+ * @param the type of event.
+ * @return the list of events or null if there is no subscription for the specified event type, symbol, and time range.
+ * @throws NullPointerException if the eventType or symbol are null.
+ */
+ public abstract > List getTimeSeriesIfSubscribed(Class eventType,
+ Object symbol, long fromTime, long toTime);
+
+ //----------------------- protected API for subclasses -----------------------
+
+ /**
+ * Processes received events. This methods invokes {@link DXFeedEventListener#eventsReceived} on all installed
+ * event listeners. This is a protected method for use by {@code DXFeed} implementation classes only.
+ *
+ * @param events the list of received events.
+ * @param the type of events.
+ */
+ protected static void processEvents(DXFeedSubscription subscription, List events) {
+ subscription.processEvents(events);
+ }
+
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/DXFeedEventListener.java b/dxfeed-api/src/main/java/com/dxfeed/api/DXFeedEventListener.java
new file mode 100644
index 000000000..ae7b1daee
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/DXFeedEventListener.java
@@ -0,0 +1,26 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api;
+
+import java.util.List;
+
+/**
+ * The listener interface for receiving events of the specified type {@code E}.
+ *
+ * @param the type of events.
+ */
+@FunctionalInterface
+public interface DXFeedEventListener {
+ /**
+ * Invoked when events of type {@code E} are received.
+ *
+ * @param events the list of received events.
+ */
+ public void eventsReceived(List events);
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/DXFeedSubscription.java b/dxfeed-api/src/main/java/com/dxfeed/api/DXFeedSubscription.java
new file mode 100644
index 000000000..c9a379150
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/DXFeedSubscription.java
@@ -0,0 +1,1052 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.*;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.devexperts.io.IOUtil;
+import com.devexperts.util.IndexedSet;
+import com.devexperts.util.IndexerFunction;
+import com.dxfeed.api.osub.*;
+import com.dxfeed.event.EventType;
+import com.dxfeed.event.LastingEvent;
+import com.dxfeed.event.candle.Candle;
+import com.dxfeed.event.candle.CandleSymbol;
+import com.dxfeed.event.market.MarketEvent;
+
+/**
+ * Subscription for a set of symbols and event types.
+ *
+ * Symbols and event types
+ *
+ * Symbols are represented by objects, simple keys are represented by {@link String} objects,
+ * complex ones use specialized object as described in the corresponding event types.
+ * However, every symbol has a unique string representation that can be used.
+ * Each event is represented by a concrete instance of
+ * event class. Event type is represented by a class literal. For example,
+ *
+ * "SPY" is a symbol for SPDR S&P 500 ETF,
+ * {@code Quote.class} is an event type for a best market quote.
+ *
+ * Events can be represented by any classes that annotated by an inheritable annotation {@link EventType}.
+ * Common types of market events extend {@link MarketEvent MarketEvent} class
+ * and reside in com.dxfeed.event.market
package.
+ *
+ * The set of subscribed symbols and the set of subscribed event types are maintained separately.
+ * The subscription is considered to be interested in the cross-product of these sets. That is, subscription
+ * is interested in any event whose type is in the set of subscribed event types and whose symbol is in the
+ * set of subscribed symbols.
+ *
+ *
The set of event types is specified when subscription is created and cannot be changed afterward.
+ * The set of symbols can be modified with
+ * {@link #setSymbols(Collection) setSymbols}, {@link #addSymbols(Collection) addSymbols},
+ * {@link #removeSymbols(Collection) removeSymbols}, and {@link #clear() clear} methods.
+ * Each of {@code xxxSymbols} method exists in two version. One version accepts a {@link Collection}
+ * of symbols and it recommended for bulk modifications of symbol set. The other version accepts
+ * varargs arrays of symbols and is provided as a convenience method to set/add/remove one or few
+ * symbols.
+ *
+ *
Symbols in a set are compared using their {@link Object#equals(Object) equals} method. All symbol objects
+ * must properly support {@link Object#hashCode() hashCode} method. A set of symbols cannot contain two equal symbols.
+ * If a symbol that is {@link #addSymbols(Collection) added} to a set of symbols is equal to the other one,
+ * then the old symbol is removed from the set and the new symbol instance is retained. However,
+ * the {@link ObservableSubscriptionChangeListener} (see below) in this case is notified on the change of
+ * subscription only when new symbol object implements {@link FilteredSubscriptionSymbol} marker interface.
+ *
+ *
Special symbol types and restrictions
+ *
+ * A single object {@link WildcardSymbol#ALL WildcardSymbol.ALL} represents subscription to all possible symbols
+ * and have the effect of subscribing to all of them. See {@link WildcardSymbol} for more details. It
+ * is always represented by a separate class (instead of a usual {@link String}) in order
+ * to avoid potential resource consumption problems that may stem from an accidental subscription via wildcard.
+ * All string symbols that start with {@link WildcardSymbol#RESERVED_PREFIX WildcardSymbol.RESERVED_PREFIX} ("*")
+ * are reserved for the same reason. Subscription to the corresponding string does not have any effect
+ * (no events will be received). Thus, it is safe to add user-specified strings to the subscription via
+ * {@link #addSymbols(Object...) addSymbols} method without any prior validation of the user input.
+ *
+ * NOTE: Wildcard subscription can create extremely high network and CPU load for certain kinds of
+ * high-frequency events like quotes. It requires a special arrangement on the side of upstream data provider and
+ * is disabled by default in upstream feed configuration. Make that sure you have adequate resources and understand
+ * the impact before using it. It can be used for low-frequency events only (like Forex quotes), because each instance
+ * of {@code DXFeedSubscription} processes events in a single thread and there is no provision to load-balance wildcard
+ * subscription amongst multiple threads.
+ *
+ * Contact your data provider for the corresponding configuration arrangement if needed.
+ *
A special class of {@link TimeSeriesSubscriptionSymbol} symbols is used internally to represent a subscription
+ * to time-series of events. Two instances of {@link TimeSeriesSubscriptionSymbol} objects are
+ * {@link TimeSeriesSubscriptionSymbol#equals(Object) equal} when their
+ * {@link TimeSeriesSubscriptionSymbol#getEventSymbol() underlying symbols} are equal, thus only one, most recently
+ * added, instance of {@link TimeSeriesSubscriptionSymbol} will be kept in the set of subscribed symbols.
+ *
+ *
It is recommended to use {@link DXFeedTimeSeriesSubscription} convenience class for time-series subscriptions.
+ *
+ *
{@link Candle} events use {@link CandleSymbol} class to represent the complex structure of the candle symbol
+ * and its attributes. However, both {@link String} and {@link CandleSymbol} objects can be used in subscription.
+ * The corresponding strings will be converted to {@link CandleSymbol} using its {@link CandleSymbol#valueOf(String) valueOf}
+ * method. Do not mix {@link String} and {@link CandleSymbol} subscription in a single {@code DXFeedSubscription} instance.
+ *
+ *
Subscription listeners
+ *
+ * This class keeps a list of {@link DXFeedEventListener} instances that are notified on any events.
+ * Event listeners are added with {@link #addEventListener(DXFeedEventListener) addEventListener} method.
+ * Event listeners must be installed before changing the set of subscribed symbols .
+ * When the set of subscribed symbols changes all registered event listeners receive update on the
+ * last events for all newly added symbols.
+ *
+ * This class keeps a set of {@link ObservableSubscriptionChangeListener} instances that are notified on any
+ * change in subscription. These listeners are installed by {@link DXFeed} to keep
+ * track of the subscription state and communicate subscription upstream to data providers.
+ *
+ *
Detached and attached subscriptions
+ *
+ * Subscription that is created via constructor is detached . It is not attached
+ * to any feed and thus it does not actually receive any events. Detached subscription still maintains
+ * a set of symbols and a list of event listeners. Detached subscription can be attached to
+ * any feed with {@link DXFeed#attachSubscription DXFeed.attachSubscription} method.
+ *
+ * Subscription that is created via {@link DXFeed#createSubscription DXFeed.createSubscription}
+ * is attached to the
+ * corresponding feed. The feed tracks all changes in subscription by installing
+ * {@link ObservableSubscriptionChangeListener} and invokes
+ * {@link #processEvents processEvents} for all received events.
+ * Subscription can be detached from the feed with
+ * {@link DXFeed#detachSubscription DXFeed.detachSubscription} method.
+ *
+ *
Subscription can be attached to multiple feeds at the same time. In this case it receives events from
+ * all feeds but there is no way distinguish which feed the corresponding event came from.
+ *
+ *
Resource management and closed subscriptions
+ *
+ * Attached subscription is a potential memory leak. If the pointer to attached subscription is lost, then there is
+ * no way to detach this subscription from the feed and the subscription will not be reclaimed by the garbage collector
+ * as long as the corresponding feed is still used. Detached subscriptions can be reclaimed by the garbage collector,
+ * but detaching subscription requires knowing the pointer to the feed at the place of the call, which is not always convenient.
+ *
+ * The convenient way to detach subscription from the feed is to call its {@link #close close} method. Closed subscription
+ * becomes permanently detached from all feeds, removes all its listeners and is guaranteed to be reclaimable by the garbage
+ * collector as soon as all external references to it are cleared.
+ *
+ *
The other way is to close an associated feed. This cannot be done via a {@link DXFeed} instance, but it can be done
+ * indirectly by closing the associated {@link DXEndpoint endpoint}.
+ *
+ *
Serialization
+ *
+ * This class's serialized state includes only serializable listeners. {@link ObservableSubscriptionChangeListener}
+ * that is installed by {@link DXFeed} when this subscription is attached is not serializable.
+ * Thus, freshly deserialized instance of this class will be detached . It has to be attached
+ * to the feed after deserialization in order for it to start receiving events.
+ *
+ *
+ *
+ * This class is thread-safe and can be used concurrently from multiple threads without external synchronization.
+ *
+ * Installed {@link DXFeedEventListener} instances are invoked from a separate thread via the executor.
+ * Default executor for all subscriptions is configured with {@link DXEndpoint#executor(Executor) DXEndpoint.executor}
+ * method. Each subscription can individually override its executor with {@link #setExecutor(Executor) setExecutor}
+ * method.
+ *
+ *
Event listeners are invoked in a serial manner with respect to a given DXFeedSubscription instance.
+ * That is, next event notification will not be performed until
+ * {@link DXFeedEventListener#eventsReceived(List) DXFeedEventListener.eventsReceived} method for the previous
+ * notification completes. It guarantees that the order of events in a given instance of {@code DXFeedSubscription}
+ * is preserved.
+ *
+ *
This requirement on ordering limits concurrency of event processing. Effectively, each
+ * subscription can use at most one CPU core to process its events. If there is a need to simultaneously
+ * process events on a large number of symbols and this event processing is so resource-consuming, that
+ * one CPU core is not enough to process all events in real-time, then it is advised to split symbols between multiple
+ * {@code DXFeedSubscription} instances. If event processing is mostly CPU-bound, then the good rule of thumb
+ * is to have as many {@code DXFeedSubscription} instances as there are CPU cores in the system.
+ *
+ *
However, multiple tasks can get submitted to the executor at the same time. In the current implementation,
+ * at most two tasks are submitted at any time if
+ * {@link DXEndpoint#DXFEED_AGGREGATION_PERIOD_PROPERTY DXFEED_AGGREGATION_PERIOD_PROPERTY} is used.
+ * One task for immediate processing of data snapshots via {@link Executor#execute(Runnable) Executor.execute} method
+ * and another task for delayed processing of data updates via
+ * {@link ScheduledExecutorService#schedule(Runnable, long, TimeUnit) ScheduledExecutorService.schedule} method
+ * if the executor implements {@link ScheduledExecutorService} interface.
+ * At most one task is submitted at any time if this property is not used.
+ *
+ *
Installed {@link ObservableSubscriptionChangeListener} instances are notified on symbol set changes
+ * while holding the lock on this subscription and in the same thread that changed the set of subscribed symbols
+ * in this subscription.
+ *
+ *
Custom executor can be used by backend applications that do not need to immediately retrieve a collection
+ * of events, but want to poll for new events at a later time, for example, from inside of a servlet request.
+ * The following code pattern is suggested in this case to initialize subscription's executor:
+ *
+ *
+ * {@link ConcurrentLinkedQueue}<{@link Runnable Runnable}> taskQueue = new ConcurrentLinkedQueue<>();
+ * subscription.{@link #setExecutor(Executor) setExecutor}(new {@link Executor}() {
+ * public void execute({@link Runnable Runnable} task) {
+ * taskQueue.{@link ConcurrentLinkedQueue#add add}(task);
+ * }
+ * });
+ * subscription.{@link #addEventListener(DXFeedEventListener) addEventListener}(...);
+ *
+ *
+ * When there is a time to poll for new events, the following code can be used:
+ *
+ *
+ * {@link Runnable Runnable} task;
+ * while ((task = taskQueue.{@link ConcurrentLinkedQueue#poll poll}()) != null )
+ * task.run();
+ *
+ *
+ * and event listener's {@link DXFeedEventListener#eventsReceived(List) eventsReceived} method will be invoked
+ * in this thread from inside of {@code task.run()} invocation.
+ *
+ * This approach has a clear advantage with {@link LastingEvent LastingEvent} types.
+ * If the above polling code is delayed or is executed only periodically, then incoming events have a chance
+ * to get conflated and listener will receive only on the most recent event updates for processing.
+ *
+ * @param the type of events.
+ */
+public class DXFeedSubscription implements Serializable, ObservableSubscription {
+ private static final long serialVersionUID = 0;
+
+ // closed state
+ private volatile boolean closed;
+
+ // initialized during construction in init() method
+ private transient EventTypeSet eventTypeSet;
+ private transient IndexerFunction eventSymbolIndexer;
+ private transient IndexedSet symbols;
+ private transient ObservableSubscriptionChangeListener changeListeners;
+ private transient volatile Executor executor;
+ private transient volatile DXFeedEventListener eventListeners; // fires without synchronization
+
+ // initialized on first use
+ private transient Set> undecoratedSymbols;
+
+ /**
+ * Creates detached subscription for a single event type.
+ *
+ * @param eventType the event type.
+ * @throws NullPointerException if event type is null.
+ */
+ public DXFeedSubscription(Class extends E> eventType) {
+ init(eventType);
+ }
+
+ /**
+ * Creates detached subscription for the given list of event types.
+ *
+ * @param eventTypes the list of event types.
+ * @throws IllegalArgumentException if the list of event types is empty.
+ * @throws NullPointerException if any event type is null.
+ */
+ @SafeVarargs
+ public DXFeedSubscription(Class extends E>... eventTypes) {
+ init(eventTypes);
+ }
+
+ /**
+ * Attaches subscription to the specified feed.
+ *
+ * @param feed feed to attach to.
+ */
+ public void attach(DXFeed feed) {
+ feed.attachSubscription(this);
+ }
+
+ /**
+ * Detaches subscription from the specified feed.
+ *
+ * @param feed feed to detach from.
+ */
+ public void detach(DXFeed feed) {
+ feed.detachSubscription(this);
+ }
+
+ /**
+ * Returns true
if this subscription is closed.
+ *
+ * @see #close
+ */
+ @Override
+ public boolean isClosed() {
+ return closed;
+ }
+
+ /**
+ * Closes this subscription and makes it permanently detached .
+ * This method notifies
+ * all installed {@link ObservableSubscriptionChangeListener} instances by invoking
+ * {@link ObservableSubscriptionChangeListener#subscriptionClosed subscriptionClosed}
+ * while holding the lock for this subscription. This method clears lists of all installed
+ * event listeners and subscription change listeners and makes sure that no more listeners
+ * can be added.
+ *
+ * This method ensures that subscription can be safely garbage-collected when all outside references
+ * to it are lost.
+ */
+ public synchronized void close() {
+ if (closed)
+ return;
+ closed = true;
+ eventListeners = null;
+ if (changeListeners != null) {
+ changeListeners.subscriptionClosed();
+ changeListeners = null;
+ }
+ }
+
+ /**
+ * Returns a set of subscribed event types. The resulting set cannot be modified.
+ */
+ @Override
+ public Set> getEventTypes() {
+ return eventTypeSet.asSet();
+ }
+
+ /**
+ * Returns true
if this subscription contains the corresponding event type.
+ * @see #getEventTypes()
+ */
+ @Override
+ public boolean containsEventType(Class> eventType) {
+ return eventTypeSet.contains(eventType);
+ }
+
+ /**
+ * Clears the set of subscribed symbols. This implementation calls
+ * {@link #setSymbols setSymbols}(Collections.EMPTY_LIST)
+ */
+ public void clear() {
+ setSymbols(Collections.EMPTY_LIST);
+ }
+
+ /**
+ * Returns a set of subscribed symbols. The resulting set cannot be modified. The contents of the resulting set
+ * are undefined if the set of symbols is changed after invocation of this method, but the resulting set is
+ * safe for concurrent reads from any threads. The resulting set maybe either a snapshot of the set of
+ * the subscribed symbols at the time of invocation or a weakly consistent view of the set.
+ */
+ public Set> getSymbols() {
+ if (undecoratedSymbols == null)
+ undecoratedSymbols = new UndecoratedSymbols();
+ return undecoratedSymbols;
+ }
+
+ /**
+ * Changes the set of subscribed symbols so that it contains just the symbols from the specified collection.
+ * To conveniently set subscription for just one or few symbols you can use
+ * {@link #setSymbols(Object...) setSymbols(Object... symbols)} method.
+ * All registered event listeners will receive update on the last events for all
+ * newly added symbols.
+ *
+ * Implementation notes
+ *
+ * This method notifies
+ * all installed {@link ObservableSubscriptionChangeListener} instances of any resulting changes in the set of
+ * subscribed symbols while holding the lock for this subscription.
+ *
+ * This implementation decorates symbols via protected {@link #decorateSymbol(Object)} method,
+ * that can be overridden in subclasses of {@code DXFeedSubscription}. Installed
+ * {@code ObservableSubscriptionChangeListener} instances receive decorated symbols.
+ *
+ * @param symbols the collection of symbols.
+ */
+ public void setSymbols(Collection> symbols) {
+ setSymbolsImpl(decorateSymbols(symbols));
+ }
+
+ /**
+ * Changes the set of subscribed symbols so that it contains just the symbols from the specified array.
+ * This is a convenience method to set subscription to one or few symbols at a time.
+ * When setting subscription to multiple
+ * symbols at once it is preferable to use
+ * {@link #setSymbols(Collection) setSymbols(Collection<?> symbols)} method.
+ * All registered event listeners will receive update on the last events for all
+ * newly added symbols.
+ *
+ *
Implementation notes
+ *
+ * This method notifies
+ * all installed {@link ObservableSubscriptionChangeListener} instances of any resulting changes in the set of
+ * subscribed symbols while holding the lock for this subscription.
+ *
+ * This implementation decorates symbols via protected {@link #decorateSymbol(Object)} method,
+ * that can be overridden in subclasses of {@code DXFeedSubscription}. Installed
+ * {@code ObservableSubscriptionChangeListener} instances receive decorated symbols.
+ *
+ * @param symbols the array of symbols.
+ */
+ public void setSymbols(Object... symbols) {
+ setSymbolsImpl(decorateSymbols(symbols));
+ }
+
+ /**
+ * Adds the specified collection of symbols to the set of subscribed symbols.
+ * To conveniently add one or few symbols you can use
+ * {@link #addSymbols(Object...) addSymbols(Object... symbols)} method.
+ * All registered event listeners will receive update on the last events for all
+ * newly added symbols.
+ *
+ *
Implementation notes
+ *
+ * This method notifies
+ * all installed {@link ObservableSubscriptionChangeListener} instances of any resulting changes in the set of
+ * subscribed symbols while holding the lock for this subscription.
+ *
+ * This implementation decorates symbols via protected {@link #decorateSymbol(Object)} method,
+ * that can be overridden in subclasses of {@code DXFeedSubscription}. Installed
+ * {@code ObservableSubscriptionChangeListener} instances receive decorated symbols.
+ *
+ * @param symbols the collection of symbols.
+ */
+ public void addSymbols(Collection> symbols) {
+ if (symbols.isEmpty())
+ return;
+ addSymbolsImpl(decorateSymbols(symbols));
+ }
+
+ /**
+ * Adds the specified array of symbols to the set of subscribed symbols.
+ * This is a convenience method to subscribe to one or few symbols at a time.
+ * When subscribing to multiple
+ * symbols at once it is preferable to use
+ * {@link #addSymbols(Collection) addSymbols(Collection<?> symbols)} method.
+ * All registered event listeners will receive update on the last events for all
+ * newly added symbols.
+ *
+ *
Implementation notes
+ *
+ * This method notifies
+ * all installed {@link ObservableSubscriptionChangeListener} instances of any resulting changes in the set of
+ * subscribed symbols while holding the lock for this subscription.
+ *
+ * This implementation decorates symbols via protected {@link #decorateSymbol(Object)} method
+ * that can be overridden in subclasses of {@code DXFeedSubscription}. Installed
+ * {@code ObservableSubscriptionChangeListener} instances receive decorated symbols.
+ *
+ * @param symbols the array of symbols.
+ */
+ public void addSymbols(Object... symbols) {
+ if (symbols.length == 0)
+ return; // no symbols -- nothing to do
+ if (symbols.length == 1)
+ addSymbolImpl(decorateSymbol(symbols[0])); // shortcut to optimized one symbol case
+ else
+ addSymbolsImpl(decorateSymbols(symbols)); // multiple symbols
+ }
+
+ /**
+ * Adds the specified symbol to the set of subscribed symbols.
+ * This is a convenience method to subscribe to one symbol at a time that
+ * has a return fast-path for a case when the symbol is already in the set.
+ * When subscribing to multiple
+ * symbols at once it is preferable to use
+ * {@link #addSymbols(Collection) addSymbols(Collection<?> symbols)} method.
+ * All registered event listeners will receive update on the last events for all
+ * newly added symbols.
+ *
+ *
Implementation notes
+ *
+ * This method notifies
+ * all installed {@link ObservableSubscriptionChangeListener} instances of any resulting changes in the set of
+ * subscribed symbols while holding the lock for this subscription.
+ *
+ * This implementation decorates symbols via protected {@link #decorateSymbol(Object)} method
+ * that can be overridden in subclasses of {@code DXFeedSubscription}. Installed
+ * {@code ObservableSubscriptionChangeListener} instances receive decorated symbols.
+ *
+ * @param symbol the symbol.
+ */
+ public void addSymbols(Object symbol) {
+ addSymbolImpl(decorateSymbol(symbol));
+ }
+
+ /**
+ * Removes the specified collection of symbols from the set of subscribed symbols.
+ * To conveniently remove one or few symbols you can use
+ * {@link #removeSymbols(Object...) removeSymbols(Object... symbols)} method.
+ *
+ *
Implementation notes
+ *
+ * This method notifies
+ * all installed {@link ObservableSubscriptionChangeListener} instances of any resulting changes in the set of
+ * subscribed symbols while holding the lock for this subscription.
+ *
+ * This implementation decorates symbols via protected {@link #decorateSymbol(Object)} method,
+ * that can be overridden in subclasses of {@code DXFeedSubscription}. Installed
+ * {@code ObservableSubscriptionChangeListener} instances receive decorated symbols.
+ *
+ * @param symbols the collection of symbols.
+ */
+ public void removeSymbols(Collection> symbols) {
+ if (symbols.isEmpty())
+ return;
+ removeSymbolsImpl(decorateSymbols(symbols));
+ }
+
+ /**
+ * Removes the specified array of symbols from the set of subscribed symbols.
+ * This is a convenience method to remove one or few symbols at a time.
+ * When removing multiple
+ * symbols at once it is preferable to use
+ * {@link #removeSymbols(Collection) removeSymbols(Collection<?> symbols)} method.
+ *
+ *
Implementation notes
+ *
+ * This method notifies
+ * all installed {@link ObservableSubscriptionChangeListener} instances of any resulting changes in the set of
+ * subscribed symbols while holding the lock for this subscription.
+ *
+ * This implementation decorates symbols via protected {@link #decorateSymbol(Object)} method,
+ * that can be overridden in subclasses of {@code DXFeedSubscription}. Installed
+ * {@code ObservableSubscriptionChangeListener} instances receive decorated symbols.
+ *
+ * @param symbols the array of symbols.
+ */
+ public void removeSymbols(Object... symbols) {
+ if (symbols.length == 0)
+ return;
+ removeSymbolsImpl(decorateSymbols(symbols));
+ }
+
+ /**
+ * Returns executor for processing event notifications on this subscription.
+ * See Threads and locks section of this class documentation.
+ * @return executor for processing event notifications on this subscription,
+ * or {@code null} if default executor of the attached {@link DXFeed} is used.
+ */
+ public Executor getExecutor() {
+ return executor;
+ }
+
+ /**
+ * Changes executor for processing event notifications on this subscription.
+ * See Threads and locks section of this class documentation.
+ * @param executor executor for processing event notifications on this subscription,
+ * or {@code null} if default executor of the attached {@link DXFeed} is used.
+ */
+ public void setExecutor(Executor executor) {
+ this.executor = executor;
+ }
+
+ /**
+ * Adds listener for events.
+ * Event lister can be added only when subscription is not producing any events.
+ * The subscription must be either empty
+ * (its set of {@link #getSymbols() symbols} is empty) or not {@link #attach(DXFeed) attached} to any feed
+ * (its set of change listeners is empty).
+ *
+ * This method does nothing if this subscription is closed.
+ * @param listener the event listener.
+ * @throws NullPointerException if listener is null.
+ * @throws IllegalStateException if subscription is attached and is not empty.
+ */
+ public synchronized void addEventListener(DXFeedEventListener listener) {
+ if (listener == null)
+ throw new NullPointerException();
+ if (closed)
+ return;
+ if (changeListeners != null && !symbols.isEmpty())
+ throw new IllegalStateException("Cannot add event listener to non-empty attached subscription. Add event listeners first");
+ if (eventListeners == null)
+ eventListeners = listener;
+ else
+ eventListeners = new EventListeners<>(addListener(eventListeners, listener));
+ }
+
+ /**
+ * Removes listener for events.
+ * @param listener the event listener.
+ * @throws NullPointerException if listener is null.
+ */
+ public synchronized void removeEventListener(DXFeedEventListener listener) {
+ if (listener == null)
+ throw new NullPointerException();
+ if (eventListeners == null)
+ return;
+ if (eventListeners == listener)
+ eventListeners = null;
+ else if (eventListeners instanceof EventListeners)
+ eventListeners = ((EventListeners)eventListeners).remove(listener);
+ }
+
+ /**
+ * Adds subscription change listener. This method does nothing if the given listener is already
+ * installed as subscription change listener for this subscription or if subscription is closed.
+ * Otherwise, it installs the
+ * corresponding listener and immediately invokes {@link ObservableSubscriptionChangeListener#symbolsAdded}
+ * on the given listener while holding the lock for this
+ * subscription. This way the given listener synchronously receives existing subscription state and and
+ * is synchronously notified on all changes in subscription afterwards.
+ *
+ * Whenever a symbol in a set of subscribed symbols is replaced by the other symbol that is
+ * {@link #equals(Object) equal} to the old one, the decision on whether to notify installed listeners
+ * about the change is based on the result of
+ * {@link #shallNotifyOnSymbolUpdate(Object, Object) shallNotifyOnSymbolUpdate} method.
+ *
+ * @param listener the subscription change listener.
+ * @throws NullPointerException if listener is null.
+ */
+ @Override
+ public synchronized void addChangeListener(ObservableSubscriptionChangeListener listener) {
+ if (listener == null)
+ throw new NullPointerException();
+ if (closed)
+ return;
+ if (changeListeners == null)
+ changeListeners = listener;
+ else if (changeListeners == listener)
+ return;
+ else {
+ if (changeListeners instanceof ChangeListeners && findListener((ChangeListeners)changeListeners, listener) >= 0)
+ return;
+ changeListeners = new ChangeListeners(addListener(changeListeners, listener));
+ }
+ // notify new listener on a set of symbols
+ if (!symbols.isEmpty())
+ listener.symbolsAdded(symbols);
+ }
+
+ /**
+ * Removes subscription change listener. This method does nothing if the given listener was not
+ * installed or was already removed as subscription change listener for this subscription.
+ * Otherwise it removes the corresponding listener and immediately invokes
+ * {@link ObservableSubscriptionChangeListener#subscriptionClosed} on the given listener while
+ * holding the lock for this subscription.
+ *
+ * @param listener the subscription change listener.
+ * @throws NullPointerException if listener is null.
+ */
+ @Override
+ public synchronized void removeChangeListener(ObservableSubscriptionChangeListener listener) {
+ if (listener == null)
+ throw new NullPointerException();
+ if (changeListeners == null)
+ return;
+ if (changeListeners.equals(listener))
+ changeListeners = null;
+ else if (changeListeners instanceof ChangeListeners) {
+ ChangeListeners oldListeners = (ChangeListeners)changeListeners;
+ changeListeners = oldListeners.remove(listener);
+ if (changeListeners == oldListeners)
+ return;
+ } else
+ return;
+ // notify listener on close
+ listener.subscriptionClosed();
+ }
+
+ //----------------------- package-private API for DXFeed -----------------------
+
+ /**
+ * Processes received events. This methods invokes {@link DXFeedEventListener#eventsReceived} on all installed
+ * event listeners. This is a package-private method for use by {@link DXFeed} class only.
+ * @param events the list of received events.
+ */
+ void processEvents(List events) {
+ DXFeedEventListener eventListeners = this.eventListeners; // atomic volatile read
+ if (eventListeners != null)
+ eventListeners.eventsReceived(events);
+ }
+
+ //----------------------- protected API for subclasses -----------------------
+
+ /**
+ * Decorates the specified symbol after it was received from the {@code DXFeedSubscription} client code
+ * before it goes to installed {@link ObservableSubscriptionChangeListener} instances.
+ * This method can be overridden in subclasses. See {@link DXFeedTimeSeriesSubscription} for an example.
+ * This implementation throws {@code NullPointerException} if {@code symbol} is null
+ * or returns {@code symbol} otherwise.
+ *
+ * @param symbol the symbol to decorate.
+ * @throws NullPointerException if symbol is null.
+ * @return decorated symbol
+ */
+ protected Object decorateSymbol(Object symbol) {
+ if (symbol == null)
+ throw new NullPointerException();
+ return symbol;
+ }
+
+ /**
+ * Undoes the decoration of the specified symbol doing the reverse operation to {@link #decorateSymbol(Object)}.
+ * This method can be overridden in subclasses. See {@link DXFeedTimeSeriesSubscription} for an example.
+ * This implementation throws {@code NullPointerException} if {@code symbol} is null
+ * or returns {@code symbol} otherwise.
+ *
+ * @param symbol the symbol to undecorate.
+ * @throws NullPointerException if symbol is null.
+ * @return undecorated symbol
+ */
+ protected Object undecorateSymbol(Object symbol) {
+ if (symbol == null)
+ throw new NullPointerException();
+ return symbol;
+ }
+
+ //----------------------- private implementation details -----------------------
+
+ private static void writeCompactCollection(ObjectOutput out, Collection> collection) throws IOException {
+ IOUtil.writeCompactInt(out, collection.size());
+ for (Object o : collection)
+ out.writeObject(o);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static T[] readCompactCollection(ObjectInput in) throws IOException, ClassNotFoundException {
+ int n = IOUtil.readCompactInt(in);
+ T[] a = (T[])new Object[n];
+ for (int i = 0; i < n; i++)
+ a[i] = (T)in.readObject();
+ return a;
+ }
+
+ private void init(Class extends E> eventType) {
+ eventTypeSet = new SingletonEventTypeSet(eventType);
+ initRest();
+ }
+
+ @SafeVarargs
+ private final void init(Class extends E>... eventTypes) {
+ if (eventTypes.length == 1) {
+ init(eventTypes[0]);
+ } else {
+ this.eventTypeSet = new MultipleEventTypes(eventTypes);
+ initRest();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initRest() {
+ eventSymbolIndexer = getClass() == DXFeedSubscription.class ? IndexerFunction.DEFAULT : this::undecorateSymbol;
+ symbols = IndexedSet.create(eventSymbolIndexer);
+ }
+
+ private IndexedSet decorateSymbols(Collection> symbols) {
+ return symbols.stream().map(this::decorateSymbol).collect(IndexedSet.collector(eventSymbolIndexer));
+ }
+
+ private IndexedSet decorateSymbols(Object... symbols) {
+ return Arrays.stream(symbols).map(this::decorateSymbol).collect(IndexedSet.collector(eventSymbolIndexer));
+ }
+
+ private synchronized void setSymbolsImpl(IndexedSet added) {
+ IndexedSet removed = IndexedSet.create(eventSymbolIndexer);
+ for (Iterator it = symbols.iterator(); it.hasNext(); ) {
+ Object oldSymbol = it.next();
+ if (!added.containsValue(oldSymbol)) {
+ it.remove();
+ removed.add(oldSymbol);
+ }
+ }
+ addAndNotify(added, removed);
+ }
+
+ private synchronized void addSymbolsImpl(IndexedSet added) {
+ addAndNotify(added, null);
+ }
+
+ private void addAndNotify(IndexedSet added, IndexedSet removed) {
+ for (Iterator it = added.iterator(); it.hasNext(); ) {
+ if (!putSymbol(it.next()))
+ it.remove(); // was there and "the same" -- don't process it
+ }
+ if (added.isEmpty() && (removed == null || removed.isEmpty()))
+ return;
+ if (changeListeners != null) {
+ if (removed != null && !removed.isEmpty())
+ changeListeners.symbolsRemoved(removed);
+ if (!added.isEmpty())
+ changeListeners.symbolsAdded(added);
+ }
+ }
+
+ private synchronized void addSymbolImpl(Object symbol) {
+ if (!putSymbol(symbol))
+ return; // was there and "the same" -- nothing to do
+ if (changeListeners != null)
+ changeListeners.symbolsAdded(Collections.singleton(symbol));
+ }
+
+ // returns false when "the same" symbol was already in the symbols set
+ private boolean putSymbol(Object symbol) {
+ Object oldSymbol = symbols.put(symbol);
+ return shallNotifyOnSymbolUpdate(symbol, oldSymbol);
+ }
+
+ /**
+ * Compares newly added symbol with the old one that was present before for
+ * the purpose of notifying {@link #addChangeListener(ObservableSubscriptionChangeListener) installed}
+ * {@link ObservableSubscriptionChangeListener} about the change.
+ * This method returns {@code false} if the new symbol shall be considered the same one and
+ * no notification is needed. The attached {@link ObservableSubscriptionChangeListener listeners} get
+ * {@link ObservableSubscriptionChangeListener#symbolsAdded(Set) symbolsAdded} notification for all
+ * symbols that this method returns {@code true} for.
+ *
+ * This implementation compares instances of {@link FilteredSubscriptionSymbol} by their reference
+ * (this implementation returns {@code true} when {@code symbol != oldSymbol}), because their equals and hashCode do
+ * not take input account their filter part. Notification for all other types of symbols (like {@link String}) is
+ * optimized away (this implementation returns {@code true} only when {@code oldSymbol == null}).
+ *
+ * @param symbol the recently added symbol to this subscription (not null).
+ * @param oldSymbol the previous symbol from the set of symbol based on equal/hashCode search in a hash set or
+ * {@code null} if it did not present before.
+ * @return {@code true} if listeners shall be notified on the change in the set of subscribed symbols.
+ */
+ protected boolean shallNotifyOnSymbolUpdate(@Nonnull Object symbol, @Nullable Object oldSymbol) {
+ return symbol instanceof FilteredSubscriptionSymbol ? symbol != oldSymbol : oldSymbol == null;
+ }
+
+ private synchronized void removeSymbolsImpl(IndexedSet removed) {
+ // remove symbols by key (regardless of identify)
+ for (Iterator it = removed.concurrentIterator(); it.hasNext(); ) {
+ Object symbol = it.next();
+ Object oldSymbol = symbols.removeValue(symbol);
+ if (oldSymbol == null)
+ removed.remove(symbol); // was not there -- nothing to remove
+ else if (oldSymbol != symbol)
+ removed.add(oldSymbol); // replace with actually removed identity
+ }
+ if (removed.isEmpty())
+ return;
+ if (changeListeners != null)
+ changeListeners.symbolsRemoved(removed);
+ }
+
+ private synchronized void writeObject(ObjectOutputStream out) throws IOException {
+ writeCompactCollection(out, eventTypeSet.asSet());
+ writeCompactCollection(out, symbols);
+ writeCompactCollection(out, getSerializable(eventListeners));
+ writeCompactCollection(out, getSerializable(changeListeners));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ int n = IOUtil.readCompactInt(in);
+ Class extends E>[] eventTypes = (Class extends E>[])new Class>[n];
+ for (int i = 0; i < n; i++)
+ eventTypes[i] = (Class extends E>)in.readObject();
+ init(eventTypes);
+ Collections.addAll(symbols, readCompactCollection(in));
+ eventListeners = new EventListeners(DXFeedSubscription.>readCompactCollection(in)).simplify();
+ changeListeners = new ChangeListeners(DXFeedSubscription.readCompactCollection(in)).simplify();
+ }
+
+ private abstract class EventTypeSet {
+ protected EventTypeSet() {}
+
+ abstract boolean contains(Class> eventType);
+ abstract Set> asSet();
+ }
+
+ private class SingletonEventTypeSet extends EventTypeSet {
+ private final Class extends E> eventType;
+ private Set> set;
+
+ SingletonEventTypeSet(Class extends E> eventType) {
+ if (eventType == null)
+ throw new NullPointerException();
+ this.eventType = eventType;
+ }
+
+ @Override
+ boolean contains(Class> eventType) {
+ return this.eventType == eventType;
+ }
+
+ @Override
+ Set> asSet() {
+ if (set == null)
+ set = Collections.>singleton(eventType);
+ return set;
+ }
+ }
+
+ private class MultipleEventTypes extends EventTypeSet {
+ private final Class extends E>[] eventTypes;
+ private Set> set;
+
+ @SuppressWarnings("unchecked")
+ MultipleEventTypes(Class extends E>... eventTypes) {
+ if (eventTypes.length == 0)
+ throw new IllegalArgumentException();
+ for (Class extends E> eventType : eventTypes)
+ if (eventType == null)
+ throw new NullPointerException();
+ int n = eventTypes.length;
+ this.eventTypes = (Class[])new Class>[n];
+ System.arraycopy(eventTypes, 0, this.eventTypes, 0, n);
+ }
+
+ @Override
+ boolean contains(Class> eventType) {
+ return asSet().contains(eventType);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ Set> asSet() {
+ if (set == null)
+ set = Collections.unmodifiableSet(new IndexedSet, Class extends E>>(Arrays.asList(eventTypes)));
+ return set;
+ }
+ }
+
+ private class UndecoratedSymbols extends AbstractSet {
+ UndecoratedSymbols() {}
+
+ @Nonnull
+ @Override
+ public Iterator iterator() {
+ return new UndecoratedIterator(symbols.concurrentIterator());
+ }
+
+ @Override
+ public int size() {
+ return symbols.size();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return symbols.containsKey(o);
+ }
+ }
+
+ private class UndecoratedIterator implements Iterator {
+ private final Iterator it;
+
+ UndecoratedIterator(Iterator it) {
+ this.it = it;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public Object next() {
+ return undecorateSymbol(it.next());
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ //----------------------- private listener lists -----------------------
+
+ private static Collection getSerializable(Object oneOrList) {
+ if (oneOrList instanceof Serializable)
+ return Collections.singletonList(oneOrList);
+ if (oneOrList instanceof ListenerList) {
+ Collection result = new ArrayList<>();
+ for (Object o : ((ListenerList>)oneOrList).a)
+ if (o instanceof Serializable)
+ result.add(o);
+ return result;
+ }
+ return Collections.emptyList();
+ }
+
+ static Object[] addListener(L oldList, L newListener) {
+ int n = oldList instanceof ListenerList ? ((ListenerList>)oldList).a.length : 1;
+ Object[] a = new Object[n + 1];
+ if (oldList instanceof ListenerList)
+ System.arraycopy(((ListenerList>)oldList).a, 0, a, 0, n);
+ else
+ a[0] = oldList;
+ a[n] = newListener;
+ return a;
+ }
+
+ static int findListener(ListenerList oldList, L newListener) {
+ for (int i = 0; i < oldList.a.length; i++)
+ if (oldList.a[i] == newListener)
+ return i;
+ return -1;
+ }
+
+ static Object[] removeListenerAt(ListenerList oldList, int removeIndex) {
+ Object[] a = new Object[oldList.a.length - 1];
+ System.arraycopy(oldList.a, 0, a, 0, removeIndex);
+ System.arraycopy(oldList.a, removeIndex + 1, a, removeIndex, oldList.a.length - removeIndex - 1);
+ return a;
+ }
+
+ private abstract static class ListenerList {
+ final Object[] a;
+
+ protected ListenerList(Object[] a) {
+ this.a = a;
+ }
+
+ abstract L create(Object[] a);
+
+ @SuppressWarnings({"unchecked"})
+ L simplify() {
+ return a.length == 0 ? null : a.length == 1 ? (L)a[0] : (L)this;
+ }
+
+ @SuppressWarnings({"unchecked"})
+ L remove(L listener) {
+ int i = findListener(this, listener);
+ return i < 0 ? (L)this : a.length == 2 ? (L)a[1 - i] : create(removeListenerAt(this, i)) ;
+ }
+ }
+
+ private static class EventListeners extends ListenerList> implements DXFeedEventListener {
+ EventListeners(Object[] a) {
+ super(a);
+ }
+
+ @Override
+ DXFeedEventListener create(Object[] a) {
+ return new EventListeners<>(a);
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked"})
+ public void eventsReceived(List events) {
+ for (Object listener : a)
+ ((DXFeedEventListener)listener).eventsReceived(events);
+ }
+ }
+
+ private static class ChangeListeners extends ListenerList implements ObservableSubscriptionChangeListener {
+ ChangeListeners(Object[] a) {
+ super(a);
+ }
+
+ @Override
+ ObservableSubscriptionChangeListener create(Object[] a) {
+ return new ChangeListeners(a);
+ }
+
+ @Override
+ public void symbolsAdded(Set> symbols) {
+ for (Object listener : a)
+ ((ObservableSubscriptionChangeListener)listener).symbolsAdded(symbols);
+ }
+
+ @Override
+ public void symbolsRemoved(Set> symbols) {
+ for (Object listener : a)
+ ((ObservableSubscriptionChangeListener)listener).symbolsRemoved(symbols);
+ }
+
+ @Override
+ public void subscriptionClosed() {
+ for (Object listener : a)
+ ((ObservableSubscriptionChangeListener)listener).subscriptionClosed();
+ }
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/DXFeedTimeSeriesSubscription.java b/dxfeed-api/src/main/java/com/dxfeed/api/DXFeedTimeSeriesSubscription.java
new file mode 100644
index 000000000..83df59372
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/DXFeedTimeSeriesSubscription.java
@@ -0,0 +1,127 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api;
+
+import com.dxfeed.api.osub.ObservableSubscriptionChangeListener;
+import com.dxfeed.api.osub.TimeSeriesSubscriptionSymbol;
+import com.dxfeed.event.TimeSeriesEvent;
+
+/**
+ * Extends {@link DXFeedSubscription} to conveniently subscribe to time-series of
+ * events for a set of symbols and event types. This class decorates symbols
+ * that are passed to {@code xxxSymbols} methods in {@link DXFeedSubscription}
+ * by wrapping them into {@link TimeSeriesSubscriptionSymbol} instances with
+ * the current value of {@link #getFromTime() fromTime} property.
+ * While {@link #getSymbols() getSymbols} method returns original (undecorated)
+ * symbols, any installed {@link ObservableSubscriptionChangeListener} will see
+ * decorated ones.
+ *
+ * Only events that implement {@link TimeSeriesEvent} interface can be
+ * subscribed to with {@code DXFeedTimeSeriesSubscription}.
+ *
+ *
From time
+ *
+ * The value of {@link #getFromTime() fromTime} property defines the time-span of
+ * events that are subscribed to. Only events that satisfy
+ * {@code event.getEventTime() >= thisSubscription.getFromTime()} are looked for.
+ *
+ * The value {@code fromTime} is initially set to {@link Long#MAX_VALUE Long.MAX_VALUE}
+ * with a special meaning that no events will be received until {@code fromTime} is
+ * changed with {@link #setFromTime(long) setFromTime} method.
+ *
+ *
Threads and locks
+ *
+ * This class is thread-safe and can be used concurrently from multiple threads without external synchronization.
+ *
+ * @param the type of events.
+ */
+public class DXFeedTimeSeriesSubscription extends DXFeedSubscription {
+ private static final long serialVersionUID = 0;
+
+ private long fromTime = Long.MAX_VALUE;
+
+ /**
+ * Creates detached time-series subscription for a single event type.
+ *
+ * @param eventType the event type.
+ * @throws NullPointerException if event type is null.
+ */
+ public DXFeedTimeSeriesSubscription(Class extends E> eventType) {
+ super(eventType);
+ }
+
+ /**
+ * Creates detached time-series subscription for the given list of event types.
+ *
+ * @param eventTypes the list of event types.
+ * @throws IllegalArgumentException if the list of event types is empty.
+ * @throws NullPointerException if any event type is null.
+ */
+ public DXFeedTimeSeriesSubscription(Class extends E>... eventTypes) {
+ super(eventTypes);
+ }
+
+ /**
+ * Returns the earliest timestamp from which time-series of events shall be received.
+ * The timestamp is in milliseconds from midnight, January 1, 1970 UTC.
+ *
+ * @return the earliest timestamp from which time-series of events shall be received.
+ *
+ * @see System#currentTimeMillis()
+ */
+ public synchronized long getFromTime() {
+ return fromTime;
+ }
+
+ /**
+ * Sets the earliest timestamp from which time-series of events shall be received.
+ * The timestamp is in milliseconds from midnight, January 1, 1970 UTC.
+ *
+ * @param fromTime the timestamp.
+ *
+ * @see System#currentTimeMillis()
+ */
+ public synchronized void setFromTime(long fromTime) {
+ if (this.fromTime != fromTime) {
+ this.fromTime = fromTime;
+ setSymbols(getSymbols());
+ }
+ }
+
+ /**
+ * Decorates the specified symbol after it was received from the {@code DXFeedSubscription} client code
+ * before it goes to installed {@link ObservableSubscriptionChangeListener} instances.
+ * This implementation wraps symbol into {@link TimeSeriesSubscriptionSymbol} with a current
+ * {@link #getFromTime() fromTime} value.
+ *
+ * @param symbol the symbol to decorate.
+ * @throws NullPointerException if symbol is null.
+ * @return decorated symbol.
+ */
+ @Override
+ protected Object decorateSymbol(Object symbol) {
+ return new TimeSeriesSubscriptionSymbol(symbol, fromTime);
+ }
+
+ /**
+ * Undoes the decoration of the specified symbol doing the reverse operation to {@link #decorateSymbol(Object)}.
+ * This implementation throws {@code ClassCastException} is symbol is not an instance of
+ * {@link TimeSeriesSubscriptionSymbol} or returns its
+ * {@link TimeSeriesSubscriptionSymbol#getEventSymbol() eventSymbol} otherwise.
+ *
+ * @param symbol the symbol to undecorate.
+ * @throws NullPointerException if symbol is null.
+ * @throws ClassCastException if symbol is not instance of {@link TimeSeriesSubscriptionSymbol}.
+ * @return undecorated symbol.
+ */
+ @Override
+ protected Object undecorateSymbol(Object symbol) {
+ return ((TimeSeriesSubscriptionSymbol>)symbol).getEventSymbol();
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/DXPublisher.java b/dxfeed-api/src/main/java/com/dxfeed/api/DXPublisher.java
new file mode 100644
index 000000000..06de458e3
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/DXPublisher.java
@@ -0,0 +1,148 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api;
+
+import java.util.Collection;
+import java.util.Set;
+
+import com.dxfeed.api.osub.*;
+import com.dxfeed.event.LastingEvent;
+import com.dxfeed.event.TimeSeriesEvent;
+import com.dxfeed.event.market.Profile;
+import com.dxfeed.event.market.Quote;
+
+/**
+ * Provides API for publishing of events to local or remote {@link DXFeed feeds}.
+ *
+ * Sample usage
+ *
+ * This section gives sample usage scenarios.
+ *
+ * Default singleton instance
+ *
+ * There is a singleton instance of the publisher that is returned by {@link #getInstance()} method.
+ * It is created on the first use with default configuration properties that are explained in detail in
+ * documentation for {@link DXEndpoint} class in the "Default properties" section. In particular,
+ * you can provide a default address to connect using
+ * "{@link DXEndpoint#DXPUBLISHER_ADDRESS_PROPERTY dxpublisher.address}"
+ * system property or by putting it into
+ * "{@link DXEndpoint#DXPUBLISHER_PROPERTIES_PROPERTY dxpublisher.properties}"
+ * file on JVM classpath.
+ *
+ * Publish a single event
+ *
+ * The following code publishes a single quote for a "A:TEST" symbol:
+ *
+ * {@link Quote Quote} quote = new Quote("A:TEST");
+ * quote.setBidPrice(100);
+ * quote.setAskPrice(101);
+ * {@link DXPublisher DXPublisher}.{@link #getInstance() getInstance}().{@link #publishEvents(Collection) publishEvents}(Arrays.asList(quote));
+ *
+ * Monitor subscription and publish profile for any test symbol
+ *
+ * The following code monitor subscription for {@link Profile} events and for any subscription
+ * on the string symbols that end with ":TEST" string generates and publishes a profile.
+ *
+ *
+ * final {@link DXPublisher DXPublisher} publisher = {@link DXPublisher DXPublisher}.{@link #getInstance() getInstance}();
+ * publisher.{@link #getSubscription(Class) getSubscription}({@link Profile Profile}.class).{@link ObservableSubscription#addChangeListener(ObservableSubscriptionChangeListener) addChangeListener}(new {@link ObservableSubscriptionChangeListener ObservableSubscriptionChangeListener}() {
+ * public void {@link ObservableSubscriptionChangeListener#symbolsAdded(Set) symbolsAdded}(Set<?> symbols) {
+ * List<Profile> events = new ArrayList<Profile>();
+ * for (Object symbol : symbols) {
+ * if (symbol instanceof String) {
+ * String s = (String)symbol;
+ * if (s.endsWith(":TEST")) {
+ * Profile profile = new Profile(s);
+ * profile.setDescription("Test symbol");
+ * events.add(profile);
+ * }
+ * }
+ * }
+ * publisher.{@link #publishEvents(Collection) publishEvents}(events);
+ * }
+ *
+ * public void symbolsRemoved(Set<?> symbols) {
+ * // nothing to do here
+ * }
+ *
+ * public void subscriptionClosed() {
+ * // nothing to do here
+ * }
+ * });
+ *
+ * Threads and locks
+ *
+ * This class is thread-safe and can be used concurrently from multiple threads without external synchronization.
+ */
+public abstract class DXPublisher {
+ /**
+ * Protected constructor for implementations of this class only.
+ */
+ protected DXPublisher() {}
+
+ /**
+ * Returns a default application-wide singleton instance of DXPublisher. Most applications use only a single
+ * data-sink and should rely on this method to get one. This is a shortcut to
+ * {@link DXEndpoint DXEndpoint}.{@link DXEndpoint#getInstance(DXEndpoint.Role) getInstance}({@link DXEndpoint.Role#PUBLISHER DXEndpoint.Role.PUBLISHER}).{@link DXEndpoint#getPublisher() getPublisher}().
+ */
+ public static DXPublisher getInstance() {
+ return DXEndpoint.getInstance(DXEndpoint.Role.PUBLISHER).getPublisher();
+ }
+
+ /**
+ * Publishes events to the corresponding feed. If the {@link DXEndpoint endpoint} of this publisher has
+ * {@link DXEndpoint#getRole() role} of {@link DXEndpoint.Role#PUBLISHER} and it is connected, the
+ * published events will be delivered to the remote endpoints. Local {@link DXEndpoint#getFeed() feed} will
+ * always receive published events.
+ *
+ * This method serializes all events into internal representation, so that the instance of the collection as
+ * well as the instances of events can be reused after invocation of this method returns.
+ *
+ *
{@link DXFeed} instances that are connected to this publisher either locally or via network
+ * receive published events if and only if they are subscribed to the corresponding symbols, or
+ * they are subscribed via {@link WildcardSymbol#ALL WildcardSymbol.ALL}, or, in case of
+ * {@link TimeSeriesEvent} type, they are subscribed via {@link DXFeedTimeSeriesSubscription} for
+ * the corresponding symbol and time frame.
+ *
+ *
Published events are not stored and get immediately lost if there is no subscription.
+ * Last published events of {@link LastingEvent} types are cached as long as subscription to
+ * them is maintained via a specific event symbol ({@link WildcardSymbol#ALL WildcardSymbol.ALL} does not count)
+ * and the cache is discarded as soon as subscription disappears.
+ *
+ * @param events the collection of events to publish.
+ */
+ public abstract void publishEvents(Collection> events);
+
+ /**
+ * Returns observable set of subscribed symbols for the specified event type.
+ * Note, that subscription is represented by object symbols. Check the type of each symbol
+ * in {@link ObservableSubscription} using {@code instanceof} operation.
+ *
+ *
The set of subscribed symbols contains {@link WildcardSymbol#ALL WildcardSymbol.ALL} if and
+ * only if there is a subscription to this wildcard symbol.
+ *
+ *
If {@link DXFeedTimeSeriesSubscription} is used
+ * to subscribe to time-service of the events of this type, then instances of
+ * {@link TimeSeriesSubscriptionSymbol} class represent the corresponding subscription item.
+ *
+ *
The resulting observable subscription can generate repeated
+ * {@link ObservableSubscriptionChangeListener#symbolsAdded(Set) symbolsAdded} notifications to
+ * its listeners for the same symbols without the corresponding
+ * {@link ObservableSubscriptionChangeListener#symbolsRemoved(Set) symbolsRemoved}
+ * notifications in between them. It happens when subscription disappears, cached data is lost, and subscription
+ * reappears again. On each {@link ObservableSubscriptionChangeListener#symbolsAdded(Set) symbolsAdded}
+ * notification data provider shall {@link #publishEvents(Collection) publish} the most recent events for
+ * the corresponding symbols.
+ *
+ * @param eventType the class of event.
+ * @param the type of event.
+ * @return Observable subscription for the specified event type.
+ */
+ public abstract ObservableSubscription getSubscription(Class eventType);
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/FilteredSubscriptionSymbol.java b/dxfeed-api/src/main/java/com/dxfeed/api/FilteredSubscriptionSymbol.java
new file mode 100644
index 000000000..4ed8b7bac
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/FilteredSubscriptionSymbol.java
@@ -0,0 +1,30 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api;
+
+import com.dxfeed.api.osub.ObservableSubscriptionChangeListener;
+import com.dxfeed.api.osub.TimeSeriesSubscriptionSymbol;
+
+/**
+ * This marker interface marks subscription symbol classes (like {@link TimeSeriesSubscriptionSymbol})
+ * that attach "filters" to the actual symbols. Filtered subscription symbols implement their
+ * {@link Object#equals(Object)} and {@link Object#hashCode()} methods based on their symbol only,
+ * without considering their "filter" part (for example, a {@link TimeSeriesSubscriptionSymbol} has
+ * a {@link TimeSeriesSubscriptionSymbol#getFromTime() fromTime} filter on a time range of events).
+ *
+ * {@link DXFeedSubscription} has the following behavior for filtered symbols. There can be only one
+ * active filter per symbol. Adding new filtered symbol with the same symbol but different filter
+ * removes the old one from subscription, adds the new one, and notifies
+ * {@link ObservableSubscriptionChangeListener} about this subscription change .
+ * The later is a special behaviour for filtered subscription symbols, because on other types of
+ * symbols (like {@link String}, for example) there is no notification when replacing one symbol
+ * with the other that is "equal" to the first one.
+ */
+public interface FilteredSubscriptionSymbol {
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/impl/ExtensibleDXEndpoint.java b/dxfeed-api/src/main/java/com/dxfeed/api/impl/ExtensibleDXEndpoint.java
new file mode 100644
index 000000000..828b2a129
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/impl/ExtensibleDXEndpoint.java
@@ -0,0 +1,36 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api.impl;
+
+import com.devexperts.util.*;
+import com.dxfeed.api.DXEndpoint;
+
+public abstract class ExtensibleDXEndpoint extends DXEndpoint {
+ private TypedMap extensions;
+
+ public abstract Object getLock();
+
+ public T getExtension(TypedKey key) {
+ synchronized (getLock()) {
+ return extensions == null ? null : extensions.get(key);
+ }
+ }
+
+ public void setExtension(TypedKey key, T value) {
+ synchronized (getLock()) {
+ if (extensions == null)
+ extensions = new TypedMap();
+ extensions.set(key, value);
+ }
+ }
+
+ public abstract ExecutorProvider.Reference getExecutorReference();
+
+ public abstract void setConnectedAddressSync(String address);
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/osub/IndexedEventSubscriptionSymbol.java b/dxfeed-api/src/main/java/com/dxfeed/api/osub/IndexedEventSubscriptionSymbol.java
new file mode 100644
index 000000000..5cb72b012
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/osub/IndexedEventSubscriptionSymbol.java
@@ -0,0 +1,108 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api.osub;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import com.dxfeed.api.DXFeedSubscription;
+import com.dxfeed.event.*;
+
+/**
+ * Represents subscription to a specific source of indexed events.
+ * This is symbol is observed by {@link ObservableSubscriptionChangeListener}
+ * methods {@link ObservableSubscriptionChangeListener#symbolsAdded(Set) symbolsAdded}
+ * and {@link ObservableSubscriptionChangeListener#symbolsRemoved(Set) symbolsRemoved}
+ * when subscription to {@link IndexedEvent} is defined.
+ *
+ * Instances of this class can be used with {@link DXFeedSubscription} to specify subscription
+ * to a particular source of indexed events. By default, when subscribing to indexed events by
+ * their event symbol object, the subscription is performed to all supported sources.
+ *
+ *
{@link TimeSeriesEvent} represents a special subtype of {@link IndexedEvent}.
+ * There is a more specific {@link TimeSeriesSubscriptionSymbol} class
+ * that is used when time-series subscription is observed.
+ *
+ *
Equality and hash codes
+ *
+ * Indexed event subscription symbols are compared based on their {@link #getEventSymbol() eventSymbol} and
+ * {@link #getSource() source}.
+ *
+ * @param the type of the event symbol.
+ */
+public class IndexedEventSubscriptionSymbol implements Serializable {
+ private static final long serialVersionUID = 0;
+
+ private final T eventSymbol;
+ private final IndexedEventSource source;
+
+ /**
+ * Creates indexed event subscription symbol with a specified event symbol and source.
+ * @param eventSymbol the event symbol.
+ * @param source the source.
+ * @throws NullPointerException if eventSymbol or source are null {@code null}.
+ */
+ public IndexedEventSubscriptionSymbol(T eventSymbol, IndexedEventSource source) {
+ if (eventSymbol == null)
+ throw new NullPointerException();
+ if (source == null)
+ throw new NullPointerException();
+ this.eventSymbol = eventSymbol;
+ this.source = source;
+ }
+
+ /**
+ * Returns event symbol.
+ * @return event symbol.
+ */
+ public final T getEventSymbol() {
+ return eventSymbol;
+ }
+
+ /**
+ * Returns indexed event source.
+ * @return indexed event source.
+ */
+ public final IndexedEventSource getSource() {
+ return source;
+ }
+
+ /**
+ * Returns {@code true} if other object is {@code IndexedEventSubscriptionSymbol} with
+ * the same {@code eventSymbol} and {@code sourceId}.
+ * @return result of equality check.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (!(o instanceof IndexedEventSubscriptionSymbol))
+ return false;
+ IndexedEventSubscriptionSymbol> that = (IndexedEventSubscriptionSymbol>)o;
+ return eventSymbol.equals(that.eventSymbol) && source.equals(that.source);
+ }
+
+ /**
+ * Returns hash code.
+ * @return hash code.
+ */
+ @Override
+ public int hashCode() {
+ return eventSymbol.hashCode() + source.hashCode() * 31;
+ }
+
+ /**
+ * Returns string representation of this indexed event subscription symbol.
+ * @return string representation of this indexed event subscription symbol.
+ */
+ @Override
+ public String toString() {
+ return eventSymbol + "{source=" + source + "}";
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/osub/ObservableSubscription.java b/dxfeed-api/src/main/java/com/dxfeed/api/osub/ObservableSubscription.java
new file mode 100644
index 000000000..24146c1aa
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/osub/ObservableSubscription.java
@@ -0,0 +1,69 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api.osub;
+
+import java.util.Set;
+
+import com.dxfeed.api.DXEndpoint;
+import com.dxfeed.api.DXPublisher;
+
+/**
+ * Observable set of subscription symbols for the specific event type.
+ *
+ * @param the type of events.
+ */
+public interface ObservableSubscription {
+ /**
+ * Returns true
if this subscription is closed.
+ * The observable subscription that is returned as a result of
+ * {@link DXPublisher#getSubscription(Class) DXPublisher.getSubscription}
+ * is closed when the corresponding {@link DXEndpoint DXEndpoint} is closed.
+ */
+ public boolean isClosed();
+
+ /**
+ * Returns a set of event types for this subscription. The resulting set cannot be modified.
+ * The observable subscription that is returned as a result of
+ * {@link DXPublisher#getSubscription(Class) DXPublisher.getSubscription}
+ * has a singleton set of event types.
+ */
+ public Set> getEventTypes();
+
+ /**
+ * Returns true
if this subscription contains the corresponding event type.
+ * @see #getEventTypes()
+ */
+ public boolean containsEventType(Class> eventType);
+
+ /**
+ * Adds subscription change listener. This method does nothing if the given listener is already
+ * installed as subscription change listener for this subscription or if subscription is closed.
+ * Otherwise, it installs the
+ * corresponding listener and immediately invokes {@link ObservableSubscriptionChangeListener#symbolsAdded}
+ * on the given listener while holding the lock for this
+ * subscription. This way the given listener synchronously receives existing subscription state and and
+ * is synchronously notified on all changes in subscription afterwards.
+ *
+ * @param listener the subscription change listener.
+ * @throws NullPointerException if listener is null.
+ */
+ public void addChangeListener(ObservableSubscriptionChangeListener listener);
+
+ /**
+ * Removes subscription change listener. This method does nothing if the given listener was not
+ * installed or was already removed as subscription change listener for this subscription.
+ * Otherwise it removes the corresponding listener and immediately invokes
+ * {@link ObservableSubscriptionChangeListener#subscriptionClosed} on the given listener while
+ * holding the lock for this subscription.
+ *
+ * @param listener the subscription change listener.
+ * @throws NullPointerException if listener is null.
+ */
+ public void removeChangeListener(ObservableSubscriptionChangeListener listener);
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/osub/ObservableSubscriptionChangeListener.java b/dxfeed-api/src/main/java/com/dxfeed/api/osub/ObservableSubscriptionChangeListener.java
new file mode 100644
index 000000000..85abf6b2b
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/osub/ObservableSubscriptionChangeListener.java
@@ -0,0 +1,81 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api.osub;
+
+import java.util.Collection;
+import java.util.Set;
+
+import com.dxfeed.api.*;
+
+/**
+ * The listener interface for receiving notifications on the changes of observed subscription.
+ * All methods on this interface are invoked while holding a lock on {@link ObservableSubscription} instance,
+ * thus all changes for a given subscription are synchronized with respect to each other.
+ *
+ * Decorated symbols
+ *
+ * The sets of symbols that are passed to {@link #symbolsAdded(Set)} and {@link #symbolsRemoved(Set)}
+ * are decorated depending on the actual implementation class of {@link DXFeedSubscription}.
+ * {@link DXFeedTimeSeriesSubscription} decorates original subscription symbols by wrapping them
+ * into instances of {@link TimeSeriesSubscriptionSymbol} class.
+ *
+ * Equality of symbols and notifications
+ *
+ * Symbols are compared using their {@link Object#equals(Object) equals} method. When one symbol in subscription is
+ * replaced by the other one that is equal to it, then the decision of whether to issue
+ * {@link #symbolsAdded(Set) symbolsAdded} notification is up to implementation.
+ *
+ * In particular, {@link DXFeedSubscription} uses its implementation of
+ * {@link DXFeedSubscription#shallNotifyOnSymbolUpdate(Object, Object) shallNotifyOnSymbolUpdate} method
+ * to figure out what to do in this case. Its default implementation is designed so that repeated
+ * additions of the same {@link String} symbol do not result in notification, while
+ * repeated additions of {@link FilteredSubscriptionSymbol} instances like {@link TimeSeriesSubscriptionSymbol} objects
+ * get notification.
+ *
+ *
However, the implementation that is returned by
+ * {@link DXPublisher#getSubscription(Class) DXPublisher.getSubscription} can generate repeated
+ * {@link ObservableSubscriptionChangeListener#symbolsAdded(Set) symbolsAdded} notifications to
+ * its listeners for the same symbols without the corresponding
+ * {@link ObservableSubscriptionChangeListener#symbolsRemoved(Set) symbolsRemoved}
+ * notifications in between them. It happens when subscription disappears, cached data is lost, and subscription
+ * reappears again. On each {@link ObservableSubscriptionChangeListener#symbolsAdded(Set) symbolsAdded}
+ * notification data provider shall {@link DXPublisher#publishEvents(Collection) publish} the most recent events for
+ * the corresponding symbols.
+ *
+ *
Wildcard symbols
+ *
+ * The set of symbols may contain {@link WildcardSymbol#ALL} object.
+ * See {@link WildcardSymbol} for details.
+ */
+@FunctionalInterface
+public interface ObservableSubscriptionChangeListener {
+ /**
+ * Invoked after a collection of symbols is added to a subscription.
+ * Subscription's set of symbols already includes added symbols when this method is invoked.
+ * The set of symbols is decorated.
+ */
+ public void symbolsAdded(Set> symbols);
+
+ /**
+ * Invoked after a collection of symbols is removed from a subscription.
+ * Subscription's set of symbols already excludes removed symbols when this method is invoked.
+ * The set of symbols is decorated.
+ * Default implementation is empty.
+ */
+ public default void symbolsRemoved(Set> symbols) {}
+
+ /**
+ * Invoked after subscription is closed or when this listener is
+ * {@link DXFeedSubscription#removeChangeListener removed} from the subscription.
+ * {@link DXPublisher} {@link DXPublisher#getSubscription(Class) subscription} is considered to be closed
+ * when the corresponding {@link DXEndpoint} is {@link DXEndpoint#close closed}.
+ * Default implementation is empty.
+ */
+ public default void subscriptionClosed() {}
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/osub/TimeSeriesSubscriptionSymbol.java b/dxfeed-api/src/main/java/com/dxfeed/api/osub/TimeSeriesSubscriptionSymbol.java
new file mode 100644
index 000000000..dd2a59601
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/osub/TimeSeriesSubscriptionSymbol.java
@@ -0,0 +1,100 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api.osub;
+
+import java.util.Set;
+
+import com.devexperts.util.TimeFormat;
+import com.dxfeed.api.*;
+import com.dxfeed.event.*;
+
+/**
+ * Represents subscription to time-series of events.
+ * This is symbol is observed by {@link ObservableSubscriptionChangeListener}
+ * methods {@link ObservableSubscriptionChangeListener#symbolsAdded(Set) symbolsAdded}
+ * and {@link ObservableSubscriptionChangeListener#symbolsRemoved(Set) symbolsRemoved}
+ * when time-series subscription is created via {@link DXFeedTimeSeriesSubscription} class.
+ *
+ * Instances of this class can be used with {@link DXFeedSubscription} 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.
+ *
+ *
{@link TimeSeriesEvent} represents a special subtype of {@link IndexedEvent}.
+ * The source identifier of a time-series event is always zero and thus
+ * {@link #getSource() getSource} method for time-series subscription symbol always returns
+ * {@link IndexedEventSource#DEFAULT DEFAULT}.
+ *
+ *
Equality and hash codes
+ *
+ * This is a {@link FilteredSubscriptionSymbol}.
+ * Time-series subscription symbols are compared based on their {@link #getEventSymbol() eventSymbol} only.
+ * It means, that a set of time-series subscription symbols can contain at most one time-series subscription
+ * for each event symbol.
+ *
+ * @param the type of the event symbol.
+ */
+public class TimeSeriesSubscriptionSymbol extends IndexedEventSubscriptionSymbol
+ implements FilteredSubscriptionSymbol
+{
+ private static final long serialVersionUID = 0;
+
+ private final long fromTime;
+
+ /**
+ * Creates time-series subscription symbol with a specified event symbol and subscription time.
+ * @param eventSymbol the event symbol.
+ * @param fromTime the subscription time.
+ * @throws NullPointerException if eventSymbol is {@code null}.
+ */
+ public TimeSeriesSubscriptionSymbol(T eventSymbol, long fromTime) {
+ super(eventSymbol, IndexedEventSource.DEFAULT);
+ this.fromTime = fromTime;
+ }
+
+ /**
+ * Returns subscription time.
+ * @return subscription time.
+ */
+ public final long getFromTime() {
+ return fromTime;
+ }
+
+ /**
+ * Returns {@code true} if other object is {@code TimeSeriesSubscriptionSymbol} with
+ * the same {@code eventSymbol}.
+ * @return result of equality check.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (!(o instanceof TimeSeriesSubscriptionSymbol))
+ return false;
+ TimeSeriesSubscriptionSymbol> that = (TimeSeriesSubscriptionSymbol>)o;
+ return getEventSymbol().equals(that.getEventSymbol());
+ }
+
+ /**
+ * Returns {@code eventSymbol} hash code.
+ * @return {@code eventSymbol} hash code.
+ */
+ @Override
+ public int hashCode() {
+ return getEventSymbol().hashCode();
+ }
+
+ /**
+ * Returns string representation of this time-series subscription symbol.
+ * @return string representation of this time-series subscription symbol.
+ */
+ @Override
+ public String toString() {
+ return getEventSymbol() + "{fromTime=" + TimeFormat.DEFAULT.withMillis().format(fromTime) + "}";
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/osub/WildcardSymbol.java b/dxfeed-api/src/main/java/com/dxfeed/api/osub/WildcardSymbol.java
new file mode 100644
index 000000000..c5b157fee
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/osub/WildcardSymbol.java
@@ -0,0 +1,129 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.api.osub;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import com.dxfeed.api.*;
+import com.dxfeed.event.LastingEvent;
+import com.dxfeed.event.market.TimeAndSale;
+
+/**
+ * Represents [wildcard] subscription to all events of the specific event type.
+ * The {@link WildcardSymbol#ALL WildcardSymbol.ALL} constant can be added to any
+ * {@link DXFeedSubscription} instance with {@link DXFeedSubscription#addSymbols(Collection) addSymbols} method
+ * to the effect of subscribing to all possible event symbols. The corresponding subscription will start
+ * receiving all published events of the corresponding types.
+ *
+ * NOTE: Wildcard subscription can create extremely high network and CPU load for certain kinds of
+ * high-frequency events like quotes. It requires a special arrangement on the side of upstream data provider and
+ * is disabled by default in upstream feed configuration. Make that sure you have adequate resources and understand
+ * the impact before using it. It can be used for low-frequency events only (like Forex quotes), because each instance
+ * of {@link DXFeedSubscription} processes events in a single thread and there is no provision to load-balance wildcard
+ * subscription amongst multiple threads. Contact your data provider for the corresponding configuration arrangement if needed.
+ *
+ *
You need to create {@link DXEndpoint} with {@link DXEndpoint#DXFEED_WILDCARD_ENABLE_PROPERTY DXFEED_WILDCARD_ENABLE_PROPERTY}
+ * property in order to support subscription to this wildcard symbols via
+ * {@link DXFeed} and to observe wildcard subscription via {@link DXPublisher}.
+ *
+ *
Sample usage
+ *
+ * See {@link DXFeed} on how to create connection to the feed.
+ * The following code creates listener that prints all {@link TimeAndSale} events that are
+ * coming from the feed using wildcard subscription symbol:
+ *
+ *
+ * DXFeedSubscription<{@link TimeAndSale TimeAndSale}> sub = feed.{@link DXFeed#createSubscription(Class) createSubscription}({@link TimeAndSale TimeAndSale.class});
+ * sub.{@link DXFeedSubscription#addEventListener addEventListener}(new DXFeedEventListener<TimeAndSale>() {
+ * public void eventsReceived(List<TimeAndSale> events) {
+ * for (TimeAndSale event : events)
+ * System.out.println(event);
+ * }
+ * });
+ * sub.{@link DXFeedSubscription#addSymbols(Object...) addSymbols}({@link WildcardSymbol#ALL WildcardSymbol.ALL});
+ *
+ * Observing wildcard subscription
+ *
+ * Any instance of {@link ObservableSubscription} that is retrieved via
+ * {@link DXPublisher#getSubscription(Class) DXPublisher.getSubscription} method can observe
+ * {@link WildcardSymbol#ALL WildcardSymbol.ALL} object in its set of symbols if any feed consumer
+ * subscribes to wildcard symbol. The recommended approach is to use {@code instanceof WildcardSymbol} check if support
+ * of wildcard subscription is required.
+ *
+ * Limitations
+ *
+ * Do not mix {@code WildcardSymbol} subscription and subscription to other symbols in a single instance
+ * of {@link DXFeedSubscription}. Doing so may result in a duplication of events and/or other implementation-specific
+ * adverse effects.
+ *
+ * Subscription via wildcard symbol for {@link LastingEvent} types does not count for the purpose of
+ * {@link DXFeed#getLastEvent(LastingEvent) DXFeed.getLastEvent} and
+ * {@link DXFeed#getLastEvents(Collection) DXFeed.getLastEvents} methods. Lasting events that are received
+ * via wildcard subscription are not conflated in the usual way. They may incur significantly higher resource
+ * requirements to process them and may get queued when network and CPU resources are inadequate.
+ *
+ *
Future compatibility
+ *
+ * In the future this class may be extended with support for more specific wildcards in addition
+ * to {@link #ALL ALL}. Data provides that publish events via {@link DXPublisher}, track subscription via
+ * {@link DXPublisher#getSubscription(Class) DXPublisher.getSubscription} method, and plan to detect wildcard
+ * subscription in order to start publishing all possible events, should use
+ * {@code symbol instanceof WildcardSymbol} code
+ * to detect wildcard symbols to be future-proof.
+ */
+public class WildcardSymbol implements Serializable {
+ private static final long serialVersionUID = 0;
+
+ /**
+ * Symbol prefix that is reserved for wildcard subscriptions.
+ * Any subscription starting with "*" is ignored with the exception of {@link WildcardSymbol}
+ * subscription.
+ */
+ public static final String RESERVED_PREFIX = "*";
+
+ /**
+ * Represents [wildcard] subscription to all events of the specific event type.
+ *
+ * NOTE: Wildcard subscription can create extremely high network and CPU load for certain kinds of
+ * high-frequency events like quotes. It requires a special arrangement on the side of upstream data provider and
+ * is disabled by default in upstream feed configuration. Make that sure you have adequate resources and understand
+ * the impact before using it. It can be used for low-frequency events only (like Forex quotes), because each instance
+ * of {@link DXFeedSubscription} processes events in a single thread and there is no provision to load-balance wildcard
+ * subscription amongst multiple threads.
+ * Contact your data provider for the corresponding configuration arrangement if needed.
+ *
+ * @see WildcardSymbol
+ */
+ public static final WildcardSymbol ALL = new WildcardSymbol(RESERVED_PREFIX);
+
+ private final String symbol;
+
+ private WildcardSymbol(String symbol) {
+ this.symbol = symbol;
+ }
+
+ /**
+ * Returns "*".
+ */
+ @Override
+ public String toString() {
+ return symbol;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o || o instanceof WildcardSymbol && symbol.equals(((WildcardSymbol)o).symbol);
+ }
+
+ @Override
+ public int hashCode() {
+ return symbol.hashCode();
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/osub/package.html b/dxfeed-api/src/main/java/com/dxfeed/api/osub/package.html
new file mode 100644
index 000000000..db015d4b6
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/osub/package.html
@@ -0,0 +1,14 @@
+
+
+
+Provides API to
+observe subscription to generate and publish events only when needed.
+
+
diff --git a/dxfeed-api/src/main/java/com/dxfeed/api/package.html b/dxfeed-api/src/main/java/com/dxfeed/api/package.html
new file mode 100644
index 000000000..429f94fc8
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/api/package.html
@@ -0,0 +1,13 @@
+
+
+
+Main package for dxFeed API that provides DXFeed as its core class.
+
+
\ No newline at end of file
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/EventType.java b/dxfeed-api/src/main/java/com/dxfeed/event/EventType.java
new file mode 100644
index 000000000..35dd19987
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/EventType.java
@@ -0,0 +1,71 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event;
+
+import java.io.Serializable;
+
+import com.dxfeed.api.DXEndpoint;
+import com.dxfeed.api.DXFeedSubscription;
+
+/**
+ * Marks all event types that can be received via dxFeed API.
+ * Events are considered instantaneous, non-persistent, and unconflateable
+ * (each event is individually delivered) unless they implement one of interfaces
+ * defined in this package to further refine their meaning.
+ *
+ * Event types are POJOs (plain old java objects) that follow bean naming convention with
+ * getters and setters for their properties.
+ * All event types are serializable, because they are transferred over network from publishers to
+ * data feed consumers. However, they are using custom serialization format for this purpose.
+ *
+ * @param type of the event symbol for this event type.
+ * @see com.dxfeed.api.DXFeed
+ */
+public interface EventType extends Serializable {
+ /**
+ * Returns event symbol that identifies this event type in {@link DXFeedSubscription subscription}.
+ * @return event symbol.
+ */
+ public T getEventSymbol();
+
+ /**
+ * Changes event symbol that identifies this event type in {@link DXFeedSubscription subscription}.
+ * @param eventSymbol event symbol.
+ */
+ public void setEventSymbol(T eventSymbol);
+
+ /**
+ * Returns time when event was created or zero when time is not available.
+ *
+ * This event time is available only when the corresponding {@link DXEndpoint} is created
+ * with {@link DXEndpoint#DXENDPOINT_EVENT_TIME_PROPERTY DXENDPOINT_EVENT_TIME_PROPERTY} and
+ * the data source has embedded event times. This is typically true only for data events
+ * that are read from historical tape files and from {@link com.dxfeed.ondemand.OnDemandService OnDemandService}.
+ * Events that are coming from a network connections do not have an embedded event time information and
+ * this method will return zero for them, meaning that event was received just now.
+ *
+ * @implSpec
+ * Default implementation returns 0.
+ *
+ * @return the difference, measured in milliseconds,
+ * between the event creation time and midnight, January 1, 1970 UTC or zero when time is not available.
+ */
+ public default long getEventTime() { return 0; }
+
+ /**
+ * Changes event creation time.
+ *
+ * @implSpec
+ * Default implementation does nothing.
+ *
+ * @param eventTime the difference, measured in milliseconds,
+ * between the event creation time and midnight, January 1, 1970 UTC.
+ */
+ public default void setEventTime(long eventTime) {}
+}
\ No newline at end of file
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/IndexedEvent.java b/dxfeed-api/src/main/java/com/dxfeed/event/IndexedEvent.java
new file mode 100644
index 000000000..7c7adabf2
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/IndexedEvent.java
@@ -0,0 +1,199 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event;
+
+import com.dxfeed.event.candle.Candle;
+import com.dxfeed.event.market.Order;
+import com.dxfeed.event.option.Series;
+
+/**
+ * Represents an indexed collection of up-to-date information about some
+ * condition or state of an external entity that updates in real-time. For example,
+ * {@link Order} represents an order to buy or to sell some market instrument
+ * that is currently active on a market exchange and multiple
+ * orders are active for each symbol at any given moment in time.
+ * {@link Candle} represent snapshots of aggregate information
+ * about trading over a specific time period and there are multiple periods available.
+ * The {@link Candle} is also an example of {@link TimeSeriesEvent} that is a more specific event type.
+ *
+ *
Index for each event is available via {@link #getIndex() index} property.
+ * Indexed events retain information about multiple events per symbol based on the event index
+ * and are conflated based on the event index. Last indexed event for each symbol and index is always
+ * delivered to event listeners on subscription, but intermediate (next-to-last) events for each
+ * symbol+index pair are not queued anywhere, they are simply discarded as stale events.
+ * More recent events represent an up-to-date information about some external entity.
+ *
+ *
Event flags, transactions and snapshots
+ *
+ * Some indexed event sources provide a consistent view of a set of events for a given symbol. Their updates
+ * may incorporate multiple changes that have to be processed at the same time.
+ * The corresponding information is carried in {@link #getEventFlags() eventFlags} property.
+ * Some indexed events, like {@link Order}, support multiple sources of information for the
+ * same symbol. The event source is available via {@link #getSource() source} property.
+ * The value of {@link #getSource() source} property is always {@link IndexedEventSource#DEFAULT DEFAULT} for
+ * time-series events and other singe-sourced events like {@link Series}, that do not support this feature.
+ *
+ * The value of {@link #getEventFlags() eventFlags} property has several significant bits that are packed
+ * into an integer in the following way:
+ *
+ *
+ * 31..7 6 5 4 3 2 1 0
+ * +---------+----+----+----+----+----+----+----+
+ * | | SM | | SS | SE | SB | RE | TX |
+ * +---------+----+----+----+----+----+----+----+
+ *
+ *
+ * Each source updates its transactional state using these bits separately.
+ * The state of each source has to be tracked separately in a map for each source.
+ * However, event {@link #getIndex() index} is unique across the sources. This is achieved by allocating
+ * an event-specific number of most significant bits of {@link #getIndex() index} for use as
+ * a {@link #getSource() source} {@link IndexedEventSource#id() id}.
+ *
+ * {@code TX} (bit 0) — {@link #TX_PENDING} is an indicator of pending transactional update.
+ * It can be retrieved from {@code eventFlags} with the following piece of code:
+ *
+ *
boolean txPending = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.{@link #TX_PENDING TX_PENDING}) != 0;
+ *
+ *
When {@code txPending} is {@code true} it means, that an ongoing transaction update that spans multiple events is
+ * in process. All events with {@code txPending} {@code true} shall be put into a separate pending list
+ * for each source id and should be processed later when an event for this source id with {@code txPending}
+ * {@code false} comes.
+ *
+ *
{@code RE} (bit 1) — {@link #REMOVE_EVENT} is used to indicate that that the event with the
+ * corresponding index has to be removed.
+ *
+ *
boolean removeEvent = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.{@link #REMOVE_EVENT REMOVE_EVENT}) != 0;
+ *
+ *
{@code SB} (bit 2) — {@link #SNAPSHOT_BEGIN} is used to indicate when the loading of a snapshot starts.
+ *
+ *
boolean snapshotBegin = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.{@link #SNAPSHOT_BEGIN SNAPSHOT_BEGIN}) != 0;
+ *
+ *
Snapshot load starts on new subscription and the first indexed event that arrives for each non-zero source id
+ * on new subscription may have {@code snapshotBegin} set to {@code true}. It means, that an ongoing snapshot
+ * consisting of multiple events is incoming. All events for this source id shall be put into a separate
+ * pending list for each source id.
+ *
+ *
{@code SE} (bit 3) — {@link #SNAPSHOT_END} or {@code SS} (bit 4) — {@link #SNAPSHOT_SNIP}
+ * are used to indicate the end of a snapshot.
+ *
+ *
boolean snapshotEnd = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.{@link #SNAPSHOT_END SNAPSHOT_END}) != 0;
+ * boolean snapshotSnip = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.{@link #SNAPSHOT_SNIP SNAPSHOT_SNIP}) != 0;
+ *
+ *
The last event of a snapshot is marked with either {@code snapshotEnd} or {@code snapshotSnip}. At this time, all events
+ * from a pending list for the corresponding source can be processed, unless {@code txPending} is also
+ * set to {@code true}. In the later case, the processing shall be further delayed due to ongoing transaction.
+ *
+ *
The difference between {@code snapshotEnd} and {@code snapshotSnip} is the following.
+ * {@code snapshotEnd} indicates that the data source had sent all the data pertaining to the subscription
+ * for the corresponding indexed event, while {@code snapshotSnip} indicates that some limit on the amount
+ * of data was reached and while there still might be more data available, it will not be provided.
+ *
+ *
{@code SM} (bit 6) — {@link #SNAPSHOT_MODE} is used to instruct dxFeed to use snapshot mode.
+ * It is intended to be used only for publishing to activate (if not yet activated) snapshot mode.
+ * The difference from {@link #SNAPSHOT_BEGIN} flag is that {@link #SNAPSHOT_MODE} only switches on snapshot mode
+ * without starting snapshot synchronization protocol.
+ *
+ *
When a snapshot is empty or consists of a single event, then the event can have both {@code snapshotBegin}
+ * and {@code snapshotEnd} or {@code snapshotSnip} flags. In case of an empty snapshot, {@code removeEvent} on this event is
+ * also set to {@code true}.
+ *
+ * @param type of the event symbol for this event type.
+ */
+public interface IndexedEvent extends EventType {
+ /* ========== AHTUNG ==========
+ * The constants below must be synchronized with similar constants in QD NG EventFlag class!!!
+ */
+
+ /**
+ * Bit mask to get transaction pending indicator from the value of {@link #getEventFlags() eventFlags} property.
+ *
+ * boolean txPending = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.TX_PENDING) != 0;
+ *
+ *
See "Event Flags" section.
+ */
+ public static final int TX_PENDING = 0x01;
+
+ /**
+ * Bit mask to get removal indicator from the value of {@link #getEventFlags() eventFlags} property.
+ *
+ *
boolean removeEvent = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.REMOVE_EVENT) != 0;
+ *
+ *
See "Event Flags" section.
+ */
+ public static final int REMOVE_EVENT = 0x02;
+
+ /**
+ * Bit mask to get snapshot begin indicator from the value of {@link #getEventFlags() eventFlags} property.
+ *
+ *
boolean snapshotBegin = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.SNAPSHOT_BEGIN) != 0;
+ *
+ *
See "Event Flags" section.
+ */
+ public static final int SNAPSHOT_BEGIN = 0x04;
+
+ /**
+ * Bit mask to get snapshot end indicator from the value of {@link #getEventFlags() eventFlags} property.
+ *
+ *
boolean snapshotEnd = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.SNAPSHOT_END) != 0;
+ *
+ *
See "Event Flags" section.
+ */
+ public static final int SNAPSHOT_END = 0x08;
+
+ /**
+ * Bit mask to get snapshot snip indicator from the value of {@link #getEventFlags() eventFlags} property.
+ *
+ *
boolean snapshotSnip = (event.{@link #getEventFlags() getEventFlags}() & IndexedEvent.SNAPSHOT_SNIP) != 0;
+ *
+ *
See "Event Flags" section.
+ */
+ public static final int SNAPSHOT_SNIP = 0x10;
+
+ /**
+ * Bit mask to set snapshot mode indicator into the value of {@link #setEventFlags(int) eventFlags} property.
+ * This flag is intended for publishing only.
+ * See "Event Flags" section.
+ */
+ public static final int SNAPSHOT_MODE = 0x40;
+
+ /**
+ * Returns source of this event.
+ * @return source of this event.
+ */
+ public IndexedEventSource getSource();
+
+ /**
+ * Returns transactional event flags.
+ * See "Event Flags" section.
+ * @return transactional event flags.
+ */
+ public int getEventFlags();
+
+ /**
+ * Changes transactional event flags.
+ * See "Event Flags" section.
+ * @param eventFlags transactional event flags.
+ * @throws IllegalArgumentException if unsupported event flag is set in {@code eventFlags}.
+ */
+ public void setEventFlags(int eventFlags);
+
+ /**
+ * Returns unique per-symbol index of this event.
+ *
+ * @return unique per-symbol index of this event.
+ */
+ public long getIndex();
+
+ /**
+ * Changes unique per-symbol index of this event.
+ *
+ * @param index unique per-symbol index of this event.
+ */
+ public void setIndex(long index);
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/IndexedEventSource.java b/dxfeed-api/src/main/java/com/dxfeed/event/IndexedEventSource.java
new file mode 100644
index 000000000..0a7939c0c
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/IndexedEventSource.java
@@ -0,0 +1,74 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event;
+
+/**
+ * Source identifier for {@link IndexedEvent}.
+ * See {@link IndexedEvent#getSource() IndexedEvent.getSource}.
+ */
+public class IndexedEventSource {
+ /**
+ * The default source with zero {@link #id() identifier}
+ * for all events that do not support multiple sources.
+ */
+ public static final IndexedEventSource DEFAULT = new IndexedEventSource(0, "DEFAULT");
+
+ protected final int id;
+ protected final String name;
+
+
+ public IndexedEventSource(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ /**
+ * Returns a source identifier. Source identifier is non-negative.
+ * @return a source identifier.
+ */
+ public final int id() {
+ return id;
+ }
+
+ /**
+ * Returns a string representation of the object.
+ * @return a string representation of the object.
+ */
+ public final String name() {
+ return name;
+ }
+
+ /**
+ * Returns a string representation of the object.
+ * @return a string representation of the object.
+ */
+ @Override
+ public final String toString() {
+ return name;
+ }
+
+ /**
+ * Indicates whether some other indexed event source has the same id.
+ * @return {@code true} if this object is the same id as the obj argument; {@code false} otherwise.
+ */
+ @Override
+ public final boolean equals(Object o) {
+ return o == this || o instanceof IndexedEventSource && id() == ((IndexedEventSource)o).id();
+ }
+
+ /**
+ * Returns a hash code value for this object.
+ * The result of this method is equal to {@link #id() id}.
+ * @return a hash code value for this object.
+ */
+ @Override
+ public final int hashCode() {
+ return id();
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/LastingEvent.java b/dxfeed-api/src/main/java/com/dxfeed/event/LastingEvent.java
new file mode 100644
index 000000000..60747b698
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/LastingEvent.java
@@ -0,0 +1,40 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event;
+
+import java.util.Collection;
+
+import com.dxfeed.api.DXFeed;
+import com.dxfeed.api.osub.WildcardSymbol;
+import com.dxfeed.event.market.Quote;
+
+/**
+ * Represents up-to-date information about some
+ * condition or state of an external entity that updates in real-time. For example,
+ * a {@link Quote} is an up-to-date information about best bid and best offer for
+ * a specific symbol.
+ *
+ *
Lasting events are conflated for
+ * each symbol. Last event for each symbol is always delivered to event listeners on
+ * subscription, but intermediate (next-to-last) events are not queued anywhere,
+ * they are simply discarded as stale events. More recent events
+ * represent an up-to-date information about some external entity.
+ *
+ *
Lasting events can be used with {@link DXFeed#getLastEvent(LastingEvent) DXFeed.getLastEvent}
+ * and {@link DXFeed#getLastEvents(Collection) DXFeed.getLastEvents}
+ * methods to retrieve last events for each symbol.
+ *
+ *
Note, that subscription to all lasting events of a specific type via
+ * {@link WildcardSymbol#ALL WildcardSymbol.ALL} symbol object does not benefit from the above
+ * advantages of lasting events.
+ *
+ * @param type of the event symbol for this event type.
+ */
+public interface LastingEvent extends EventType {
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/TimeSeriesEvent.java b/dxfeed-api/src/main/java/com/dxfeed/event/TimeSeriesEvent.java
new file mode 100644
index 000000000..4acf4a761
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/TimeSeriesEvent.java
@@ -0,0 +1,145 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event;
+
+import java.util.Collection;
+import javax.xml.bind.annotation.XmlTransient;
+
+import com.dxfeed.api.*;
+import com.dxfeed.api.osub.TimeSeriesSubscriptionSymbol;
+import com.dxfeed.event.candle.Candle;
+import com.dxfeed.event.market.TimeAndSale;
+import com.dxfeed.model.AbstractIndexedEventModel;
+import com.dxfeed.model.TimeSeriesEventModel;
+
+/**
+ * Represents time-series snapshots of some
+ * process that is evolving in time or actual events in some external system
+ * that have an associated time stamp and can be uniquely identified.
+ * For example, {@link TimeAndSale} events represent the actual sales
+ * that happen on a market exchange at specific time moments, while
+ * {@link Candle} events represent snapshots of aggregate information
+ * about trading over a specific time period.
+ *
+ * Time-series events can be used with {@link DXFeedTimeSeriesSubscription}
+ * to receive a time-series history of past events. Time-series events
+ * are conflated based on unique {@link #getIndex() index} for each symbol.
+ * Last indexed event for each symbol and index is always
+ * delivered to event listeners on subscription, but intermediate (next-to-last) events for each
+ * symbol+index pair are not queued anywhere, they are simply discarded as stale events.
+ *
+ *
Timestamp of an event is available via {@link #getTime() time} property
+ * with a millisecond precision. Some events may happen multiple times per millisecond.
+ * In this case they have the same {@link #getTime() time}, but different
+ * {@link #getIndex() index}. An ordering of {@link #getIndex() index} is the same
+ * as an ordering of {@link #getTime() time}. That is, an collection of time-series
+ * events that is ordered by {@link #getIndex() index} is ascending order will be
+ * also ordered by {@link #getTime() time} in ascending order.
+ *
+ *
Time-series events are a more specific subtype of {@link IndexedEvent}.
+ * All general documentation and Event Flags section, in particular,
+ * applies to time-series events. However, the time-series events never come from multiple sources for the
+ * same symbol and their {@link #getSource() source} is always {@link IndexedEventSource#DEFAULT DEFAULT}.
+ *
+ *
Unlike a general {@link IndexedEvent} that is subscribed to via {@link DXFeedSubscription} using a plain symbol
+ * to receive all events for all indices, time-series events are typically subscribed to using
+ * {@link TimeSeriesSubscriptionSymbol} class to specific time range of the subscription. There is a dedicated
+ * {@link DXFeedTimeSeriesSubscription} class that is designed to simplify the task of subscribing for
+ * time-series events.
+ *
+ *
{@link TimeSeriesEventModel} class handles all the snapshot and transaction logic and conveniently represents
+ * a list of current time-series events ordered by their {@link #getTime() time}.
+ * It relies on the code of {@link AbstractIndexedEventModel} to handle this logic.
+ * Use the source code of {@link AbstractIndexedEventModel} for clarification on transactions and snapshot logic.
+ *
+ *
Classes that implement this interface may also implement
+ * {@link LastingEvent} interface, which makes it possible to
+ * use {@link DXFeed#getLastEvent(LastingEvent) DXFeed.getLastEvent} method to
+ * retrieve the last event for the corresponding symbol.
+ *
+ *
Publishing time-series
+ *
+ * When publishing time-series event with {@link DXPublisher#publishEvents(Collection) DXPublisher.publishEvents}
+ * method on incoming {@link TimeSeriesSubscriptionSymbol} the snapshot of currently known events for the
+ * requested time range has to be published first.
+ *
+ * A snapshot is published in the descending order of {@link #getIndex() index}
+ * (which is the same as the descending order of {@link #getTime() time}), starting with
+ * an event with the largest index and marking it with {@link #SNAPSHOT_BEGIN} bit in {@link #getEventFlags() eventFlags}.
+ * All other event follow with default, zero {@link #getEventFlags() eventFlags}.
+ * If there is no actual event at the time that was specified in subscription
+ * {@link TimeSeriesSubscriptionSymbol#getFromTime() fromTime} property, then event with the corresponding time
+ * has to be created anyway and published. To distinguish it from the actual event, it has to be marked
+ * with {@link #REMOVE_EVENT} bit in {@link #getEventFlags() eventFlags}.
+ * {@link #SNAPSHOT_END} bit in this event's {@link #getEventFlags() eventFlags}
+ * is optional during publishing. It will be properly set on receiving end anyway. Note, that publishing
+ * any event with time that is below subscription {@link TimeSeriesSubscriptionSymbol#getFromTime() fromTime}
+ * also works as a legal indicator for the end of the snapshot.
+ *
+ *
Both {@link TimeAndSale} and {@link Candle} time-series events define a sequence property that is mixed
+ * into an {@link #getIndex() index} property. It provides a way to distinguish different events at the same time.
+ * For a snapshot end event the sequence has to be left at its default zero value. It means, that if there is
+ * an actual event to be published at subscription {@link TimeSeriesSubscriptionSymbol#getFromTime() fromTime}
+ * with non-zero sequence, then generation of an additional snapshot end event with the subscription
+ * {@link TimeSeriesSubscriptionSymbol#getFromTime() fromTime} and zero sequence is still required.
+ *
+ * @param type of the event symbol for this event type.
+ */
+public interface TimeSeriesEvent extends IndexedEvent {
+ /**
+ * Returns a source identifier for this event, which is always {@link IndexedEventSource#DEFAULT DEFAULT} for time-series events.
+ * @return a source identifier for this event, which is always {@link IndexedEventSource#DEFAULT DEFAULT} for time-series events.
+ */
+ @Override
+ public IndexedEventSource getSource();
+
+ /**
+ * Returns unique per-symbol index of this event.
+ * Event indices are unique within event symbol.
+ * Typically event index for a time series event includes
+ * {@link #getTime() time} inside.
+ *
+ * Implementation notes
+ *
+ * The most common scheme for event indices is to set highest 32 bits of event index
+ * to event timestamp in seconds. The lowest 32 bits are then split as follows.
+ * Bits 22 to 31 encode milliseconds of time stamp, and bits 0 to 21 encode some
+ * kind of a sequence number.
+ *
+ * Ultimately, the scheme for event indices is specific for each even type.
+ * The actual classes for specific event types perform the corresponding encoding.
+ *
+ * @return unique per-symbol index of this event.
+ */
+ @Override
+ public long getIndex();
+
+ /**
+ * Returns unique per-symbol index of this event.
+ *
+ * @return unique per-symbol index of this event.
+ * @deprecated Use {@link #getIndex()}
+ */
+ @Deprecated
+ @XmlTransient
+ public default long getEventId() {
+ return getIndex();
+ }
+
+ /**
+ * Returns timestamp of the event.
+ * The timestamp is in milliseconds from midnight, January 1, 1970 UTC.
+ *
+ * @return timestamp of the event.
+ *
+ * @see System#currentTimeMillis()
+ */
+ public long getTime();
+
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/candle/Candle.java b/dxfeed-api/src/main/java/com/dxfeed/event/candle/Candle.java
new file mode 100644
index 000000000..c05a93984
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/candle/Candle.java
@@ -0,0 +1,441 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event.candle;
+
+import javax.xml.bind.annotation.*;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import com.devexperts.util.TimeFormat;
+import com.devexperts.util.TimeUtil;
+import com.dxfeed.api.DXFeedTimeSeriesSubscription;
+import com.dxfeed.api.osub.TimeSeriesSubscriptionSymbol;
+import com.dxfeed.event.*;
+import com.dxfeed.impl.XmlCandleSymbolAdapter;
+import com.dxfeed.impl.XmlTimeAdapter;
+import com.dxfeed.model.AbstractIndexedEventModel;
+import com.dxfeed.model.TimeSeriesEventModel;
+
+/**
+ * Candle event with open, high, low, close prices and other information for a specific period.
+ * Candles are build with a specified {@link CandlePeriod} using a specified {@link CandlePrice} type
+ * with a data taken from the specified {@link CandleExchange} from the specified {@link CandleSession}
+ * with further details of aggregation provided by {@link CandleAlignment}.
+ *
+ *
Event symbol of the candle is represented with {@link CandleSymbol} class.
+ * Since the {@code Candle} is a time-series event, it is typically subscribed to using
+ * {@link DXFeedTimeSeriesSubscription} class that handles the necessarily wrapping
+ * of the symbol into {@link TimeSeriesSubscriptionSymbol} to specify a subscription
+ * time range.
+ *
+ *
Properties
+ *
+ * {@code Candle} event has the following properties:
+ *
+ *
+ * {@link #getEventSymbol() eventSymbol} - candle event symbol;
+ * {@link #getEventFlags() eventFlags} - transactional event flags;
+ * {@link #getIndex() index} - unique per-symbol index of this candle;
+ * {@link #getTime() time} - timestamp of this candle in milliseconds;
+ * {@link #getSequence() sequence} - sequence number of this candle; distinguishes candles with same {@link #getTime() time};
+ * {@link #getCount() count} - total number of original trade (or quote) events in this candle;
+ * {@link #getOpen() open} - the first (open) price of this candle;
+ * {@link #getHigh() high} - the maximal (high) price of this candle;
+ * {@link #getLow() low} - the minimal (low) price of this candle;
+ * {@link #getClose() close} - the last (close) price of this candle;
+ * {@link #getVolume() volume} - total volume in this candle;
+ * {@link #getVWAP() vwap} - volume-weighted average price (VWAP) in this candle;
+ * {@link #getBidVolume() bidVolume} - bid volume in this candle;
+ * {@link #getAskVolume() askVolume} - bid volume in this candle.
+ *
+ *
+ *
+ *
+ * Some candle sources provide a consistent view of the set of known candles.
+ * The corresponding information is carried in {@link #getEventFlags() eventFlags} property.
+ * The logic behind this property is detailed in {@link IndexedEvent} class documentation.
+ * Multiple event sources for the same symbol are not supported for candles, thus
+ * {@link #getSource() source} property is always {@link IndexedEventSource#DEFAULT DEFAULT}.
+ *
+ * {@link TimeSeriesEventModel} class handles all the snapshot and transaction logic and conveniently represents
+ * a list current of time-series events order by their {@link #getTime() time}.
+ * It relies on the code of {@link AbstractIndexedEventModel} to handle this logic.
+ * Use the source code of {@link AbstractIndexedEventModel} for clarification on transactions and snapshot logic.
+ *
+ *
Publishing Candles
+ *
+ * Publishing of candle events follows the general rules explained in {@link TimeSeriesEvent} class
+ * documentation.
+ *
+ * Implementation details
+ *
+ * This event is implemented on top of QDS record {@code TradeHistory} for tick candles
+ * with {@link CandlePeriod#TICK CandlePeriod.TICK}, records {@code Trade.} for
+ * a certain set of popular periods, and QDS record {@code Candle} for arbitrary custom
+ * periods, with a set of {@code Candle{}} records for a popular combinations of custom
+ * candle symbol attributes like {@link CandlePrice} for an efficient support of bid-ask charting.
+ */
+@XmlRootElement(name = "Candle")
+public class Candle implements TimeSeriesEvent, LastingEvent {
+ private static final long serialVersionUID = 2;
+
+ // ========================= public static =========================
+
+ /**
+ * Maximum allowed sequence value.
+ * @see #setSequence(int)
+ */
+ public static final int MAX_SEQUENCE = (1 << 22) - 1;
+
+ // ========================= instance =========================
+
+ /*
+ * EventFlags property has several significant bits that are packed into an integer in the following way:
+ * 31..7 6 5 4 3 2 1 0
+ * +---------+----+----+----+----+----+----+----+
+ * | | SM | | SS | SE | SB | RE | TX |
+ * +---------+----+----+----+----+----+----+----+
+ */
+
+ private CandleSymbol eventSymbol;
+ private int eventFlags;
+ private long eventTime;
+
+ private long index;
+ private long count;
+ private double open = Double.NaN;
+ private double high = Double.NaN;
+ private double low = Double.NaN;
+ private double close = Double.NaN;
+ private long volume;
+ private double vwap = Double.NaN;
+ private long bidVolume;
+ private long askVolume;
+
+ /**
+ * Creates new candle with default values.
+ */
+ public Candle() {}
+
+ /**
+ * Creates new candle with the specified candle event symbol.
+ * @param eventSymbol candle event symbol.
+ */
+ public Candle(CandleSymbol eventSymbol) {
+ this.eventSymbol = eventSymbol;
+ }
+
+ /**
+ * Returns candle event symbol.
+ * @return candle event symbol.
+ */
+ @Override
+ @XmlJavaTypeAdapter(type=CandleSymbol.class, value=XmlCandleSymbolAdapter.class)
+ @XmlSchemaType(name="string")
+ public CandleSymbol getEventSymbol() {
+ return eventSymbol;
+ }
+
+ /**
+ * Changes candle event symbol.
+ * @param eventSymbol candle event symbol.
+ */
+ @Override
+ public void setEventSymbol(CandleSymbol eventSymbol) {
+ this.eventSymbol = eventSymbol;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getEventTime() {
+ return eventTime;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setEventTime(long eventTime) {
+ this.eventTime = eventTime;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @XmlTransient
+ public IndexedEventSource getSource() {
+ return IndexedEventSource.DEFAULT;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getEventFlags() {
+ return eventFlags;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setEventFlags(int eventFlags) {
+ this.eventFlags = eventFlags;
+ }
+
+ /**
+ * Returns unique per-symbol index of this candle event.
+ * Candle index is composed of {@link #getTime() time} and {@link #getSequence() sequence}.
+ * Changing either time or sequence changes event index.
+ * @return unique index of this candle event.
+ */
+ @Override
+ public long getIndex() {
+ return index;
+ }
+
+ /**
+ * Changes unique per-symbol index of this candle event.
+ * Candle index is composed of {@link #getTime() time} and {@link #getSequence() sequence} and
+ * invocation of this method changes time and sequence.
+ * Do not use this method directly.
+ * Change {@link #setTime(long) time} and/or {@link #setSequence(int) sequence}.
+ *
+ * @param index the event index.
+ * @see #getIndex()
+ */
+ @Override
+ public void setIndex(long index) {
+ this.index = index;
+ }
+
+ /**
+ * Changes unique per-symbol index of this candle event.
+ * @param index the event index.
+ * @deprecated Use {@link #setIndex(long)}
+ */
+ public void setEventId(long index) {
+ setIndex(index);
+ }
+
+ /**
+ * Returns timestamp of the candle in milliseconds.
+ * @return timestamp of the candle in milliseconds
+ */
+ @Override
+ @XmlJavaTypeAdapter(type=long.class, value=XmlTimeAdapter.class)
+ @XmlSchemaType(name="dateTime")
+ public long getTime() {
+ return (index >> 32) * 1000 + ((index >> 22) & 0x3ff);
+ }
+
+ /**
+ * Changes timestamp of the candle in milliseconds.
+ * @param time timestamp of the candle in milliseconds.
+ * @see #getTime()
+ */
+ public void setTime(long time) {
+ index = ((long)TimeUtil.getSecondsFromTime(time) << 32) | ((long)TimeUtil.getMillisFromTime(time) << 22) | getSequence();
+ }
+
+ /**
+ * Returns sequence number of this event to distinguish events that have the same
+ * {@link #getTime() time}. This sequence number does not have to be unique and
+ * does not need to be sequential. Sequence can range from 0 to {@link #MAX_SEQUENCE}.
+ */
+ public int getSequence() {
+ return (int)index & MAX_SEQUENCE;
+ }
+
+ /**
+ * Changes {@link #getSequence()} sequence number} of this event.
+ * @param sequence the sequence.
+ * @throws IllegalArgumentException if sequence is below zero or above {@link #MAX_SEQUENCE}.
+ * @see #getSequence()
+ */
+ public void setSequence(int sequence) {
+ if (sequence < 0 || sequence > MAX_SEQUENCE)
+ throw new IllegalArgumentException();
+ index = (index & ~MAX_SEQUENCE) | sequence;
+ }
+
+ /**
+ * Returns total number of original trade (or quote) events in this candle.
+ * @return total number of original trade (or quote) events in this candle.
+ */
+ public long getCount() {
+ return count;
+ }
+
+ /**
+ * Changes total number of original trade (or quote) events in this candle.
+ * @param count total number of original trade (or quote) events in this candle.
+ */
+ public void setCount(long count) {
+ this.count = count;
+ }
+
+ /**
+ * Returns the first (open) price of this candle.
+ * @return the first (open) price of this candle.
+ */
+ public double getOpen() {
+ return open;
+ }
+
+ /**
+ * Changes the first (open) price of this candle.
+ * @param open the first (open) price of this candle.
+ */
+ public void setOpen(double open) {
+ this.open = open;
+ }
+
+ /**
+ * Returns the maximal (high) price of this candle.
+ * @return the maximal (high) price of this candle.
+ */
+ public double getHigh() {
+ return high;
+ }
+
+ /**
+ * Changes the maximal (high) price of this candle.
+ * @param high the maximal (high) price of this candle.
+ */
+ public void setHigh(double high) {
+ this.high = high;
+ }
+
+ /**
+ * Returns the minimal (low) price of this candle.
+ * @return the minimal (low) price of this candle.
+ */
+ public double getLow() {
+ return low;
+ }
+
+ /**
+ * Changes the minimal (low) price of this candle.
+ * @param low the minimal (low) price of this candle.
+ */
+ public void setLow(double low) {
+ this.low = low;
+ }
+
+ /**
+ * Returns the last (close) price of this candle.
+ * @return the last (close) price of this candle.
+ */
+ public double getClose() {
+ return close;
+ }
+
+ /**
+ * Changes the last (close) price of this candle.
+ * @param close the last (close) price of this candle.
+ */
+ public void setClose(double close) {
+ this.close = close;
+ }
+
+ /**
+ * Returns total volume in this candle.
+ * @return total volume in this candle.
+ */
+ public long getVolume() {
+ return volume;
+ }
+
+ /**
+ * Changes total volume in this candle.
+ * @param volume total volume in this candle.
+ */
+ public void setVolume(long volume) {
+ this.volume = volume;
+ }
+
+ /**
+ * Returns volume-weighted average price (VWAP) in this candle.
+ * Total turnover in this candle can be computed with getVWAP() * {@link #getVolume() getVolume}()
.
+ * @return volume-weighted average price (VWAP) in this candle.
+ */
+ @XmlElement(name = "vwap") // all lower-case
+ public double getVWAP() {
+ return vwap;
+ }
+
+ /**
+ * Changes volume-weighted average price (VWAP) in this candle.
+ * @param vwap volume-weighted average price (VWAP) in this candle.
+ */
+ public void setVWAP(double vwap) {
+ this.vwap = vwap;
+ }
+
+ /**
+ * Returns bid volume in this candle.
+ * @return bid volume in this candle.
+ */
+ public long getBidVolume() {
+ return bidVolume;
+ }
+
+ /**
+ * Changes bid volume in this candle.
+ * @param bidVolume bid volume in this candle.
+ */
+ public void setBidVolume(long bidVolume) {
+ this.bidVolume = bidVolume;
+ }
+
+ /**
+ * Returns ask volume in this candle.
+ * @return ask volume in this candle.
+ */
+ public long getAskVolume() {
+ return askVolume;
+ }
+
+ /**
+ * Changes ask volume in this candle.
+ * @param askVolume ask volume in this candle.
+ */
+ public void setAskVolume(long askVolume) {
+ this.askVolume = askVolume;
+ }
+
+ /**
+ * Returns string representation of this candle.
+ * @return string representation of this candle.
+ */
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "{" + baseFieldsToString() + "}";
+ }
+
+ String baseFieldsToString() {
+ return eventSymbol +
+ ", eventTime=" + TimeFormat.DEFAULT.withMillis().format(getEventTime()) +
+ ", eventFlags=0x" + Integer.toHexString(getEventFlags()) +
+ ", time=" + TimeFormat.DEFAULT.withMillis().format(getTime()) +
+ ", sequence=" + getSequence() +
+ ", count=" + count +
+ ", open=" + open +
+ ", high=" + high +
+ ", low=" + low +
+ ", close=" + close +
+ ", volume=" + volume +
+ ", vwap=" + vwap +
+ ", bidVolume=" + bidVolume +
+ ", askVolume=" + askVolume;
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandleAlignment.java b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandleAlignment.java
new file mode 100644
index 000000000..4363e92c4
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandleAlignment.java
@@ -0,0 +1,153 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event.candle;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.dxfeed.event.market.MarketEventSymbols;
+
+/**
+ * Candle alignment attribute of {@link CandleSymbol} defines how candle are aligned with respect to time.
+ *
+ * Implementation details
+ *
+ * This attribute is encoded in a symbol string with
+ * {@link MarketEventSymbols#getAttributeStringByKey(String, String) MarketEventSymbols.getAttributeStringByKey},
+ * {@link MarketEventSymbols#changeAttributeStringByKey(String, String, String) changeAttributeStringByKey}, and
+ * {@link MarketEventSymbols#removeAttributeStringByKey(String, String) removeAttributeStringByKey} methods.
+ * The key to use with these methods is available via
+ * {@link #ATTRIBUTE_KEY} constant.
+ * The value that this key shall be set to is equal to
+ * the corresponding {@link #toString() CandleAlignment.toString()}
+ */
+public enum CandleAlignment implements CandleSymbolAttribute {
+ /**
+ * Align candles on midnight.
+ */
+ MIDNIGHT("m"),
+
+ /**
+ * Align candles on trading sessions.
+ */
+ SESSION("s");
+
+ /**
+ * Default alignment is {@link #MIDNIGHT}.
+ */
+ public static final CandleAlignment DEFAULT = MIDNIGHT;
+
+ /**
+ * The attribute key that is used to store the value of {@code CandleAlignment} in
+ * a symbol string using methods of {@link MarketEventSymbols} class.
+ * The value of this constant is "a".
+ * The value that this key shall be set to is equal to
+ * the corresponding {@link #toString() CandleAlignment.toString()}
+ */
+ public static final String ATTRIBUTE_KEY = "a";
+
+ private final String string;
+
+ CandleAlignment(String string) {
+ this.string = string;
+ }
+
+ /**
+ * Returns candle event symbol string with this candle alignment set.
+ * @param symbol original candle event symbol.
+ * @return candle event symbol string with this candle alignment set.
+ */
+ public String changeAttributeForSymbol(String symbol) {
+ return this == DEFAULT ?
+ MarketEventSymbols.removeAttributeStringByKey(symbol, ATTRIBUTE_KEY) :
+ MarketEventSymbols.changeAttributeStringByKey(symbol, ATTRIBUTE_KEY, toString());
+ }
+
+ /**
+ * Internal method that initializes attribute in the candle symbol.
+ * @param candleSymbol candle symbol.
+ * @throws IllegalStateException if used outside of internal initialization logic.
+ */
+ public void checkInAttributeImpl(CandleSymbol candleSymbol) {
+ if (candleSymbol.alignment != null)
+ throw new IllegalStateException("Already initialized");
+ candleSymbol.alignment = this;
+ }
+
+ /**
+ * Returns string representation of this candle alignment.
+ * The string representation of candle alignment "m" for {@link #MIDNIGHT}
+ * and "s" for {@link #SESSION}.
+ * @return string representation of this candle alignment.
+ */
+ @Override
+ public String toString() {
+ return string;
+ }
+
+ private static final Map BY_STRING = new HashMap();
+
+ static {
+ for (CandleAlignment align : values())
+ BY_STRING.put(align.toString(), align);
+ }
+
+ /**
+ * Parses string representation of candle alignment into object.
+ * Any string that was returned by {@link #toString()} can be parsed
+ * and case is ignored for parsing.
+ * @param s string representation of candle alignment.
+ * @return candle alignment.
+ * @throws IllegalArgumentException if the string representation is invalid.
+ */
+ public static CandleAlignment parse(String s) {
+ CandleAlignment result = BY_STRING.get(s);
+ // fast path to reverse toString result
+ if (result != null)
+ return result;
+ // slow path for different case
+ for (CandleAlignment align : values()) {
+ if (align.toString().equalsIgnoreCase(s))
+ return align;
+ }
+ throw new IllegalArgumentException("Unknown candle alignment: " + s);
+ }
+
+ /**
+ * Returns candle alignment of the given candle symbol string.
+ * The result is {@link #DEFAULT} if the symbol does not have candle alignment attribute.
+ * @param symbol candle symbol string.
+ * @return candle alignment of the given candle symbol string.
+ */
+ public static CandleAlignment getAttributeForSymbol(String symbol) {
+ String string = MarketEventSymbols.getAttributeStringByKey(symbol, ATTRIBUTE_KEY);
+ return string == null ? DEFAULT : parse(string);
+ }
+
+ /**
+ * Returns candle symbol string with the normalized representation of the candle alignment attribute.
+ * @param symbol candle symbol string.
+ * @return candle symbol string with the normalized representation of the the candle alignment attribute.
+ */
+ public static String normalizeAttributeForSymbol(String symbol) {
+ String a = MarketEventSymbols.getAttributeStringByKey(symbol, ATTRIBUTE_KEY);
+ if (a == null)
+ return symbol;
+ try {
+ CandleAlignment other = parse(a);
+ if (other == DEFAULT)
+ MarketEventSymbols.removeAttributeStringByKey(symbol, ATTRIBUTE_KEY);
+ if (!a.equals(other.toString()))
+ return MarketEventSymbols.changeAttributeStringByKey(symbol, ATTRIBUTE_KEY, other.toString());
+ return symbol;
+ } catch (IllegalArgumentException e) {
+ return symbol;
+ }
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandleExchange.java b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandleExchange.java
new file mode 100644
index 000000000..cb098d337
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandleExchange.java
@@ -0,0 +1,116 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event.candle;
+
+import com.dxfeed.event.market.MarketEventSymbols;
+
+/**
+ * Exchange attribute of {@link CandleSymbol} defines exchange identifier where data is
+ * taken from to build the candles.
+ *
+ * Implementation details
+ *
+ * This attribute is encoded in a symbol string with
+ * {@link MarketEventSymbols#getExchangeCode(String) MarketEventSymbols.getExchangeCode} and
+ * {@link MarketEventSymbols#changeExchangeCode(String, char) changeExchangeCode} methods.
+ */
+public class CandleExchange implements CandleSymbolAttribute {
+ /**
+ * Composite exchange where data is taken from all exchanges.
+ */
+ public static final CandleExchange COMPOSITE = new CandleExchange('\0');
+
+ /**
+ * Default exchange is {@link #COMPOSITE}.
+ */
+ public static final CandleExchange DEFAULT = COMPOSITE;
+
+ private final char exchangeCode;
+
+ private CandleExchange(char exchangeCode) {
+ this.exchangeCode = exchangeCode;
+ }
+
+ /**
+ * Returns exchange code. It is {@code '\0'} for {@link #COMPOSITE} exchange.
+ * @return exchange code.
+ */
+ public char getExchangeCode() {
+ return exchangeCode;
+ }
+
+
+ /**
+ * Returns string representation of this exchange.
+ * It is the string {@code "COMPOSITE"} for {@link #COMPOSITE} exchange or
+ * exchange character otherwise.
+ * @return string representation of this exchange.
+ */
+ @Override
+ public String toString() {
+ return exchangeCode == '\0' ? "COMPOSITE" : "" + exchangeCode;
+ }
+
+ /**
+ * Indicates whether this exchange attribute is the same as another one.
+ * @return {@code true} if this exchange attribute is the same as another one.
+ */
+ @Override
+ public boolean equals(Object o) {
+ return this == o || o instanceof CandleExchange && exchangeCode == ((CandleExchange)o).exchangeCode;
+ }
+
+ /**
+ * Returns hash code of this exchange attribute.
+ * @return hash code of this exchange attribute.
+ */
+ @Override
+ public int hashCode() {
+ return (int)exchangeCode;
+ }
+
+ /**
+ * Returns candle event symbol string with this exchange set.
+ * @param symbol original candle event symbol.
+ * @return candle event symbol string with this exchange set.
+ */
+ public String changeAttributeForSymbol(String symbol) {
+ return MarketEventSymbols.changeExchangeCode(symbol, exchangeCode);
+ }
+
+ /**
+ * Internal method that initializes attribute in the candle symbol.
+ * @param candleSymbol candle symbol.
+ * @throws IllegalStateException if used outside of internal initialization logic.
+ */
+ public void checkInAttributeImpl(CandleSymbol candleSymbol) {
+ if (candleSymbol.exchange != null)
+ throw new IllegalStateException("Already initialized");
+ candleSymbol.exchange = this;
+ }
+
+ /**
+ * Returns exchange attribute object that corresponds to the specified exchange code character.
+ * @param exchangeCode exchange code character.
+ * @return exchange attribute object.
+ */
+ public static CandleExchange valueOf(char exchangeCode) {
+ return exchangeCode == '\0' ? COMPOSITE : new CandleExchange(exchangeCode);
+ }
+
+ /**
+ * Returns exchange attribute object of the given candle symbol string.
+ * The result is {@link #DEFAULT} if the symbol does not have exchange attribute.
+ * @param symbol candle symbol string.
+ * @return exchange attribute object of the given candle symbol string.
+ */
+ public static CandleExchange getAttributeForSymbol(String symbol) {
+ return valueOf(MarketEventSymbols.getExchangeCode(symbol));
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandlePeriod.java b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandlePeriod.java
new file mode 100644
index 000000000..4439d70a5
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandlePeriod.java
@@ -0,0 +1,239 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event.candle;
+
+import com.dxfeed.event.market.MarketEventSymbols;
+
+/**
+ * Period attribute of {@link CandleSymbol} defines aggregation period of the candles.
+ * Aggregation period is defined as pair of a {@link #getValue()} and {@link #getType() type}.
+ *
+ * Implementation details
+ *
+ * This attribute is encoded in a symbol string with
+ * {@link MarketEventSymbols#getAttributeStringByKey(String, String) MarketEventSymbols.getAttributeStringByKey},
+ * {@link MarketEventSymbols#changeAttributeStringByKey(String, String, String) changeAttributeStringByKey}, and
+ * {@link MarketEventSymbols#removeAttributeStringByKey(String, String) removeAttributeStringByKey} methods.
+ * The key to use with these methods is available via
+ * {@link #ATTRIBUTE_KEY} constant.
+ * The value that this key shall be set to is equal to
+ * the corresponding {@link #toString() CandlePeriod.toString()}
+ */
+public class CandlePeriod implements CandleSymbolAttribute {
+
+ /**
+ * Tick aggregation where each candle represents an individual tick.
+ */
+ public static final CandlePeriod TICK = new CandlePeriod(1, CandleType.TICK);
+
+ /**
+ * Day aggregation where each candle represents a day.
+ */
+ public static final CandlePeriod DAY = new CandlePeriod(1, CandleType.DAY);
+
+ /**
+ * Default period is {@link #TICK}.
+ */
+ public static final CandlePeriod DEFAULT = TICK;
+
+ /**
+ * The attribute key that is used to store the value of {@code CandlePeriod} in
+ * a symbol string using methods of {@link MarketEventSymbols} class.
+ * The value of this constant is an empty string, because this is the
+ * main attribute that every {@link CandleSymbol} must have.
+ * The value that this key shall be set to is equal to
+ * the corresponding {@link #toString() CandlePeriod.toString()}
+ */
+ public static final String ATTRIBUTE_KEY = ""; // empty string as attribute key is allowed!
+
+ private final double value;
+ private final CandleType type;
+
+ private String string;
+
+ private CandlePeriod(double value, CandleType type) {
+ this.value = value;
+ this.type = type;
+ }
+
+ /**
+ * Returns aggregation period in milliseconds as closely as possible.
+ * Certain aggregation types like {@link CandleType#SECOND SECOND} and
+ * {@link CandleType#DAY DAY} span a specific number of milliseconds.
+ * {@link CandleType#MONTH}, {@link CandleType#OPTEXP} and {@link CandleType#YEAR}
+ * are approximate. Candle period of
+ * {@link CandleType#TICK}, {@link CandleType#VOLUME}, {@link CandleType#PRICE},
+ * {@link CandleType#PRICE_MOMENTUM} and {@link CandleType#PRICE_RENKO}
+ * is not defined and this method returns {@code 0}.
+ * The result of this method is equal to
+ * {@code (long)(this.getType().getPeriodIntervalMillis() * this.getValue())}
+ * @see CandleType#getPeriodIntervalMillis()
+ * @return aggregation period in milliseconds.
+ */
+ public long getPeriodIntervalMillis() {
+ return (long)(type.getPeriodIntervalMillis() * value);
+ }
+
+ /**
+ * Returns candle event symbol string with this aggregation period set.
+ * @param symbol original candle event symbol.
+ * @return candle event symbol string with this aggregation period set.
+ */
+ public String changeAttributeForSymbol(String symbol) {
+ return this == DEFAULT ?
+ MarketEventSymbols.removeAttributeStringByKey(symbol, ATTRIBUTE_KEY) :
+ MarketEventSymbols.changeAttributeStringByKey(symbol, ATTRIBUTE_KEY, toString());
+ }
+
+ /**
+ * Internal method that initializes attribute in the candle symbol.
+ * @param candleSymbol candle symbol.
+ * @throws IllegalStateException if used outside of internal initialization logic.
+ */
+ public void checkInAttributeImpl(CandleSymbol candleSymbol) {
+ if (candleSymbol.period != null)
+ throw new IllegalStateException("Already initialized");
+ candleSymbol.period = this;
+ }
+
+ /**
+ * Returns aggregation period value. For example, the value of {@code 5} with
+ * the candle type of {@link CandleType#MINUTE MINUTE} represents 5 minute
+ * aggregation period.
+ *
+ * @return aggregation period value.
+ */
+ public double getValue() {
+ return value;
+ }
+
+ /**
+ * Returns aggregation period type.
+ * @return aggregation period type.
+ */
+ public CandleType getType() {
+ return type;
+ }
+
+ /**
+ * Indicates whether this aggregation period is the same as another one.
+ * The same aggregation period has the same {@link #getValue() value} and
+ * {@link #getType() type}.
+ * @return {@code true} if this aggregation period is the same as another one.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (!(o instanceof CandlePeriod))
+ return false;
+ CandlePeriod that = (CandlePeriod)o;
+ return Double.compare(value, that.value) == 0 && type == that.type;
+ }
+
+ /**
+ * Returns hash code of this aggregation period.
+ * @return hash code of this aggregation period.
+ */
+ @Override
+ public int hashCode() {
+ long temp = value != +0.0d ? Double.doubleToLongBits(value) : 0L;
+ return 31 * (int)(temp ^ (temp >>> 32)) + type.hashCode();
+ }
+
+ /**
+ * Returns string representation of this aggregation period.
+ * The string representation is composed of value and type string.
+ * For example, 5 minute aggregation is represented as {@code "5m"}.
+ * The value of {@code 1} is omitted in the string representation, so
+ * {@link #DAY} (one day) is represented as {@code "d"}.
+ * This string representation can be converted back into object
+ * with {@link #parse(String)} method.
+ * @return string representation of this aggregation period.
+ */
+ @Override
+ public String toString() {
+ if (string == null)
+ string = value == 1 ? type.toString() : value == (long)value ? (long)value + "" + type : value + "" + type;
+ return string;
+ }
+
+ /**
+ * Parses string representation of aggregation period into object.
+ * Any string that was returned by {@link #toString()} can be parsed.
+ * This method is flexible in the way candle types can be specified.
+ * See {@link CandleType#parse(String)} for details.
+ * @param s string representation of aggregation period.
+ * @return aggregation period object.
+ * @throws IllegalArgumentException if string representation is invalid.
+ */
+ public static CandlePeriod parse(String s) {
+ if (s.equals(CandleType.DAY.toString()))
+ return DAY;
+ if (s.equals(CandleType.TICK.toString()))
+ return TICK;
+ int i = 0;
+ for (; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if ((c < '0' || c > '9') && c != '.' && c != '-')
+ break;
+ }
+ String value = s.substring(0, i);
+ String type = s.substring(i);
+ return valueOf(value.isEmpty() ? 1 : Double.parseDouble(value), CandleType.parse(type));
+ }
+
+ /**
+ * Returns candle period with the given value and type.
+ * @param value candle period value.
+ * @param type candle period type.
+ * @return candle period with the given value and type.
+ */
+ public static CandlePeriod valueOf(double value, CandleType type) {
+ if (value == 1 && type == CandleType.DAY)
+ return DAY;
+ if (value == 1 && type == CandleType.TICK)
+ return TICK;
+ return new CandlePeriod(value, type);
+ }
+
+ /**
+ * Returns candle period of the given candle symbol string.
+ * The result is {@link #DEFAULT} if the symbol does not have candle period attribute.
+ * @param symbol candle symbol string.
+ * @return candle period of the given candle symbol string.
+ * @throws IllegalArgumentException if string representation is invalid.
+ */
+ public static CandlePeriod getAttributeForSymbol(String symbol) {
+ String string = MarketEventSymbols.getAttributeStringByKey(symbol, ATTRIBUTE_KEY);
+ return string == null ? DEFAULT : parse(string);
+ }
+
+
+ /**
+ * Returns candle symbol string with the normalized representation of the candle period attribute.
+ * @param symbol candle symbol string.
+ * @return candle symbol string with the normalized representation of the the candle period attribute.
+ */
+ public static String normalizeAttributeForSymbol(String symbol) {
+ String a = MarketEventSymbols.getAttributeStringByKey(symbol, ATTRIBUTE_KEY);
+ if (a == null)
+ return symbol;
+ try {
+ CandlePeriod other = parse(a);
+ if (other.equals(DEFAULT))
+ MarketEventSymbols.removeAttributeStringByKey(symbol, ATTRIBUTE_KEY);
+ if (!a.equals(other.toString()))
+ return MarketEventSymbols.changeAttributeStringByKey(symbol, ATTRIBUTE_KEY, other.toString());
+ return symbol;
+ } catch (IllegalArgumentException e) {
+ return symbol;
+ }
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandlePrice.java b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandlePrice.java
new file mode 100644
index 000000000..40e563569
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandlePrice.java
@@ -0,0 +1,176 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event.candle;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.dxfeed.event.market.MarketEventSymbols;
+import com.dxfeed.event.market.PriceType;
+
+/**
+ * Price type attribute of {@link CandleSymbol} defines price that is used to build the candles.
+ *
+ * Implementation details
+ *
+ * This attribute is encoded in a symbol string with
+ * {@link MarketEventSymbols#getAttributeStringByKey(String, String) MarketEventSymbols.getAttributeStringByKey},
+ * {@link MarketEventSymbols#changeAttributeStringByKey(String, String, String) changeAttributeStringByKey}, and
+ * {@link MarketEventSymbols#removeAttributeStringByKey(String, String) removeAttributeStringByKey} methods.
+ * The key to use with these methods is available via
+ * {@link #ATTRIBUTE_KEY} constant.
+ * The value that this key shall be set to is equal to
+ * the corresponding {@link #toString() CandlePrice.toString()}
+ */
+public enum CandlePrice implements CandleSymbolAttribute {
+ /**
+ * Last trading price.
+ */
+ LAST("last"),
+
+ /**
+ * Quote bid price.
+ */
+ BID("bid"),
+
+ /**
+ * Quote ask price.
+ */
+ ASK("ask"),
+
+ /**
+ * Market price defined as average between quote bid and ask prices.
+ */
+ MARK("mark"),
+
+ /**
+ * Official settlement price that is defined by exchange or last trading price otherwise.
+ * It updates based on all {@link PriceType PriceType} values:
+ * {@link PriceType#INDICATIVE}, {@link PriceType#PRELIMINARY}, and {@link PriceType#FINAL}.
+ */
+ SETTLEMENT("s");
+
+ /**
+ * Default price type is {@link #LAST}.
+ */
+ public static final CandlePrice DEFAULT = LAST;
+
+ /**
+ * The attribute key that is used to store the value of {@code CandlePrice} in
+ * a symbol string using methods of {@link MarketEventSymbols} class.
+ * The value of this constant is "price".
+ * The value that this key shall be set to is equal to
+ * the corresponding {@link #toString() CandlePrice.toString()}
+ */
+ public static final String ATTRIBUTE_KEY = "price";
+
+ private final String string;
+
+ CandlePrice(String string) {
+ this.string = string;
+ }
+
+ /**
+ * Returns candle event symbol string with this candle price type set.
+ * @param symbol original candle event symbol.
+ * @return candle event symbol string with this candle price type set.
+ */
+ public String changeAttributeForSymbol(String symbol) {
+ return this == DEFAULT ?
+ MarketEventSymbols.removeAttributeStringByKey(symbol, ATTRIBUTE_KEY) :
+ MarketEventSymbols.changeAttributeStringByKey(symbol, ATTRIBUTE_KEY, toString());
+ }
+
+ /**
+ * Internal method that initializes attribute in the candle symbol.
+ * @param candleSymbol candle symbol.
+ * @throws IllegalStateException if used outside of internal initialization logic.
+ */
+ public void checkInAttributeImpl(CandleSymbol candleSymbol) {
+ if (candleSymbol.price != null)
+ throw new IllegalStateException("Already initialized");
+ candleSymbol.price = this;
+ }
+
+ /**
+ * Returns string representation of this candle price type.
+ * The string representation of candle price type is a lower case string
+ * that corresponds to its {@link #name() name}. For example,
+ * {@link #LAST} is represented as "last".
+ * @return string representation of this candle price type.
+ */
+ @Override
+ public String toString() {
+ return string;
+ }
+
+ private static final Map BY_STRING = new HashMap();
+
+ static {
+ for (CandlePrice price : values())
+ BY_STRING.put(price.toString(), price);
+ }
+
+ /**
+ * Parses string representation of candle price type into object.
+ * Any string that was returned by {@link #toString()} can be parsed
+ * and case is ignored for parsing.
+ * @param s string representation of candle price type.
+ * @return candle price type.
+ * @throws IllegalArgumentException if the string representation is invalid.
+ */
+ public static CandlePrice parse(String s) {
+ int n = s.length();
+ if (n == 0)
+ throw new IllegalArgumentException("Missing candle price");
+ CandlePrice result = BY_STRING.get(s);
+ // fast path to reverse toString result
+ if (result != null)
+ return result;
+ // slow path for everything else
+ for (CandlePrice price : values()) {
+ String ps = price.toString();
+ if (ps.length() >= n && ps.substring(0, n).equalsIgnoreCase(s))
+ return price;
+ }
+ throw new IllegalArgumentException("Unknown candle price: " + s);
+ }
+
+ /**
+ * Returns candle price type of the given candle symbol string.
+ * The result is {@link #DEFAULT} if the symbol does not have candle price attribute.
+ * @param symbol candle symbol string.
+ * @return candle price of the given candle symbol string.
+ */
+ public static CandlePrice getAttributeForSymbol(String symbol) {
+ String string = MarketEventSymbols.getAttributeStringByKey(symbol, ATTRIBUTE_KEY);
+ return string == null ? DEFAULT : parse(string);
+ }
+
+ /**
+ * Returns candle symbol string with the normalized representation of the candle price type attribute.
+ * @param symbol candle symbol string.
+ * @return candle symbol string with the normalized representation of the the candle price type attribute.
+ */
+ public static String normalizeAttributeForSymbol(String symbol) {
+ String a = MarketEventSymbols.getAttributeStringByKey(symbol, ATTRIBUTE_KEY);
+ if (a == null)
+ return symbol;
+ try {
+ CandlePrice other = parse(a);
+ if (other == DEFAULT)
+ MarketEventSymbols.removeAttributeStringByKey(symbol, ATTRIBUTE_KEY);
+ if (!a.equals(other.toString()))
+ return MarketEventSymbols.changeAttributeStringByKey(symbol, ATTRIBUTE_KEY, other.toString());
+ return symbol;
+ } catch (IllegalArgumentException e) {
+ return symbol;
+ }
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandleSession.java b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandleSession.java
new file mode 100644
index 000000000..547e62a49
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/candle/CandleSession.java
@@ -0,0 +1,158 @@
+/*
+ * QDS - Quick Data Signalling Library
+ * Copyright (C) 2002-2016 Devexperts LLC
+ *
+ * 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/.
+ */
+package com.dxfeed.event.candle;
+
+import com.dxfeed.event.market.MarketEventSymbols;
+import com.dxfeed.schedule.SessionFilter;
+
+/**
+ * Session attribute of {@link CandleSymbol} defines trading that is used to build the candles.
+ *
+ * Implementation details
+ *
+ * This attribute is encoded in a symbol string with
+ * {@link MarketEventSymbols#getAttributeStringByKey(String, String) MarketEventSymbols.getAttributeStringByKey},
+ * {@link MarketEventSymbols#changeAttributeStringByKey(String, String, String) changeAttributeStringByKey}, and
+ * {@link MarketEventSymbols#removeAttributeStringByKey(String, String) removeAttributeStringByKey} methods.
+ *
+ * {@link #ANY} session is a default.
+ * The key to use with these methods is available via
+ * {@link #ATTRIBUTE_KEY} constant.
+ * The value that this key shall be set to is equal to
+ * the corresponding {@link #toString() CandleSession.toString()}
+ */
+public enum CandleSession implements CandleSymbolAttribute