diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index 65d9ea794..929b4a27c 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,3 +1,17 @@ +QDS 3.292: + +* [QD-1251] New order source for cboe C2 options +* [QD-1241] dxFeed API: Extend Order event for Full Order Book + - JVM parameter "-Ddxscheme.fob=true" will add Full Order Book fields to Order record + - List of Order sources that support Full Order Book can be customized by + "-Dcom.dxfeed.event.market.impl.Order.fob.suffixes" parameter (default value="|#NTV") +* [QD-1244] QDS: Add support for long and timestamp fields in QD schema + - Added support for long values and time in millis to com.dxfeed.annotation.EventFieldType + (to enable long use @EventFieldMapping(type = EventFieldType.LONG) annotation) + - Changed ordering of constants in com.dxfeed.annotation.EventFieldType +* [QD-1235] QD Core: Disable conflation for Order events + - JVM parameter "-Dcom.devexperts.qd.impl.matrix.History.conflateFilter" manages which records are conflated; + if "-Ddxscheme.fob=true" is specified, then default value is "!:Order*", else it is "*" QDS 3.291: diff --git a/auth/pom.xml b/auth/pom.xml index d5835fe45..4161c5638 100644 --- a/auth/pom.xml +++ b/auth/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/dxfeed-api/pom.xml b/dxfeed-api/pom.xml index 0be83d768..053e1ea4b 100644 --- a/dxfeed-api/pom.xml +++ b/dxfeed-api/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java index b6d48ff8b..a725db62b 100644 --- a/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java +++ b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java @@ -25,11 +25,21 @@ public enum EventFieldType { */ TRANSIENT, + /** @deprecated Use {@link #TIME_SECONDS} instead. */ + @Deprecated + TIME, + /** * 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, + TIME_SECONDS, + + /** + * This type can be used for {@code long} properties that store time in milliseconds since Java Epoch. + * It will be mapped to a time millis QD field. + */ + TIME_MILLIS, /** * This type can be used for {@code int} properties that store number of days since Java Epoch. @@ -42,6 +52,13 @@ public enum EventFieldType { */ INT, + /** + * This type can be used for {@code long} properties. + * By default primitive longs are mapped to {@link #DECIMAL} - use this type with {@link EventFieldMapping} + * annotation to map to a long QD field. + */ + LONG, + /** * This type can be used for all primitive properties. * It will be mapped to a decimal QD field. @@ -70,5 +87,5 @@ public enum EventFieldType { * This type can be used for all reference properties. * It will be mapped to a serial object QD field. */ - MARSHALLED + MARSHALLED, } diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/AnalyticOrder.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/AnalyticOrder.java index 08e770634..0d4ecb6e7 100644 --- a/dxfeed-api/src/main/java/com/dxfeed/event/market/AnalyticOrder.java +++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/AnalyticOrder.java @@ -103,6 +103,19 @@ *

* This event type cannot be used with {@link DXFeed#getLastEvent DXFeed.getLastEvent} method. * + *

Full Order Book Support

+ * + * Some feeds provide support for "Full Order Book" (FOB) where additional fields will be available: + * + * *

Implementation details

*

* This event is implemented on top of QDS records {@code AnalyticOrder#}, diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/Order.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/Order.java index 7956df068..33a5868a6 100644 --- a/dxfeed-api/src/main/java/com/dxfeed/event/market/Order.java +++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/Order.java @@ -100,6 +100,19 @@ * * This event type cannot be used with {@link DXFeed#getLastEvent DXFeed.getLastEvent} method. * + *

Full Order Book Support

+ * + * Some feeds provide support for "Full Order Book" (FOB) where additional fields will be available: + * + * *

Implementation details

* * This event is implemented on top of QDS record {@code Quote} for composite quotes with {@link Scope#COMPOSITE}, diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderAction.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderAction.java new file mode 100644 index 000000000..af15a8045 --- /dev/null +++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderAction.java @@ -0,0 +1,161 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 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.market; + +/** + * Action enum for the Full Order Book (FOB) Orders. Action describes business meaning of the {@link Order} event: + * whether order was added or replaced, partially or fully executed, etc. + */ +public enum OrderAction { + + /** + * Default enum value for orders that do not support "Full Order Book" and for backward compatibility - + * action must be derived from other {@link Order} fields. + * + *

All Full Order Book related fields for this action will be empty. + */ + UNDEFINED(0), + + /** + * New Order is added to Order Book. + * + *

Full Order Book fields: + *

+ */ + NEW(1), + + /** + * Order is modified and price-time-priority is not maintained (i.e. order has re-entered Order Book). + * Order {@link Order#getEventSymbol() symbol} and {@link Order#getOrderSide() side} will remain the same. + * + *

Full Order Book fields: + *

+ */ + REPLACE(2), + + /** + * Order is modified without changing its price-time-priority (usually due to partial cancel by user). + * Order's {@link Order#getSize() size} will contain new updated size. + * + *

Full Order Book fields: + *

+ */ + MODIFY(3), + + /** + * Order is fully canceled and removed from Order Book. + * Order's {@link Order#getSize() size} will be equal to 0. + * + *

Full Order Book fields: + *

+ */ + DELETE(4), + + /** + * Size is changed (usually reduced) due to partial order execution. + * Order's {@link Order#getSize() size} will be updated to show current outstanding size. + * + *

Full Order Book fields: + *

+ */ + PARTIAL(5), + + /** + * Order is fully executed and removed from Order Book. + * Order's {@link Order#getSize() size} will be equals to 0. + * + *

Full Order Book fields: + *

+ */ + EXECUTE(6), + + /** + * Non-Book Trade - this Trade not refers to any entry in Order Book. + * Order's {@link Order#getSize() size} and {@link Order#getPrice() price} will be equals to 0. + * + *

Full Order Book fields: + *

+ */ + TRADE(7), + + /** + * Prior Trade/Order Execution bust. + * Order's {@link Order#getSize() size} and {@link Order#getPrice() price} will be equals to 0. + * + *

Full Order Book fields: + *

+ */ + BUST(8); + + private static final OrderAction[] ACTIONS = Util.buildEnumArrayByOrdinal(UNDEFINED, 9); + + /** + * Returns side by integer code bit pattern. + * @param code integer code. + * @return side. + * @throws ArrayIndexOutOfBoundsException if code is invalid. + */ + public static OrderAction valueOf(int code) { + return ACTIONS[code]; + } + + private final int code; + + private OrderAction(int code) { + this.code = code; + if (code != ordinal()) + throw new IllegalArgumentException("code differs from ordinal"); + } + + /** + * Returns integer code that is used in flag bits. + * @return integer code. + */ + public int getCode() { + return code; + } +} diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderBase.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderBase.java index fb56d17fc..e68a6b329 100644 --- a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderBase.java +++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderBase.java @@ -85,8 +85,9 @@ * Use the source code of {@link AbstractIndexedEventModel} for clarification on transactions and snapshot logic. */ @XmlType(propOrder = { - "eventFlags", "index", "time", "timeNanoPart", "sequence", "source", - "price", "sizeAsDouble", "executedSize", "count", "exchangeCode", "orderSide", "scope" + "eventFlags", "index", "time", "timeNanoPart", "sequence", "source", "action", "actionTime", + "orderId", "auxOrderId", "price", "sizeAsDouble", "executedSize", "count", "exchangeCode", "orderSide", "scope", + "tradeId", "tradePrice", "tradeSize" }) public class OrderBase extends MarketEvent implements IndexedEvent { private static final long serialVersionUID = 3; @@ -148,12 +149,16 @@ public class OrderBase extends MarketEvent implements IndexedEvent { /* * Flags property has several significant bits that are packed into an integer in the following way: - * 31..11 10...4 3 2 1 0 - * +--------+--------+----+----+----+----+ - * | |Exchange| Side | Scope | - * +--------+--------+----+----+----+----+ + * 31..15 14..11 10..4 3 2 1 0 + * +--------+--------+--------+----+----+----+----+ + * | | Action |Exchange| Side | Scope | + * +--------+--------+--------+----+----+----+----+ */ + // ACTION values are taken from OrderAction enum. + static final int ACTION_MASK = 0x0f; + static final int ACTION_SHIFT = 11; + // EXCHANGE values are ASCII chars in [0, 127]. static final int EXCHANGE_MASK = 0x7f; static final int EXCHANGE_SHIFT = 4; @@ -197,12 +202,21 @@ public class OrderBase extends MarketEvent implements IndexedEvent { private long index; private long timeSequence; private int timeNanoPart; + + private long actionTime; + private long orderId; + private long auxOrderId; + private double price = Double.NaN; private double size = Double.NaN; private double executedSize = Double.NaN; private long count; private int flags; + private long tradeId; + private double tradePrice = Double.NaN; + private double tradeSize = Double.NaN; + /** * Creates new order with default values. */ @@ -378,13 +392,90 @@ public long getTimeNanos() { /** * Changes time of this order. * Time is measured in nanoseconds between the current time and midnight, January 1, 1970 UTC. - * @param timeNanos time of this order in nanoseconds.. + * @param timeNanos time of this order in nanoseconds. */ public void setTimeNanos(long timeNanos) { setTime(TimeNanosUtil.getMillisFromNanos(timeNanos)); timeNanoPart = TimeNanosUtil.getNanoPartFromNanos(timeNanos); } + /** + * Returns order action if available, otherwise - {@link OrderAction#UNDEFINED}. + *

This field is a part of the FOB support. + * @return order action or {@link OrderAction#UNDEFINED}. + */ + @XmlElement + public OrderAction getAction() { + return OrderAction.valueOf(Util.getBits(flags, ACTION_MASK, ACTION_SHIFT)); + } + + /** + * Changes action of this order. + * @param action side of this order. + */ + public void setAction(OrderAction action) { + flags = Util.setBits(flags, ACTION_MASK, ACTION_SHIFT, action.getCode()); + } + + /** + * Returns time of the last {@link #getAction() action}. + *

This field is a part of the FOB support. + * @return time of the last order action. + */ + @XmlJavaTypeAdapter(type=long.class, value=XmlTimeAdapter.class) + public long getActionTime() { + return actionTime; + } + + /** + * Changes time of the last action + * @param actionTime last order action time. + */ + public void setActionTime(long actionTime) { + this.actionTime = actionTime; + } + + /** + * Returns order ID if available. Some actions ({@link OrderAction#TRADE}, + * {@link OrderAction#BUST}) have no order ID since they are not related to any order in Order book. + *

This field is a part of the FOB support. + * @return order ID or 0 if not available. + */ + public long getOrderId() { + return orderId; + } + + /** + * Changes order ID. + * @param orderId order ID. + */ + public void setOrderId(long orderId) { + this.orderId = orderId; + } + + /** + * Returns auxiliary order ID if available: + *

    + *
  • in {@link OrderAction#NEW} - ID of the order replaced by this new order
  • + *
  • in {@link OrderAction#DELETE} - ID of the order that replaces this deleted order
  • + *
  • in {@link OrderAction#PARTIAL} - ID of the aggressor order
  • + *
  • in {@link OrderAction#EXECUTE} - ID of the aggressor order
  • + *
+ *

This field is a part of the FOB support. + * @return auxiliary order ID or 0 if not available. + */ + public long getAuxOrderId() { + return auxOrderId; + } + + /** + * Changes auxiliary order ID. + * @param auxOrderId auxiliary order ID. + */ + public void setAuxOrderId(long auxOrderId) { + this.auxOrderId = auxOrderId; + } + /** * Returns price of this order. * @return price of this order. @@ -477,6 +568,57 @@ public void setCount(long count) { this.count = count; } + /** + * Returns trade (order execution) ID for events containing trade-related action. + *

This field is a part of the FOB support. + * @return trade ID or 0 if not available. + */ + public long getTradeId() { + return tradeId; + } + + /** + * Changes trade ID. + * @param tradeId trade ID. + */ + public void setTradeId(long tradeId) { + this.tradeId = tradeId; + } + + /** + * Returns trade price for events containing trade-related action. + *

This field is a part of the FOB support. + * @return trade price of this action. + */ + public double getTradePrice() { + return tradePrice; + } + + /** + * Changes trade price. + * @param tradePrice trade price. + */ + public void setTradePrice(double tradePrice) { + this.tradePrice = tradePrice; + } + + /** + * Returns trade size for events containing trade-related action. + *

This field is a part of the FOB support. + * @return trade size. + */ + public double getTradeSize() { + return tradeSize; + } + + /** + * Changes trade size. + * @param tradeSize trade size. + */ + public void setTradeSize(double tradeSize) { + this.tradeSize = tradeSize; + } + /** * Returns exchange code of this order. * @return exchange code of this order. @@ -594,13 +736,20 @@ String baseFieldsToString() { ", time=" + TimeFormat.DEFAULT.withMillis().format(getTime()) + ", sequence=" + getSequence() + ", timeNanoPart=" + timeNanoPart + + ", action=" + getAction() + + ", actionTime=" + TimeFormat.DEFAULT.withMillis().format(actionTime) + + ", orderId=" + orderId + + ", auxOrderId=" + auxOrderId + ", price=" + price + ", size=" + size + ", executedSize=" + executedSize + ", count=" + count + ", exchange=" + Util.encodeChar(getExchangeCode()) + ", side=" + getOrderSide() + - ", scope=" + getScope(); + ", scope=" + getScope() + + ", tradeId=" + tradeId + + ", tradePrice=" + tradePrice + + ", tradeSize=" + tradeSize; } // ========================= package private access for delegate ========================= diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderSource.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderSource.java index ebec7baca..7e2036fe7 100644 --- a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderSource.java +++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderSource.java @@ -48,11 +48,13 @@ public final class OrderSource extends IndexedEventSource { private static final int TYPE_ORDER = 0; private static final int TYPE_ANALYTIC_ORDER = 1; private static final int TYPE_SPREAD_ORDER = 2; - private static final int N_TYPES = 3; + private static final int FLAG_FULL_ORDER_BOOK = 3; + private static final int N_TYPES = 4; private static final int PUB_ORDER = 1 << TYPE_ORDER; private static final int PUB_ANALYTIC_ORDER = 1 << TYPE_ANALYTIC_ORDER; private static final int PUB_SPREAD_ORDER = 1 << TYPE_SPREAD_ORDER; + private static final int FULL_ORDER_BOOK = 1 << FLAG_FULL_ORDER_BOOK; @SuppressWarnings("unchecked") private static final List[] PUBLISHABLE_LISTS = new List[N_TYPES]; @@ -117,10 +119,11 @@ public final class OrderSource extends IndexedEventSource { /** * Default source for publishing custom order books. - * {@link Order}, {@link AnalyticOrder} and {@link SpreadOrder} events are {@link #isPublishable(Class) publishable} on this - * source and the corresponding subscription can be observed via {@link DXPublisher}. + * {@link Order}, {@link AnalyticOrder} and {@link SpreadOrder} events are {@link #isPublishable(Class) publishable} + * on this source and the corresponding subscription can be observed via {@link DXPublisher}. */ - public static final OrderSource DEFAULT = new OrderSource(0, "DEFAULT", PUB_ORDER | PUB_ANALYTIC_ORDER | PUB_SPREAD_ORDER); + public static final OrderSource DEFAULT = new OrderSource(0, "DEFAULT", + PUB_ORDER | PUB_ANALYTIC_ORDER | PUB_SPREAD_ORDER | FLAG_FULL_ORDER_BOOK); // ======== BEGIN: Custom OrderSource definitions ======== @@ -132,7 +135,7 @@ public final class OrderSource extends IndexedEventSource { * {@link Order} events are {@link #isPublishable(Class) publishable} on this * source and the corresponding subscription can be observed via {@link DXPublisher}. */ - public static final OrderSource NTV = new OrderSource("NTV", PUB_ORDER); + public static final OrderSource NTV = new OrderSource("NTV", PUB_ORDER | FULL_ORDER_BOOK); /** * NASDAQ Total View. Record for price level book. @@ -302,6 +305,13 @@ public final class OrderSource extends IndexedEventSource { */ public static final OrderSource CFE = new OrderSource("CFE", PUB_ORDER); + /** + * CBOE Options C2 Exchange. + * {@link Order} events are {@link #isPublishable(Class) publishable} on this + * source and the corresponding subscription can be observed via {@link DXPublisher}. + */ + public static final OrderSource C2OX = new OrderSource("C2OX", PUB_ORDER); + /** * Small Exchange. * {@link Order} events are {@link #isPublishable(Class) publishable} on this @@ -369,6 +379,15 @@ public static List publishable(Class eventType return PUBLISHABLE_VIEWS[getEventTypeId(eventType)]; } + /** + * Returns a list of publishable order sources that support Full Order Book. + * + * @return a list of publishable order sources that support Full Order Book. + */ + public static List fullOrderBook() { + return PUBLISHABLE_VIEWS[FLAG_FULL_ORDER_BOOK]; + } + // ========================= private instance fields ========================= private final int pubFlags; @@ -399,6 +418,10 @@ private OrderSource(int id, String name, int pubFlags) { if (!SOURCES_BY_NAME.add(this)) throw new IllegalArgumentException("duplicate name"); + // Flag FULL_ORDER_BOOK requires that source must be publishable + if ((pubFlags & FULL_ORDER_BOOK) != 0 && (pubFlags & (PUB_ORDER | PUB_ANALYTIC_ORDER | PUB_SPREAD_ORDER)) == 0) + throw new IllegalArgumentException("unpublishable full order book order"); + CACHE_SIZE = Math.max(CACHE_SIZE, SOURCES_BY_ID.size() * 4); for (int i = 0; i < N_TYPES; i++) { @@ -431,6 +454,15 @@ public boolean isPublishable(Class eventType) { return (pubFlags & (1 << getEventTypeId(eventType))) != 0; } + /** + * Returns {@code true} if this source supports Full Order Book. + * + * @return {@code true} if this source supports Full Order Book. + */ + public boolean isFullOrderBook() { + return (pubFlags & FULL_ORDER_BOOK) != 0; + } + // ========================= private helper methods ========================= private static void checkChar(char c) { diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/SpreadOrder.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/SpreadOrder.java index d8747f046..6f2bf1fd6 100644 --- a/dxfeed-api/src/main/java/com/dxfeed/event/market/SpreadOrder.java +++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/SpreadOrder.java @@ -94,6 +94,19 @@ * * This event type cannot be used with {@link DXFeed#getLastEvent DXFeed.getLastEvent} method. * + *

Full Order Book Support

+ * + * Some feeds provide support for "Full Order Book" (FOB) where additional fields will be available: + *
    + *
  • {@link #getAction() action} - event business meaning (see {@link OrderAction} for more details)
  • + *
  • {@link #getActionTime() actionTime} - time of the last action
  • + *
  • {@link #getOrderId() orderId} - ID of this order
  • + *
  • {@link #getAuxOrderId() auxOrderId} - additional ID for this order
  • + *
  • {@link #getTradeId() tradeId} - trade (order execution) ID
  • + *
  • {@link #getTradePrice() tradePrice} - price of the trade
  • + *
  • {@link #getTradeSize() tradeSize} - size of the trade
  • + *
+ * *

Implementation details

* * This event is implemented on top of QDS records {@code SpreadOrder#}, diff --git a/dxfeed-bin/pom.xml b/dxfeed-bin/pom.xml index 580f4a501..f30cadff3 100644 --- a/dxfeed-bin/pom.xml +++ b/dxfeed-bin/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/dxfeed-codegen-verify/pom.xml b/dxfeed-codegen-verify/pom.xml index 6b9c97c67..f80d92335 100644 --- a/dxfeed-codegen-verify/pom.xml +++ b/dxfeed-codegen-verify/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/dxfeed-codegen/pom.xml b/dxfeed-codegen/pom.xml index efa68665b..779492fa1 100644 --- a/dxfeed-codegen/pom.xml +++ b/dxfeed-codegen/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/DelegateGen.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/DelegateGen.java index f31f4f6db..d87650413 100644 --- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/DelegateGen.java +++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/DelegateGen.java @@ -192,14 +192,28 @@ DelegateGen field(String fieldName, FieldType fieldType) { DelegateGen phantom(String phantomProperty) { if (fieldMappings.isEmpty()) { + // Phantom record record.phantomProperty = phantomProperty; mappingGen.phantom = true; } else { - lastFieldMapping().field.phantomProperty = phantomProperty; + // Phantom field + RecordField field = lastFieldMapping().field; + field.conditionalProperty = phantomProperty; + field.isPhantom = true; } return this; } + // implies optional + DelegateGen onlyIf(String conditionalProperty) { + // Conditional field + RecordField field = lastFieldMapping().field; + field.conditionalProperty = conditionalProperty; + field.isPhantom = false; + field.required = false; + return this; + } + DelegateGen internal() { lastFieldMapping().internal = true; return this; @@ -284,7 +298,7 @@ DelegateGen mapTimeAndSequence() { } DelegateGen mapTimeAndSequence(String timeFieldName, String sequenceFieldName) { - map("Time", timeFieldName, FieldType.TIME).internal(); + map("Time", timeFieldName, FieldType.TIME_SECONDS).internal(); map("Sequence", sequenceFieldName, FieldType.SEQUENCE).internal(); assign("TimeSequence", "(((long)#Time.Seconds#) << 32) | (#Sequence# & 0xFFFFFFFFL)"); injectPutEventCode( @@ -299,7 +313,7 @@ DelegateGen mapTimeAndSequenceToIndex() { } DelegateGen mapTimeAndSequenceToIndex(String timeFieldName, String sequenceFieldName) { - map("Time", timeFieldName, FieldType.TIME).time(0).internal(); + map("Time", timeFieldName, FieldType.TIME_SECONDS).time(0).internal(); map("Sequence", sequenceFieldName, FieldType.SEQUENCE).time(1).internal(); assign("Index", "(((long)#Time.Seconds#) << 32) | (#Sequence# & 0xFFFFFFFFL)"); injectPutEventCode( diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FactoryImplGen.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FactoryImplGen.java index 2d8e5bfeb..f79064f3d 100644 --- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FactoryImplGen.java +++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FactoryImplGen.java @@ -119,22 +119,26 @@ private void generateBuildSchemeCode(ClassGen cg) { private void generateFieldCode(ClassGen cg, Map fields, String recordNameReference, boolean isRegional) { - String phantomProperty = null; + String conditionalProperty = null; + boolean isPhantom = false; for (Map.Entry fieldEntry : fields.entrySet()) { String fieldName = fieldEntry.getKey(); RecordField f = fieldEntry.getValue(); if (isRegional && f.isCompositeOnly) continue; - if (phantomProperty != null && !phantomProperty.equals(f.phantomProperty)) { + if (conditionalProperty != null && + (!conditionalProperty.equals(f.conditionalProperty) || isPhantom != f.isPhantom)) + { cg.unindent(); cg.code("}"); - phantomProperty = null; + conditionalProperty = null; } - if (f.phantomProperty != null && !f.phantomProperty.equals(phantomProperty)) { + if (f.conditionalProperty != null && !f.conditionalProperty.equals(conditionalProperty)) { cg.addImport(new ClassName(SystemProperties.class)); - cg.code("if (SystemProperties.getBooleanProperty(\"" + f.phantomProperty + "\", false)) {"); + cg.code("if (SystemProperties.getBooleanProperty(\"" + f.conditionalProperty + "\", false)) {"); cg.indent(); - phantomProperty = f.phantomProperty; + conditionalProperty = f.conditionalProperty; + isPhantom = f.isPhantom; } if (f.onlySuffixesDefault != null || f.exceptSuffixes != null) { cg.code("if (" + @@ -182,7 +186,7 @@ private void generateFieldCode(ClassGen cg, Map fields, Str if (f.onlySuffixesDefault != null || f.exceptSuffixes != null) cg.unindent(); } - if (phantomProperty != null) { + if (conditionalProperty != null) { cg.unindent(); cg.code("}"); } diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FieldType.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FieldType.java index 37156d023..5c9d2259f 100644 --- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FieldType.java +++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FieldType.java @@ -30,8 +30,15 @@ enum FieldType { .addAccess(Access.createWithAccessPattern("", "int", "0", "getInt(cursor, %s)", "setInt(cursor, %s, %s)")) .setMapper(new DecimalMapper(int.class)) ), - TIME(new Builder() - .addField(new Field(false, SerialFieldType.TIME)) + TIME_MILLIS(new Builder() + .addField(new Field(false, SerialFieldType.TIME_MILLIS)) + .addAccess(Access.createWithAccessPattern("Millis", "long", "0", "getLong(cursor, %s)", "setLong(cursor, %s, %s)")) + .addAccess(Access.createWithAccessPattern("Seconds", "int", "0", "TimeUtil.getSecondsFromTime(getLong(cursor, %s))", "setLong(cursor, %s, %s * 1000L)")) + .addImport(new ClassName(TimeUtil.class)) + .setMapper(new DefaultMapper("Millis", long.class)) + ), + TIME_SECONDS(new Builder() + .addField(new Field(false, SerialFieldType.TIME_SECONDS)) .addAccess(Access.createWithAccessPattern("Millis", "long", "0", "getInt(cursor, %s) * 1000L", "setInt(cursor, %s, TimeUtil.getSecondsFromTime(%s))")) .addAccess(Access.createWithAccessPattern("Seconds", "int", "0", "getInt(cursor, %s)", "setInt(cursor, %s, %s)")) .addImport(new ClassName(TimeUtil.class)) @@ -57,6 +64,11 @@ enum FieldType { .addAccess(Access.createWithAccessPattern("", "int", "0", "getInt(cursor, %s)", "setInt(cursor, %s, %s)")) .setMapper(new DefaultMapper(int.class)) ), + LONG(new Builder() + .addField(new Field(false, SerialFieldType.LONG)) + .addAccess(Access.createWithAccessPattern("", "long", "0", "getLong(cursor, %s)", "setLong(cursor, %s, %s)")) + .setMapper(new DefaultMapper(long.class)) + ), INT(new Builder() .addField(new Field(false, SerialFieldType.COMPACT_INT)) .addAccess(Access.createWithAccessPattern("", "int", "0", "getInt(cursor, %s)", "setInt(cursor, %s, %s)")) @@ -202,6 +214,8 @@ static final class Field { final boolean isObject; final SerialFieldType serialType; final boolean adaptiveDecimal; + //FIXME + //final boolean adaptiveTime; // no selectors final String[] typeSelectors; final String suffix; diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGen.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGen.java index 8fb0f5082..0d72f2663 100644 --- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGen.java +++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGen.java @@ -65,6 +65,10 @@ public class ImplCodeGen { "1hour|2hour|3hour|4hour|6hour|8hour|12hour|Day|2Day|3Day|4Day|Week|Month|OptExp"; private static final String BID_ASK_VOLUME_SUFFIXES = ".*[{,]price=(bid|ask|mark|s)[,}].*"; + private static final String DXSCHEME_FOB = "dxscheme.fob"; + private static final String FOB_SUFFIX_PROPERTY = "com.dxfeed.event.market.impl.Order.fob.suffixes"; + private static final String FOB_SUFFIX_DEFAULT = getFullOrderBookSuffixes(); + public static void main(String[] args) throws IOException { new ImplCodeGen("", false).run(); } @@ -92,11 +96,11 @@ public void runForDxfeedImpl() throws IOException { inheritMappingFrom(MARKET_EVENT_MAPPING). map("Sequence", FieldType.SEQUENCE).optional().disabledByDefault().internal(). // assign after bid/ask time map("TimeNanoPart", FieldType.TIME_NANO_PART).optional().disabledByDefault(). - map("BidTime", "Bid.Time", FieldType.TIME).optional(). + map("BidTime", "Bid.Time", FieldType.TIME_SECONDS).optional(). map("BidExchangeCode", "Bid.Exchange", FieldType.CHAR).alt("recordExchange").compositeOnly().optional(). map("BidPrice", "Bid.Price", FieldType.PRICE). map("BidSize", "Bid.Size", FieldType.SIZE). - map("AskTime", "Ask.Time", FieldType.TIME).optional(). + map("AskTime", "Ask.Time", FieldType.TIME_SECONDS).optional(). map("AskExchangeCode", "Ask.Exchange", FieldType.CHAR).alt("recordExchange").compositeOnly().optional(). map("AskPrice", "Ask.Price", FieldType.PRICE). map("AskSize", "Ask.Size", FieldType.SIZE). @@ -111,10 +115,10 @@ public void runForDxfeedImpl() throws IOException { field("BidSize", "Bid.Size", FieldType.DECIMAL_AS_DOUBLE). field("AskPrice", "Ask.Price", FieldType.DECIMAL_AS_DOUBLE). field("AskSize", "Ask.Size", FieldType.DECIMAL_AS_DOUBLE). - field("BidPriceTimestamp", "Bid.Price.Timestamp", FieldType.TIME). - field("BidSizeTimestamp", "Bid.Size.Timestamp", FieldType.TIME). - field("AskPriceTimestamp", "Ask.Price.Timestamp", FieldType.TIME). - field("AskSizeTimestamp", "Ask.Size.Timestamp", FieldType.TIME); + field("BidPriceTimestamp", "Bid.Price.Timestamp", FieldType.TIME_SECONDS). + field("BidSizeTimestamp", "Bid.Size.Timestamp", FieldType.TIME_SECONDS). + field("AskPriceTimestamp", "Ask.Price.Timestamp", FieldType.TIME_SECONDS). + field("AskSizeTimestamp", "Ask.Size.Timestamp", FieldType.TIME_SECONDS); ctx.delegate("Trade", Trade.class, "Trade&"). inheritDelegateFrom(MARKET_EVENT_DELEGATE). @@ -190,7 +194,7 @@ public void runForDxfeedImpl() throws IOException { exchanges("I", true). // generate only Book&I by default field("Id", "ID", FieldType.INDEX).time(0). field("Sequence", FieldType.VOID).time(1). - field("Time", FieldType.TIME). + field("Time", FieldType.TIME_SECONDS). field("Type", FieldType.CHAR). field("Price", FieldType.PRICE). field("Size", FieldType.SIZE). @@ -211,8 +215,8 @@ public void runForDxfeedImpl() throws IOException { field("FreeFloat", FieldType.DECIMAL_AS_LONG).optional(). map("HighLimitPrice", "HighLimitPrice", FieldType.PRICE).optional(). map("LowLimitPrice", "LowLimitPrice", FieldType.PRICE).optional(). - map("HaltStartTime", "Halt.StartTime", FieldType.TIME).optional(). - map("HaltEndTime", "Halt.EndTime", FieldType.TIME).optional(). + map("HaltStartTime", "Halt.StartTime", FieldType.TIME_SECONDS).optional(). + map("HaltEndTime", "Halt.EndTime", FieldType.TIME_SECONDS).optional(). map("Flags", "Flags", FieldType.FLAGS).optional(). map("Description", "Description", FieldType.STRING). map("StatusReason", "StatusReason", FieldType.STRING).optional(). @@ -233,11 +237,17 @@ public void runForDxfeedImpl() throws IOException { ). mapTimeAndSequence(). map("TimeNanoPart", "TimeNanoPart", FieldType.TIME_NANO_PART).optional().disabledByDefault(). + map("ActionTime", "ActionTime", FieldType.TIME_MILLIS).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("OrderId", "OrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("AuxOrderId", "AuxOrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). map("Price", "Price", FieldType.PRICE). map("Size", "Size", FieldType.SIZE). map("ExecutedSize", "ExecutedSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault(). map("Count", "Count", FieldType.INT_DECIMAL).onlySuffixes("com.dxfeed.event.order.impl.Order.suffixes.count", ""). map("Flags", "Flags", FieldType.FLAGS). + map("TradeId", "TradeId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("TradePrice", "TradePrice", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("TradeSize", "TradeSize", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). map("MarketMaker", "MMID", FieldType.SHORT_STRING).onlySuffixes( "com.dxfeed.event.order.impl.Order.suffixes.mmid", "|#NTV|#BATE|#CHIX|#CEUX|#BXTR"). field("IcebergPeakSize", "IcebergPeakSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault(). @@ -269,11 +279,17 @@ public void runForDxfeedImpl() throws IOException { ). mapTimeAndSequence(). map("TimeNanoPart", "TimeNanoPart", FieldType.TIME_NANO_PART).optional().disabledByDefault(). + map("ActionTime", "ActionTime", FieldType.TIME_MILLIS).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("OrderId", "OrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("AuxOrderId", "AuxOrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). map("Price", "Price", FieldType.PRICE). map("Size", "Size", FieldType.SIZE). map("ExecutedSize", "ExecutedSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault(). map("Count", "Count", FieldType.INT_DECIMAL).onlySuffixes("com.dxfeed.event.order.impl.AnalyticOrder.suffixes.count", ""). map("Flags", "Flags", FieldType.FLAGS). + map("TradeId", "TradeId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("TradePrice", "TradePrice", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("TradeSize", "TradeSize", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). map("MarketMaker", "MMID", FieldType.SHORT_STRING).onlySuffixes( "com.dxfeed.event.order.impl.AnalyticOrder.suffixes.mmid", "|#NTV|#BATE|#CHIX|#CEUX|#BXTR"). map("IcebergPeakSize", "IcebergPeakSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault(). @@ -305,11 +321,17 @@ public void runForDxfeedImpl() throws IOException { ). mapTimeAndSequence(). map("TimeNanoPart", "TimeNanoPart", FieldType.TIME_NANO_PART).optional().disabledByDefault(). + map("ActionTime", "ActionTime", FieldType.TIME_MILLIS).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("OrderId", "OrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("AuxOrderId", "AuxOrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). map("Price", "Price", FieldType.PRICE). map("Size", "Size", FieldType.SIZE). map("ExecutedSize", "ExecutedSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault(). map("Count", "Count", FieldType.INT_DECIMAL).onlySuffixes("com.dxfeed.event.order.impl.SpreadOrder.suffixes.count", ""). map("Flags", "Flags", FieldType.FLAGS). + map("TradeId", "TradeId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("TradePrice", "TradePrice", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). + map("TradeSize", "TradeSize", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT). map("SpreadSymbol", "SpreadSymbol", FieldType.STRING). injectPutEventCode( "if (index < 0)", @@ -326,7 +348,7 @@ public void runForDxfeedImpl() throws IOException { subContract(QDContract.TICKER). source("m.getRecordExchange() == 0 ? OrderSource.COMPOSITE_BID : OrderSource.REGIONAL_BID"). assign("Index", "((long)getSource().id() << 48) | ((long)m.getRecordExchange() << 32)"). - map("Time", "BidTime", "Bid.Time", FieldType.TIME).optional(). + map("Time", "BidTime", "Bid.Time", FieldType.TIME_SECONDS).optional(). assign("Sequence", "0"). map("Price", "BidPrice", "Bid.Price", FieldType.PRICE). map("Size", "BidSize", "Bid.Size", FieldType.SIZE). @@ -341,7 +363,7 @@ public void runForDxfeedImpl() throws IOException { subContract(QDContract.TICKER). source("m.getRecordExchange() == 0 ? OrderSource.COMPOSITE_ASK : OrderSource.REGIONAL_ASK"). assign("Index", "((long)getSource().id() << 48) | ((long)m.getRecordExchange() << 32)"). - map("Time", "AskTime", "Ask.Time", FieldType.TIME).optional(). + map("Time", "AskTime", "Ask.Time", FieldType.TIME_SECONDS).optional(). assign("Sequence", "0"). map("Price", "AskPrice", "Ask.Price", FieldType.PRICE). map("Size", "AskSize", "Ask.Size", FieldType.SIZE). @@ -359,7 +381,7 @@ public void runForDxfeedImpl() throws IOException { assign("Index", "((long)getSource().id() << 48) | ((long)#ExchangeCode# << 32) | (#MarketMaker# & 0xFFFFFFFFL)"). map("ExchangeCode", "MMExchange", FieldType.CHAR).time(0). map("MarketMaker", "MMID", FieldType.SHORT_STRING).time(1). - map("Time", "BidTime", "MMBid.Time", FieldType.TIME).optional(). + map("Time", "BidTime", "MMBid.Time", FieldType.TIME_SECONDS).optional(). assign("Sequence", "0"). map("Price", "BidPrice", "MMBid.Price", FieldType.PRICE). map("Size", "BidSize", "MMBid.Size", FieldType.SIZE). @@ -374,7 +396,7 @@ public void runForDxfeedImpl() throws IOException { assign("Index", "((long)getSource().id() << 48) | ((long)#ExchangeCode# << 32) | (#MarketMaker# & 0xFFFFFFFFL)"). map("ExchangeCode", "MMExchange", FieldType.CHAR).time(0). map("MarketMaker", "MMID", FieldType.SHORT_STRING).time(1). - map("Time", "AskTime", "MMAsk.Time", FieldType.TIME).optional(). + map("Time", "AskTime", "MMAsk.Time", FieldType.TIME_SECONDS).optional(). assign("Sequence", "0"). map("Price", "AskPrice", "MMAsk.Price", FieldType.PRICE). map("Size", "AskSize", "MMAsk.Size", FieldType.SIZE). @@ -439,7 +461,7 @@ public void runForDxfeedImpl() throws IOException { // use common mapping for "Candle" record, just generate Trade records and bind them to Candle delegate ctx.record("com.dxfeed.event.candle", Candle.class, "Candle", "Trade."). suffixes(TRADE_RECORD_SUFFIXES). - field("Time", FieldType.TIME).time(0). + field("Time", FieldType.TIME_SECONDS).time(0). field("Sequence", FieldType.SEQUENCE).time(1). field("Count", FieldType.DECIMAL_AS_LONG).optional(). field("Open", FieldType.PRICE). @@ -552,4 +574,11 @@ private String getOrderSuffixes(Class eventType) { map(orderSource -> "|#" + orderSource.name()). collect(Collectors.joining()); } + + private static String getFullOrderBookSuffixes() { + return OrderSource.fullOrderBook().stream(). + filter(os -> !OrderSource.DEFAULT.equals(os) && !OrderSource.isSpecialSourceId(os.id())). + map(orderSource -> "|#" + orderSource.name()). + collect(Collectors.joining()); + } } diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGenAnnotationProcessor.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGenAnnotationProcessor.java index 8be247463..37141015f 100644 --- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGenAnnotationProcessor.java +++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGenAnnotationProcessor.java @@ -236,8 +236,12 @@ private FieldType mapToFieldType(EventFieldType fieldSerializedType) { case DATE: return FieldType.DATE; case DECIMAL: return FieldType.DECIMAL_AS_DOUBLE; case INT: return FieldType.INT; + case LONG: return FieldType.LONG; case SHORT_STRING: return FieldType.SHORT_STRING; - case TIME: return FieldType.TIME; + //noinspection deprecation + case TIME: return FieldType.TIME_SECONDS; + case TIME_SECONDS: return FieldType.TIME_SECONDS; + case TIME_MILLIS: return FieldType.TIME_MILLIS; case STRING: return FieldType.STRING; case MARSHALLED: return FieldType.MARSHALLED; } diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/RecordField.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/RecordField.java index ac8663c9c..d4ca11112 100644 --- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/RecordField.java +++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/RecordField.java @@ -36,7 +36,10 @@ class RecordField { String onlySuffixesDefault; String exceptSuffixes; String voidSuffixes; - String phantomProperty; + // Represents either phantom or conditional property + String conditionalProperty; + // Flag indicating phantom property + boolean isPhantom; RecordField(String propertyName, String eventName, String fieldName, FieldType fieldType) { this.propertyName = propertyName; @@ -146,6 +149,6 @@ private String generateIndexesAreNotDefinedCondition() { } boolean isActive() { - return phantomProperty == null && fieldType.accesses.size() > 0; + return !isPhantom && fieldType.accesses.size() > 0; } } diff --git a/dxfeed-codegen/src/test/java/com/dxfeed/api/codegen/event/BetterQuote.java b/dxfeed-codegen/src/test/java/com/dxfeed/api/codegen/event/BetterQuote.java index 63c37295b..2e0a02804 100644 --- a/dxfeed-codegen/src/test/java/com/dxfeed/api/codegen/event/BetterQuote.java +++ b/dxfeed-codegen/src/test/java/com/dxfeed/api/codegen/event/BetterQuote.java @@ -11,6 +11,8 @@ */ package com.dxfeed.api.codegen.event; +import com.dxfeed.annotation.EventFieldMapping; +import com.dxfeed.annotation.EventFieldType; import com.dxfeed.annotation.EventTypeMapping; import com.dxfeed.event.market.Quote; @@ -77,6 +79,7 @@ public void setBetterInt(int betterInt) { this.betterInt = betterInt; } + @EventFieldMapping(type = EventFieldType.LONG) public long getBetterLong() { return betterLong; } diff --git a/dxfeed-impl/pom.xml b/dxfeed-impl/pom.xml index 3ff318b2b..6ecb2f9aa 100644 --- a/dxfeed-impl/pom.xml +++ b/dxfeed-impl/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/dxfeed-impl/src/main/java/com/dxfeed/api/impl/EventDelegateFactory.java b/dxfeed-impl/src/main/java/com/dxfeed/api/impl/EventDelegateFactory.java index 25d327b64..bcf8d5a7f 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/api/impl/EventDelegateFactory.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/api/impl/EventDelegateFactory.java @@ -31,6 +31,7 @@ protected String getBaseRecordName(String recordName) { return recordName; } + //TODO rename to selectDecimal protected SerialFieldType select(SerialFieldType type, String... typeSelectors) { if ("true".equalsIgnoreCase(System.getProperty("dxscheme.wide"))) type = SerialFieldType.WIDE_DECIMAL; @@ -45,4 +46,6 @@ protected SerialFieldType select(SerialFieldType type, String... typeSelectors) } return type; } + + //FIXME implement selectTime } diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/candle/CandleFactoryImpl.java b/dxfeed-impl/src/main/java/com/dxfeed/event/candle/CandleFactoryImpl.java index 5b74c793e..87a9de34c 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/event/candle/CandleFactoryImpl.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/event/candle/CandleFactoryImpl.java @@ -36,7 +36,7 @@ public final class CandleFactoryImpl extends EventDelegateFactory implements Rec // BEGIN: CODE AUTOMATICALLY GENERATED: DO NOT MODIFY. IT IS REGENERATED BY com.dxfeed.api.codegen.ImplCodeGen @Override public void buildScheme(SchemeBuilder builder) { - builder.addRequiredField("TradeHistory", "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD); + builder.addRequiredField("TradeHistory", "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField("TradeHistory", "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD); builder.addOptionalField("TradeHistory", "Exchange", SerialFieldType.UTF_CHAR, "Candle", "ExchangeCode", true); builder.addRequiredField("TradeHistory", "Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); @@ -46,7 +46,7 @@ public void buildScheme(SchemeBuilder builder) { for (String suffix : SystemProperties.getProperty("com.dxfeed.event.candle.impl.Candle.suffixes", "").split("\\|")) { String recordName = "Candle" + suffix; - builder.addRequiredField(recordName, "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD); + builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD); builder.addOptionalField(recordName, "Count", select(SerialFieldType.DECIMAL), "Candle", "Count", true); builder.addRequiredField(recordName, "Open", select(SerialFieldType.DECIMAL, "dxscheme.price")); @@ -65,7 +65,7 @@ public void buildScheme(SchemeBuilder builder) { for (String suffix : SystemProperties.getProperty("com.dxfeed.event.candle.impl.Trade.suffixes", "133ticks|144ticks|233ticks|333ticks|400ticks|512ticks|1600ticks|3200ticks|1min|2min|3min|4min|5min|6min|10min|12min|15min|20min|30min|1hour|2hour|3hour|4hour|6hour|8hour|12hour|Day|2Day|3Day|4Day|Week|Month|OptExp").split("\\|")) { String recordName = "Trade." + suffix; - builder.addRequiredField(recordName, "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD); + builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD); builder.addOptionalField(recordName, "Count", select(SerialFieldType.DECIMAL), "Candle", "Count", true); builder.addRequiredField(recordName, "Open", select(SerialFieldType.DECIMAL, "dxscheme.price")); diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/AnalyticOrderDelegate.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/AnalyticOrderDelegate.java index a7f4259b4..0a081cee2 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/AnalyticOrderDelegate.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/AnalyticOrderDelegate.java @@ -47,11 +47,17 @@ public AnalyticOrder getEvent(AnalyticOrder event, RecordCursor cursor) { event.setIndex(((long) getSource().id() << 32) | (m.getIndex(cursor) & 0xFFFFFFFFL)); event.setTimeSequence((((long) m.getTimeSeconds(cursor)) << 32) | (m.getSequence(cursor) & 0xFFFFFFFFL)); event.setTimeNanoPart(m.getTimeNanoPart(cursor)); + event.setActionTime(m.getActionTimeMillis(cursor)); + event.setOrderId(m.getOrderId(cursor)); + event.setAuxOrderId(m.getAuxOrderId(cursor)); event.setPrice(m.getPrice(cursor)); event.setSizeAsDouble(m.getSizeDouble(cursor)); event.setExecutedSize(m.getExecutedSize(cursor)); event.setCount(m.getCount(cursor)); event.setFlags(m.getFlags(cursor)); + event.setTradeId(m.getTradeId(cursor)); + event.setTradePrice(m.getTradePrice(cursor)); + event.setTradeSize(m.getTradeSize(cursor)); event.setMarketMaker(m.getMarketMakerString(cursor)); event.setIcebergPeakSize(m.getIcebergPeakSize(cursor)); event.setIcebergHiddenSize(m.getIcebergHiddenSize(cursor)); @@ -69,11 +75,17 @@ public RecordCursor putEvent(AnalyticOrder event, RecordBuffer buf) { m.setTimeSeconds(cursor, (int) (event.getTimeSequence() >>> 32)); m.setSequence(cursor, (int) event.getTimeSequence()); m.setTimeNanoPart(cursor, event.getTimeNanoPart()); + m.setActionTimeMillis(cursor, event.getActionTime()); + m.setOrderId(cursor, event.getOrderId()); + m.setAuxOrderId(cursor, event.getAuxOrderId()); m.setPrice(cursor, event.getPrice()); m.setSizeDouble(cursor, event.getSizeAsDouble()); m.setExecutedSize(cursor, event.getExecutedSize()); m.setCount(cursor, (int) event.getCount()); m.setFlags(cursor, event.getFlags()); + m.setTradeId(cursor, event.getTradeId()); + m.setTradePrice(cursor, event.getTradePrice()); + m.setTradeSize(cursor, event.getTradeSize()); m.setMarketMakerString(cursor, event.getMarketMaker()); m.setIcebergPeakSize(cursor, event.getIcebergPeakSize()); m.setIcebergHiddenSize(cursor, event.getIcebergHiddenSize()); diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/MarketFactoryImpl.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/MarketFactoryImpl.java index a3a633855..61a5f6f43 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/MarketFactoryImpl.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/MarketFactoryImpl.java @@ -48,11 +48,11 @@ public final class MarketFactoryImpl extends EventDelegateFactory implements Rec public void buildScheme(SchemeBuilder builder) { builder.addOptionalField("Quote", "Sequence", SerialFieldType.SEQUENCE, "Quote", "Sequence", false); builder.addOptionalField("Quote", "TimeNanoPart", SerialFieldType.COMPACT_INT, "Quote", "TimeNanoPart", false); - builder.addOptionalField("Quote", "Bid.Time", SerialFieldType.TIME, "Quote", "BidTime", true); + builder.addOptionalField("Quote", "Bid.Time", SerialFieldType.TIME_SECONDS, "Quote", "BidTime", true); builder.addOptionalField("Quote", "Bid.Exchange", SerialFieldType.UTF_CHAR, "Quote", "BidExchangeCode", true); builder.addRequiredField("Quote", "Bid.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField("Quote", "Bid.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); - builder.addOptionalField("Quote", "Ask.Time", SerialFieldType.TIME, "Quote", "AskTime", true); + builder.addOptionalField("Quote", "Ask.Time", SerialFieldType.TIME_SECONDS, "Quote", "AskTime", true); builder.addOptionalField("Quote", "Ask.Exchange", SerialFieldType.UTF_CHAR, "Quote", "AskExchangeCode", true); builder.addRequiredField("Quote", "Ask.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField("Quote", "Ask.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); @@ -60,10 +60,10 @@ public void buildScheme(SchemeBuilder builder) { String recordName = "Quote&" + exchange; builder.addOptionalField(recordName, "Sequence", SerialFieldType.SEQUENCE, "Quote", "Sequence", false); builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "Quote", "TimeNanoPart", false); - builder.addOptionalField(recordName, "Bid.Time", SerialFieldType.TIME, "Quote", "BidTime", true); + builder.addOptionalField(recordName, "Bid.Time", SerialFieldType.TIME_SECONDS, "Quote", "BidTime", true); builder.addRequiredField(recordName, "Bid.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField(recordName, "Bid.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); - builder.addOptionalField(recordName, "Ask.Time", SerialFieldType.TIME, "Quote", "AskTime", true); + builder.addOptionalField(recordName, "Ask.Time", SerialFieldType.TIME_SECONDS, "Quote", "AskTime", true); builder.addRequiredField(recordName, "Ask.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField(recordName, "Ask.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); } @@ -73,13 +73,13 @@ public void buildScheme(SchemeBuilder builder) { builder.addRequiredField("Quote2", "Bid.Size", select(SerialFieldType.DECIMAL)); builder.addRequiredField("Quote2", "Ask.Price", select(SerialFieldType.DECIMAL)); builder.addRequiredField("Quote2", "Ask.Size", select(SerialFieldType.DECIMAL)); - builder.addRequiredField("Quote2", "Bid.Price.Timestamp", SerialFieldType.TIME); - builder.addRequiredField("Quote2", "Bid.Size.Timestamp", SerialFieldType.TIME); - builder.addRequiredField("Quote2", "Ask.Price.Timestamp", SerialFieldType.TIME); - builder.addRequiredField("Quote2", "Ask.Size.Timestamp", SerialFieldType.TIME); + builder.addRequiredField("Quote2", "Bid.Price.Timestamp", SerialFieldType.TIME_SECONDS); + builder.addRequiredField("Quote2", "Bid.Size.Timestamp", SerialFieldType.TIME_SECONDS); + builder.addRequiredField("Quote2", "Ask.Price.Timestamp", SerialFieldType.TIME_SECONDS); + builder.addRequiredField("Quote2", "Ask.Size.Timestamp", SerialFieldType.TIME_SECONDS); } - builder.addOptionalField("Trade", "Last.Time", SerialFieldType.TIME, "Trade", "Time", true); + builder.addOptionalField("Trade", "Last.Time", SerialFieldType.TIME_SECONDS, "Trade", "Time", true); builder.addOptionalField("Trade", "Last.Sequence", SerialFieldType.SEQUENCE, "Trade", "Sequence", true); builder.addOptionalField("Trade", "Last.TimeNanoPart", SerialFieldType.COMPACT_INT, "Trade", "TimeNanoPart", false); builder.addOptionalField("Trade", "Last.Exchange", SerialFieldType.UTF_CHAR, "Trade", "ExchangeCode", true); @@ -96,7 +96,7 @@ public void buildScheme(SchemeBuilder builder) { } for (char exchange : SystemProperties.getProperty("com.dxfeed.event.market.impl.Trade.exchanges", "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray()) { String recordName = "Trade&" + exchange; - builder.addOptionalField(recordName, "Last.Time", SerialFieldType.TIME, "Trade", "Time", true); + builder.addOptionalField(recordName, "Last.Time", SerialFieldType.TIME_SECONDS, "Trade", "Time", true); builder.addOptionalField(recordName, "Last.Sequence", SerialFieldType.SEQUENCE, "Trade", "Sequence", true); builder.addOptionalField(recordName, "Last.TimeNanoPart", SerialFieldType.COMPACT_INT, "Trade", "TimeNanoPart", false); builder.addRequiredField(recordName, "Last.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); @@ -108,7 +108,7 @@ public void buildScheme(SchemeBuilder builder) { builder.addOptionalField(recordName, "Last.Flags", SerialFieldType.COMPACT_INT, "Trade", "Flags", true); } - builder.addOptionalField("TradeETH", "ETHLast.Time", SerialFieldType.TIME, "TradeETH", "Time", true); + builder.addOptionalField("TradeETH", "ETHLast.Time", SerialFieldType.TIME_SECONDS, "TradeETH", "Time", true); builder.addOptionalField("TradeETH", "ETHLast.Sequence", SerialFieldType.SEQUENCE, "TradeETH", "Sequence", true); builder.addOptionalField("TradeETH", "Last.TimeNanoPart", SerialFieldType.COMPACT_INT, "TradeETH", "TimeNanoPart", false); builder.addOptionalField("TradeETH", "ETHLast.Exchange", SerialFieldType.UTF_CHAR, "TradeETH", "ExchangeCode", true); @@ -120,7 +120,7 @@ public void buildScheme(SchemeBuilder builder) { builder.addRequiredField("TradeETH", "ETHLast.Flags", SerialFieldType.COMPACT_INT); for (char exchange : SystemProperties.getProperty("com.dxfeed.event.market.impl.TradeETH.exchanges", "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray()) { String recordName = "TradeETH&" + exchange; - builder.addOptionalField(recordName, "ETHLast.Time", SerialFieldType.TIME, "TradeETH", "Time", true); + builder.addOptionalField(recordName, "ETHLast.Time", SerialFieldType.TIME_SECONDS, "TradeETH", "Time", true); builder.addOptionalField(recordName, "ETHLast.Sequence", SerialFieldType.SEQUENCE, "TradeETH", "Sequence", true); builder.addOptionalField(recordName, "Last.TimeNanoPart", SerialFieldType.COMPACT_INT, "TradeETH", "TimeNanoPart", false); builder.addRequiredField(recordName, "ETHLast.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); @@ -171,7 +171,7 @@ public void buildScheme(SchemeBuilder builder) { String recordName = "Book&" + exchange; builder.addRequiredField(recordName, "ID", SerialFieldType.COMPACT_INT, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField(recordName, "Sequence", SerialFieldType.VOID, SchemeFieldTime.SECOND_TIME_INT_FIELD); - builder.addRequiredField(recordName, "Time", SerialFieldType.TIME); + builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS); builder.addRequiredField(recordName, "Type", SerialFieldType.UTF_CHAR); builder.addRequiredField(recordName, "Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField(recordName, "Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); @@ -190,25 +190,41 @@ public void buildScheme(SchemeBuilder builder) { builder.addOptionalField("Profile", "FreeFloat", select(SerialFieldType.DECIMAL), "Profile", "FreeFloat", true); builder.addOptionalField("Profile", "HighLimitPrice", select(SerialFieldType.DECIMAL, "dxscheme.price"), "Profile", "HighLimitPrice", true); builder.addOptionalField("Profile", "LowLimitPrice", select(SerialFieldType.DECIMAL, "dxscheme.price"), "Profile", "LowLimitPrice", true); - builder.addOptionalField("Profile", "Halt.StartTime", SerialFieldType.TIME, "Profile", "HaltStartTime", true); - builder.addOptionalField("Profile", "Halt.EndTime", SerialFieldType.TIME, "Profile", "HaltEndTime", true); + builder.addOptionalField("Profile", "Halt.StartTime", SerialFieldType.TIME_SECONDS, "Profile", "HaltStartTime", true); + builder.addOptionalField("Profile", "Halt.EndTime", SerialFieldType.TIME_SECONDS, "Profile", "HaltEndTime", true); builder.addOptionalField("Profile", "Flags", SerialFieldType.COMPACT_INT, "Profile", "Flags", true); builder.addRequiredField("Profile", "Description", SerialFieldType.UTF_CHAR_ARRAY); builder.addOptionalField("Profile", "StatusReason", SerialFieldType.UTF_CHAR_ARRAY, "Profile", "StatusReason", true); - for (String suffix : SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.suffixes", "|#NTV|#ntv|#NFX|#ESPD|#XNFI|#ICE|#ISE|#DEA|#DEX|#BYX|#BZX|#BATE|#CHIX|#CEUX|#BXTR|#IST|#BI20|#ABE|#FAIR|#GLBX|#glbx|#ERIS|#XEUR|#xeur|#CFE|#SMFE").split("\\|")) { + for (String suffix : SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.suffixes", "|#NTV|#ntv|#NFX|#ESPD|#XNFI|#ICE|#ISE|#DEA|#DEX|#BYX|#BZX|#BATE|#CHIX|#CEUX|#BXTR|#IST|#BI20|#ABE|#FAIR|#GLBX|#glbx|#ERIS|#XEUR|#xeur|#CFE|#C2OX|#SMFE").split("\\|")) { String recordName = "Order" + suffix; builder.addRequiredField(recordName, "Void", SerialFieldType.VOID, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField(recordName, "Index", SerialFieldType.COMPACT_INT, SchemeFieldTime.SECOND_TIME_INT_FIELD); - builder.addRequiredField(recordName, "Time", SerialFieldType.TIME); + builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS); builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE); builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "Order", "TimeNanoPart", false); + if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) { + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "ActionTime", SerialFieldType.TIME_MILLIS, "Order", "ActionTime", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "OrderId", SerialFieldType.LONG, "Order", "OrderId", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "AuxOrderId", SerialFieldType.LONG, "Order", "AuxOrderId", true); + } builder.addRequiredField(recordName, "Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField(recordName, "Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); builder.addOptionalField(recordName, "ExecutedSize", select(SerialFieldType.DECIMAL), "Order", "ExecutedSize", false); if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.Order.suffixes.count", ""))) builder.addOptionalField(recordName, "Count", select(SerialFieldType.COMPACT_INT), "Order", "Count", true); builder.addRequiredField(recordName, "Flags", SerialFieldType.COMPACT_INT); + if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) { + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "TradeId", SerialFieldType.LONG, "Order", "TradeId", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "TradePrice", select(SerialFieldType.DECIMAL), "Order", "TradePrice", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "TradeSize", select(SerialFieldType.DECIMAL), "Order", "TradeSize", true); + } if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.Order.suffixes.mmid", "|#NTV|#BATE|#CHIX|#CEUX|#BXTR"))) builder.addOptionalField(recordName, "MMID", SerialFieldType.SHORT_STRING, "Order", "MarketMaker", true); builder.addOptionalField(recordName, "IcebergPeakSize", select(SerialFieldType.DECIMAL), "Order", "IcebergPeakSize", false); @@ -221,15 +237,31 @@ public void buildScheme(SchemeBuilder builder) { String recordName = "AnalyticOrder" + suffix; builder.addRequiredField(recordName, "Void", SerialFieldType.VOID, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField(recordName, "Index", SerialFieldType.COMPACT_INT, SchemeFieldTime.SECOND_TIME_INT_FIELD); - builder.addRequiredField(recordName, "Time", SerialFieldType.TIME); + builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS); builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE); builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "AnalyticOrder", "TimeNanoPart", false); + if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) { + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "ActionTime", SerialFieldType.TIME_MILLIS, "AnalyticOrder", "ActionTime", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "OrderId", SerialFieldType.LONG, "AnalyticOrder", "OrderId", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "AuxOrderId", SerialFieldType.LONG, "AnalyticOrder", "AuxOrderId", true); + } builder.addRequiredField(recordName, "Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField(recordName, "Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); builder.addOptionalField(recordName, "ExecutedSize", select(SerialFieldType.DECIMAL), "AnalyticOrder", "ExecutedSize", false); if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.AnalyticOrder.suffixes.count", ""))) builder.addOptionalField(recordName, "Count", select(SerialFieldType.COMPACT_INT), "AnalyticOrder", "Count", true); builder.addRequiredField(recordName, "Flags", SerialFieldType.COMPACT_INT); + if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) { + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "TradeId", SerialFieldType.LONG, "AnalyticOrder", "TradeId", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "TradePrice", select(SerialFieldType.DECIMAL), "AnalyticOrder", "TradePrice", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "TradeSize", select(SerialFieldType.DECIMAL), "AnalyticOrder", "TradeSize", true); + } if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.AnalyticOrder.suffixes.mmid", "|#NTV|#BATE|#CHIX|#CEUX|#BXTR"))) builder.addOptionalField(recordName, "MMID", SerialFieldType.SHORT_STRING, "AnalyticOrder", "MarketMaker", true); builder.addOptionalField(recordName, "IcebergPeakSize", select(SerialFieldType.DECIMAL), "AnalyticOrder", "IcebergPeakSize", false); @@ -242,30 +274,46 @@ public void buildScheme(SchemeBuilder builder) { String recordName = "SpreadOrder" + suffix; builder.addRequiredField(recordName, "Void", SerialFieldType.VOID, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField(recordName, "Index", SerialFieldType.COMPACT_INT, SchemeFieldTime.SECOND_TIME_INT_FIELD); - builder.addRequiredField(recordName, "Time", SerialFieldType.TIME); + builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS); builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE); builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "SpreadOrder", "TimeNanoPart", false); + if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) { + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "ActionTime", SerialFieldType.TIME_MILLIS, "SpreadOrder", "ActionTime", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "OrderId", SerialFieldType.LONG, "SpreadOrder", "OrderId", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "AuxOrderId", SerialFieldType.LONG, "SpreadOrder", "AuxOrderId", true); + } builder.addRequiredField(recordName, "Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField(recordName, "Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); builder.addOptionalField(recordName, "ExecutedSize", select(SerialFieldType.DECIMAL), "SpreadOrder", "ExecutedSize", false); if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.SpreadOrder.suffixes.count", ""))) builder.addOptionalField(recordName, "Count", select(SerialFieldType.COMPACT_INT), "SpreadOrder", "Count", true); builder.addRequiredField(recordName, "Flags", SerialFieldType.COMPACT_INT); + if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) { + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "TradeId", SerialFieldType.LONG, "SpreadOrder", "TradeId", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "TradePrice", select(SerialFieldType.DECIMAL), "SpreadOrder", "TradePrice", true); + if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV"))) + builder.addOptionalField(recordName, "TradeSize", select(SerialFieldType.DECIMAL), "SpreadOrder", "TradeSize", true); + } builder.addRequiredField(recordName, "SpreadSymbol", SerialFieldType.UTF_CHAR_ARRAY); } builder.addRequiredField("MarketMaker", "MMExchange", SerialFieldType.UTF_CHAR, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField("MarketMaker", "MMID", SerialFieldType.SHORT_STRING, SchemeFieldTime.SECOND_TIME_INT_FIELD); - builder.addOptionalField("MarketMaker", "MMBid.Time", SerialFieldType.TIME, "Order", "BidTime", true); + builder.addOptionalField("MarketMaker", "MMBid.Time", SerialFieldType.TIME_SECONDS, "Order", "BidTime", true); builder.addRequiredField("MarketMaker", "MMBid.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField("MarketMaker", "MMBid.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); builder.addOptionalField("MarketMaker", "MMBid.Count", select(SerialFieldType.COMPACT_INT), "Order", "BidCount", true); - builder.addOptionalField("MarketMaker", "MMAsk.Time", SerialFieldType.TIME, "Order", "AskTime", true); + builder.addOptionalField("MarketMaker", "MMAsk.Time", SerialFieldType.TIME_SECONDS, "Order", "AskTime", true); builder.addRequiredField("MarketMaker", "MMAsk.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField("MarketMaker", "MMAsk.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size")); builder.addOptionalField("MarketMaker", "MMAsk.Count", select(SerialFieldType.COMPACT_INT), "Order", "AskCount", true); - builder.addRequiredField("TimeAndSale", "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD); + builder.addRequiredField("TimeAndSale", "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField("TimeAndSale", "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD); builder.addOptionalField("TimeAndSale", "TimeNanoPart", SerialFieldType.COMPACT_INT, "TimeAndSale", "TimeNanoPart", false); builder.addRequiredField("TimeAndSale", "Exchange", SerialFieldType.UTF_CHAR); @@ -279,7 +327,7 @@ public void buildScheme(SchemeBuilder builder) { builder.addOptionalField("TimeAndSale", "Seller", SerialFieldType.UTF_CHAR_ARRAY, "TimeAndSale", "Seller", false); for (char exchange : SystemProperties.getProperty("com.dxfeed.event.market.impl.TimeAndSale.exchanges", "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray()) { String recordName = "TimeAndSale&" + exchange; - builder.addRequiredField(recordName, "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD); + builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD); builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "TimeAndSale", "TimeNanoPart", false); builder.addRequiredField(recordName, "Exchange", SerialFieldType.UTF_CHAR); diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/OrderDelegate.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/OrderDelegate.java index 76f5f9257..ccca49dcb 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/OrderDelegate.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/OrderDelegate.java @@ -47,11 +47,17 @@ public Order getEvent(Order event, RecordCursor cursor) { event.setIndex(((long) getSource().id() << 32) | (m.getIndex(cursor) & 0xFFFFFFFFL)); event.setTimeSequence((((long) m.getTimeSeconds(cursor)) << 32) | (m.getSequence(cursor) & 0xFFFFFFFFL)); event.setTimeNanoPart(m.getTimeNanoPart(cursor)); + event.setActionTime(m.getActionTimeMillis(cursor)); + event.setOrderId(m.getOrderId(cursor)); + event.setAuxOrderId(m.getAuxOrderId(cursor)); event.setPrice(m.getPrice(cursor)); event.setSizeAsDouble(m.getSizeDouble(cursor)); event.setExecutedSize(m.getExecutedSize(cursor)); event.setCount(m.getCount(cursor)); event.setFlags(m.getFlags(cursor)); + event.setTradeId(m.getTradeId(cursor)); + event.setTradePrice(m.getTradePrice(cursor)); + event.setTradeSize(m.getTradeSize(cursor)); event.setMarketMaker(m.getMarketMakerString(cursor)); return event; } @@ -65,11 +71,17 @@ public RecordCursor putEvent(Order event, RecordBuffer buf) { m.setTimeSeconds(cursor, (int) (event.getTimeSequence() >>> 32)); m.setSequence(cursor, (int) event.getTimeSequence()); m.setTimeNanoPart(cursor, event.getTimeNanoPart()); + m.setActionTimeMillis(cursor, event.getActionTime()); + m.setOrderId(cursor, event.getOrderId()); + m.setAuxOrderId(cursor, event.getAuxOrderId()); m.setPrice(cursor, event.getPrice()); m.setSizeDouble(cursor, event.getSizeAsDouble()); m.setExecutedSize(cursor, event.getExecutedSize()); m.setCount(cursor, (int) event.getCount()); m.setFlags(cursor, event.getFlags()); + m.setTradeId(cursor, event.getTradeId()); + m.setTradePrice(cursor, event.getTradePrice()); + m.setTradeSize(cursor, event.getTradeSize()); m.setMarketMakerString(cursor, event.getMarketMaker()); if (index < 0) throw new IllegalArgumentException("Invalid index to publish"); diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/SpreadOrderDelegate.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/SpreadOrderDelegate.java index 7bb8768bf..58a6b9b32 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/SpreadOrderDelegate.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/SpreadOrderDelegate.java @@ -47,11 +47,17 @@ public SpreadOrder getEvent(SpreadOrder event, RecordCursor cursor) { event.setIndex(((long) getSource().id() << 32) | (m.getIndex(cursor) & 0xFFFFFFFFL)); event.setTimeSequence((((long) m.getTimeSeconds(cursor)) << 32) | (m.getSequence(cursor) & 0xFFFFFFFFL)); event.setTimeNanoPart(m.getTimeNanoPart(cursor)); + event.setActionTime(m.getActionTimeMillis(cursor)); + event.setOrderId(m.getOrderId(cursor)); + event.setAuxOrderId(m.getAuxOrderId(cursor)); event.setPrice(m.getPrice(cursor)); event.setSizeAsDouble(m.getSizeDouble(cursor)); event.setExecutedSize(m.getExecutedSize(cursor)); event.setCount(m.getCount(cursor)); event.setFlags(m.getFlags(cursor)); + event.setTradeId(m.getTradeId(cursor)); + event.setTradePrice(m.getTradePrice(cursor)); + event.setTradeSize(m.getTradeSize(cursor)); event.setSpreadSymbol(m.getSpreadSymbol(cursor)); return event; } @@ -65,11 +71,17 @@ public RecordCursor putEvent(SpreadOrder event, RecordBuffer buf) { m.setTimeSeconds(cursor, (int) (event.getTimeSequence() >>> 32)); m.setSequence(cursor, (int) event.getTimeSequence()); m.setTimeNanoPart(cursor, event.getTimeNanoPart()); + m.setActionTimeMillis(cursor, event.getActionTime()); + m.setOrderId(cursor, event.getOrderId()); + m.setAuxOrderId(cursor, event.getAuxOrderId()); m.setPrice(cursor, event.getPrice()); m.setSizeDouble(cursor, event.getSizeAsDouble()); m.setExecutedSize(cursor, event.getExecutedSize()); m.setCount(cursor, (int) event.getCount()); m.setFlags(cursor, event.getFlags()); + m.setTradeId(cursor, event.getTradeId()); + m.setTradePrice(cursor, event.getTradePrice()); + m.setTradeSize(cursor, event.getTradeSize()); m.setSpreadSymbol(cursor, event.getSpreadSymbol()); if (index < 0) throw new IllegalArgumentException("Invalid index to publish"); diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/AnalyticOrderMapping.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/AnalyticOrderMapping.java index 4ebeebb6a..665f245dd 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/AnalyticOrderMapping.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/AnalyticOrderMapping.java @@ -23,11 +23,17 @@ public class AnalyticOrderMapping extends OrderBaseMapping { private final int iTime; private final int iSequence; private final int iTimeNanoPart; + private final int iActionTime; + private final int iOrderId; + private final int iAuxOrderId; private final int iPrice; private final int iSize; private final int iExecutedSize; private final int iCount; private final int iFlags; + private final int iTradeId; + private final int iTradePrice; + private final int iTradeSize; private final int iMarketMaker; private final int iIcebergPeakSize; private final int iIcebergHiddenSize; @@ -40,11 +46,17 @@ public AnalyticOrderMapping(DataRecord record) { iTime = MappingUtil.findIntField(record, "Time", true); iSequence = MappingUtil.findIntField(record, "Sequence", true); iTimeNanoPart = MappingUtil.findIntField(record, "TimeNanoPart", false); + iActionTime = MappingUtil.findIntField(record, "ActionTime", false); + iOrderId = MappingUtil.findIntField(record, "OrderId", false); + iAuxOrderId = MappingUtil.findIntField(record, "AuxOrderId", false); iPrice = findIntField("Price", true); iSize = findIntField("Size", true); iExecutedSize = findIntField("ExecutedSize", false); iCount = findIntField("Count", false); iFlags = MappingUtil.findIntField(record, "Flags", true); + iTradeId = MappingUtil.findIntField(record, "TradeId", false); + iTradePrice = findIntField("TradePrice", false); + iTradeSize = findIntField("TradeSize", false); iMarketMaker = MappingUtil.findIntField(record, "MMID", false); iIcebergPeakSize = findIntField("IcebergPeakSize", false); iIcebergHiddenSize = findIntField("IcebergHiddenSize", false); @@ -97,6 +109,54 @@ public void setTimeNanoPart(RecordCursor cursor, int timeNanoPart) { setInt(cursor, iTimeNanoPart, timeNanoPart); } + public long getActionTimeMillis(RecordCursor cursor) { + if (iActionTime < 0) + return 0; + return getLong(cursor, iActionTime); + } + + public void setActionTimeMillis(RecordCursor cursor, long actionTime) { + if (iActionTime < 0) + return; + setLong(cursor, iActionTime, actionTime); + } + + public int getActionTimeSeconds(RecordCursor cursor) { + if (iActionTime < 0) + return 0; + return TimeUtil.getSecondsFromTime(getLong(cursor, iActionTime)); + } + + public void setActionTimeSeconds(RecordCursor cursor, int actionTime) { + if (iActionTime < 0) + return; + setLong(cursor, iActionTime, actionTime * 1000L); + } + + public long getOrderId(RecordCursor cursor) { + if (iOrderId < 0) + return 0; + return getLong(cursor, iOrderId); + } + + public void setOrderId(RecordCursor cursor, long orderId) { + if (iOrderId < 0) + return; + setLong(cursor, iOrderId, orderId); + } + + public long getAuxOrderId(RecordCursor cursor) { + if (iAuxOrderId < 0) + return 0; + return getLong(cursor, iAuxOrderId); + } + + public void setAuxOrderId(RecordCursor cursor, long auxOrderId) { + if (iAuxOrderId < 0) + return; + setLong(cursor, iAuxOrderId, auxOrderId); + } + public double getPrice(RecordCursor cursor) { return getAsDouble(cursor, iPrice); } @@ -265,6 +325,90 @@ public void setFlags(RecordCursor cursor, int flags) { setInt(cursor, iFlags, flags); } + public long getTradeId(RecordCursor cursor) { + if (iTradeId < 0) + return 0; + return getLong(cursor, iTradeId); + } + + public void setTradeId(RecordCursor cursor, long tradeId) { + if (iTradeId < 0) + return; + setLong(cursor, iTradeId, tradeId); + } + + public double getTradePrice(RecordCursor cursor) { + if (iTradePrice < 0) + return Double.NaN; + return getAsDouble(cursor, iTradePrice); + } + + public void setTradePrice(RecordCursor cursor, double tradePrice) { + if (iTradePrice < 0) + return; + setAsDouble(cursor, iTradePrice, tradePrice); + } + + public int getTradePriceDecimal(RecordCursor cursor) { + if (iTradePrice < 0) + return 0; + return getAsTinyDecimal(cursor, iTradePrice); + } + + public void setTradePriceDecimal(RecordCursor cursor, int tradePrice) { + if (iTradePrice < 0) + return; + setAsTinyDecimal(cursor, iTradePrice, tradePrice); + } + + public long getTradePriceWideDecimal(RecordCursor cursor) { + if (iTradePrice < 0) + return 0; + return getAsWideDecimal(cursor, iTradePrice); + } + + public void setTradePriceWideDecimal(RecordCursor cursor, long tradePrice) { + if (iTradePrice < 0) + return; + setAsWideDecimal(cursor, iTradePrice, tradePrice); + } + + public double getTradeSize(RecordCursor cursor) { + if (iTradeSize < 0) + return Double.NaN; + return getAsDouble(cursor, iTradeSize); + } + + public void setTradeSize(RecordCursor cursor, double tradeSize) { + if (iTradeSize < 0) + return; + setAsDouble(cursor, iTradeSize, tradeSize); + } + + public int getTradeSizeDecimal(RecordCursor cursor) { + if (iTradeSize < 0) + return 0; + return getAsTinyDecimal(cursor, iTradeSize); + } + + public void setTradeSizeDecimal(RecordCursor cursor, int tradeSize) { + if (iTradeSize < 0) + return; + setAsTinyDecimal(cursor, iTradeSize, tradeSize); + } + + public long getTradeSizeWideDecimal(RecordCursor cursor) { + if (iTradeSize < 0) + return 0; + return getAsWideDecimal(cursor, iTradeSize); + } + + public void setTradeSizeWideDecimal(RecordCursor cursor, long tradeSize) { + if (iTradeSize < 0) + return; + setAsWideDecimal(cursor, iTradeSize, tradeSize); + } + @Deprecated public String getMMIDString(RecordCursor cursor) { if (iMarketMaker < 0) diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/OrderMapping.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/OrderMapping.java index 570563f16..6f32178e5 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/OrderMapping.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/OrderMapping.java @@ -23,11 +23,17 @@ public class OrderMapping extends OrderBaseMapping { private final int iTime; private final int iSequence; private final int iTimeNanoPart; + private final int iActionTime; + private final int iOrderId; + private final int iAuxOrderId; private final int iPrice; private final int iSize; private final int iExecutedSize; private final int iCount; private final int iFlags; + private final int iTradeId; + private final int iTradePrice; + private final int iTradeSize; private final int iMarketMaker; private final int iIcebergPeakSize; private final int iIcebergHiddenSize; @@ -40,11 +46,17 @@ public OrderMapping(DataRecord record) { iTime = MappingUtil.findIntField(record, "Time", true); iSequence = MappingUtil.findIntField(record, "Sequence", true); iTimeNanoPart = MappingUtil.findIntField(record, "TimeNanoPart", false); + iActionTime = MappingUtil.findIntField(record, "ActionTime", false); + iOrderId = MappingUtil.findIntField(record, "OrderId", false); + iAuxOrderId = MappingUtil.findIntField(record, "AuxOrderId", false); iPrice = findIntField("Price", true); iSize = findIntField("Size", true); iExecutedSize = findIntField("ExecutedSize", false); iCount = findIntField("Count", false); iFlags = MappingUtil.findIntField(record, "Flags", true); + iTradeId = MappingUtil.findIntField(record, "TradeId", false); + iTradePrice = findIntField("TradePrice", false); + iTradeSize = findIntField("TradeSize", false); iMarketMaker = MappingUtil.findIntField(record, "MMID", false); iIcebergPeakSize = findIntField("IcebergPeakSize", false); iIcebergHiddenSize = findIntField("IcebergHiddenSize", false); @@ -97,6 +109,54 @@ public void setTimeNanoPart(RecordCursor cursor, int timeNanoPart) { setInt(cursor, iTimeNanoPart, timeNanoPart); } + public long getActionTimeMillis(RecordCursor cursor) { + if (iActionTime < 0) + return 0; + return getLong(cursor, iActionTime); + } + + public void setActionTimeMillis(RecordCursor cursor, long actionTime) { + if (iActionTime < 0) + return; + setLong(cursor, iActionTime, actionTime); + } + + public int getActionTimeSeconds(RecordCursor cursor) { + if (iActionTime < 0) + return 0; + return TimeUtil.getSecondsFromTime(getLong(cursor, iActionTime)); + } + + public void setActionTimeSeconds(RecordCursor cursor, int actionTime) { + if (iActionTime < 0) + return; + setLong(cursor, iActionTime, actionTime * 1000L); + } + + public long getOrderId(RecordCursor cursor) { + if (iOrderId < 0) + return 0; + return getLong(cursor, iOrderId); + } + + public void setOrderId(RecordCursor cursor, long orderId) { + if (iOrderId < 0) + return; + setLong(cursor, iOrderId, orderId); + } + + public long getAuxOrderId(RecordCursor cursor) { + if (iAuxOrderId < 0) + return 0; + return getLong(cursor, iAuxOrderId); + } + + public void setAuxOrderId(RecordCursor cursor, long auxOrderId) { + if (iAuxOrderId < 0) + return; + setLong(cursor, iAuxOrderId, auxOrderId); + } + public double getPrice(RecordCursor cursor) { return getAsDouble(cursor, iPrice); } @@ -265,6 +325,90 @@ public void setFlags(RecordCursor cursor, int flags) { setInt(cursor, iFlags, flags); } + public long getTradeId(RecordCursor cursor) { + if (iTradeId < 0) + return 0; + return getLong(cursor, iTradeId); + } + + public void setTradeId(RecordCursor cursor, long tradeId) { + if (iTradeId < 0) + return; + setLong(cursor, iTradeId, tradeId); + } + + public double getTradePrice(RecordCursor cursor) { + if (iTradePrice < 0) + return Double.NaN; + return getAsDouble(cursor, iTradePrice); + } + + public void setTradePrice(RecordCursor cursor, double tradePrice) { + if (iTradePrice < 0) + return; + setAsDouble(cursor, iTradePrice, tradePrice); + } + + public int getTradePriceDecimal(RecordCursor cursor) { + if (iTradePrice < 0) + return 0; + return getAsTinyDecimal(cursor, iTradePrice); + } + + public void setTradePriceDecimal(RecordCursor cursor, int tradePrice) { + if (iTradePrice < 0) + return; + setAsTinyDecimal(cursor, iTradePrice, tradePrice); + } + + public long getTradePriceWideDecimal(RecordCursor cursor) { + if (iTradePrice < 0) + return 0; + return getAsWideDecimal(cursor, iTradePrice); + } + + public void setTradePriceWideDecimal(RecordCursor cursor, long tradePrice) { + if (iTradePrice < 0) + return; + setAsWideDecimal(cursor, iTradePrice, tradePrice); + } + + public double getTradeSize(RecordCursor cursor) { + if (iTradeSize < 0) + return Double.NaN; + return getAsDouble(cursor, iTradeSize); + } + + public void setTradeSize(RecordCursor cursor, double tradeSize) { + if (iTradeSize < 0) + return; + setAsDouble(cursor, iTradeSize, tradeSize); + } + + public int getTradeSizeDecimal(RecordCursor cursor) { + if (iTradeSize < 0) + return 0; + return getAsTinyDecimal(cursor, iTradeSize); + } + + public void setTradeSizeDecimal(RecordCursor cursor, int tradeSize) { + if (iTradeSize < 0) + return; + setAsTinyDecimal(cursor, iTradeSize, tradeSize); + } + + public long getTradeSizeWideDecimal(RecordCursor cursor) { + if (iTradeSize < 0) + return 0; + return getAsWideDecimal(cursor, iTradeSize); + } + + public void setTradeSizeWideDecimal(RecordCursor cursor, long tradeSize) { + if (iTradeSize < 0) + return; + setAsWideDecimal(cursor, iTradeSize, tradeSize); + } + @Deprecated public String getMMIDString(RecordCursor cursor) { if (iMarketMaker < 0) diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/SpreadOrderMapping.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/SpreadOrderMapping.java index 5b955cd11..fc289f3f6 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/SpreadOrderMapping.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/SpreadOrderMapping.java @@ -22,11 +22,17 @@ public class SpreadOrderMapping extends OrderBaseMapping { private final int iTime; private final int iSequence; private final int iTimeNanoPart; + private final int iActionTime; + private final int iOrderId; + private final int iAuxOrderId; private final int iPrice; private final int iSize; private final int iExecutedSize; private final int iCount; private final int iFlags; + private final int iTradeId; + private final int iTradePrice; + private final int iTradeSize; private final int oSpreadSymbol; public SpreadOrderMapping(DataRecord record) { @@ -35,11 +41,17 @@ public SpreadOrderMapping(DataRecord record) { iTime = MappingUtil.findIntField(record, "Time", true); iSequence = MappingUtil.findIntField(record, "Sequence", true); iTimeNanoPart = MappingUtil.findIntField(record, "TimeNanoPart", false); + iActionTime = MappingUtil.findIntField(record, "ActionTime", false); + iOrderId = MappingUtil.findIntField(record, "OrderId", false); + iAuxOrderId = MappingUtil.findIntField(record, "AuxOrderId", false); iPrice = findIntField("Price", true); iSize = findIntField("Size", true); iExecutedSize = findIntField("ExecutedSize", false); iCount = findIntField("Count", false); iFlags = MappingUtil.findIntField(record, "Flags", true); + iTradeId = MappingUtil.findIntField(record, "TradeId", false); + iTradePrice = findIntField("TradePrice", false); + iTradeSize = findIntField("TradeSize", false); oSpreadSymbol = MappingUtil.findObjField(record, "SpreadSymbol", true); } @@ -87,6 +99,54 @@ public void setTimeNanoPart(RecordCursor cursor, int timeNanoPart) { setInt(cursor, iTimeNanoPart, timeNanoPart); } + public long getActionTimeMillis(RecordCursor cursor) { + if (iActionTime < 0) + return 0; + return getLong(cursor, iActionTime); + } + + public void setActionTimeMillis(RecordCursor cursor, long actionTime) { + if (iActionTime < 0) + return; + setLong(cursor, iActionTime, actionTime); + } + + public int getActionTimeSeconds(RecordCursor cursor) { + if (iActionTime < 0) + return 0; + return TimeUtil.getSecondsFromTime(getLong(cursor, iActionTime)); + } + + public void setActionTimeSeconds(RecordCursor cursor, int actionTime) { + if (iActionTime < 0) + return; + setLong(cursor, iActionTime, actionTime * 1000L); + } + + public long getOrderId(RecordCursor cursor) { + if (iOrderId < 0) + return 0; + return getLong(cursor, iOrderId); + } + + public void setOrderId(RecordCursor cursor, long orderId) { + if (iOrderId < 0) + return; + setLong(cursor, iOrderId, orderId); + } + + public long getAuxOrderId(RecordCursor cursor) { + if (iAuxOrderId < 0) + return 0; + return getLong(cursor, iAuxOrderId); + } + + public void setAuxOrderId(RecordCursor cursor, long auxOrderId) { + if (iAuxOrderId < 0) + return; + setLong(cursor, iAuxOrderId, auxOrderId); + } + public double getPrice(RecordCursor cursor) { return getAsDouble(cursor, iPrice); } @@ -255,6 +315,90 @@ public void setFlags(RecordCursor cursor, int flags) { setInt(cursor, iFlags, flags); } + public long getTradeId(RecordCursor cursor) { + if (iTradeId < 0) + return 0; + return getLong(cursor, iTradeId); + } + + public void setTradeId(RecordCursor cursor, long tradeId) { + if (iTradeId < 0) + return; + setLong(cursor, iTradeId, tradeId); + } + + public double getTradePrice(RecordCursor cursor) { + if (iTradePrice < 0) + return Double.NaN; + return getAsDouble(cursor, iTradePrice); + } + + public void setTradePrice(RecordCursor cursor, double tradePrice) { + if (iTradePrice < 0) + return; + setAsDouble(cursor, iTradePrice, tradePrice); + } + + public int getTradePriceDecimal(RecordCursor cursor) { + if (iTradePrice < 0) + return 0; + return getAsTinyDecimal(cursor, iTradePrice); + } + + public void setTradePriceDecimal(RecordCursor cursor, int tradePrice) { + if (iTradePrice < 0) + return; + setAsTinyDecimal(cursor, iTradePrice, tradePrice); + } + + public long getTradePriceWideDecimal(RecordCursor cursor) { + if (iTradePrice < 0) + return 0; + return getAsWideDecimal(cursor, iTradePrice); + } + + public void setTradePriceWideDecimal(RecordCursor cursor, long tradePrice) { + if (iTradePrice < 0) + return; + setAsWideDecimal(cursor, iTradePrice, tradePrice); + } + + public double getTradeSize(RecordCursor cursor) { + if (iTradeSize < 0) + return Double.NaN; + return getAsDouble(cursor, iTradeSize); + } + + public void setTradeSize(RecordCursor cursor, double tradeSize) { + if (iTradeSize < 0) + return; + setAsDouble(cursor, iTradeSize, tradeSize); + } + + public int getTradeSizeDecimal(RecordCursor cursor) { + if (iTradeSize < 0) + return 0; + return getAsTinyDecimal(cursor, iTradeSize); + } + + public void setTradeSizeDecimal(RecordCursor cursor, int tradeSize) { + if (iTradeSize < 0) + return; + setAsTinyDecimal(cursor, iTradeSize, tradeSize); + } + + public long getTradeSizeWideDecimal(RecordCursor cursor) { + if (iTradeSize < 0) + return 0; + return getAsWideDecimal(cursor, iTradeSize); + } + + public void setTradeSizeWideDecimal(RecordCursor cursor, long tradeSize) { + if (iTradeSize < 0) + return; + setAsWideDecimal(cursor, iTradeSize, tradeSize); + } + public String getSpreadSymbol(RecordCursor cursor) { return (String) getObj(cursor, oSpreadSymbol); } diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/option/OptionFactoryImpl.java b/dxfeed-impl/src/main/java/com/dxfeed/event/option/OptionFactoryImpl.java index efb976246..918b65bb5 100644 --- a/dxfeed-impl/src/main/java/com/dxfeed/event/option/OptionFactoryImpl.java +++ b/dxfeed-impl/src/main/java/com/dxfeed/event/option/OptionFactoryImpl.java @@ -36,7 +36,7 @@ public final class OptionFactoryImpl extends EventDelegateFactory implements Rec // BEGIN: CODE AUTOMATICALLY GENERATED: DO NOT MODIFY. IT IS REGENERATED BY com.dxfeed.api.codegen.ImplCodeGen @Override public void buildScheme(SchemeBuilder builder) { - builder.addOptionalField("Greeks", "Time", SerialFieldType.TIME, "Greeks", "Time", true, SchemeFieldTime.FIRST_TIME_INT_FIELD); + builder.addOptionalField("Greeks", "Time", SerialFieldType.TIME_SECONDS, "Greeks", "Time", true, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addOptionalField("Greeks", "Sequence", SerialFieldType.SEQUENCE, "Greeks", "Sequence", true, SchemeFieldTime.SECOND_TIME_INT_FIELD); builder.addRequiredField("Greeks", "Greeks.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField("Greeks", "Volatility", select(SerialFieldType.DECIMAL)); @@ -46,7 +46,7 @@ public void buildScheme(SchemeBuilder builder) { builder.addRequiredField("Greeks", "Rho", select(SerialFieldType.DECIMAL)); builder.addRequiredField("Greeks", "Vega", select(SerialFieldType.DECIMAL)); - builder.addRequiredField("TheoPrice", "Theo.Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD); + builder.addRequiredField("TheoPrice", "Theo.Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addOptionalField("TheoPrice", "Theo.Sequence", SerialFieldType.SEQUENCE, "TheoPrice", "Sequence", true, SchemeFieldTime.SECOND_TIME_INT_FIELD); builder.addRequiredField("TheoPrice", "Theo.Price", select(SerialFieldType.DECIMAL, "dxscheme.price")); builder.addRequiredField("TheoPrice", "Theo.UnderlyingPrice", select(SerialFieldType.DECIMAL, "dxscheme.price")); @@ -55,7 +55,7 @@ public void buildScheme(SchemeBuilder builder) { builder.addOptionalField("TheoPrice", "Theo.Dividend", select(SerialFieldType.DECIMAL), "TheoPrice", "Dividend", true); builder.addOptionalField("TheoPrice", "Theo.Interest", select(SerialFieldType.DECIMAL), "TheoPrice", "Interest", true); - builder.addOptionalField("Underlying", "Time", SerialFieldType.TIME, "Underlying", "Time", true, SchemeFieldTime.FIRST_TIME_INT_FIELD); + builder.addOptionalField("Underlying", "Time", SerialFieldType.TIME_SECONDS, "Underlying", "Time", true, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addOptionalField("Underlying", "Sequence", SerialFieldType.SEQUENCE, "Underlying", "Sequence", true, SchemeFieldTime.SECOND_TIME_INT_FIELD); builder.addOptionalField("Underlying", "Volatility", select(SerialFieldType.DECIMAL), "Underlying", "Volatility", true); builder.addOptionalField("Underlying", "FrontVolatility", select(SerialFieldType.DECIMAL), "Underlying", "FrontVolatility", true); @@ -66,7 +66,7 @@ public void buildScheme(SchemeBuilder builder) { builder.addOptionalField("Series", "Void", SerialFieldType.VOID, "Series", "Void", true, SchemeFieldTime.FIRST_TIME_INT_FIELD); builder.addOptionalField("Series", "Index", SerialFieldType.COMPACT_INT, "Series", "Index", true, SchemeFieldTime.SECOND_TIME_INT_FIELD); - builder.addOptionalField("Series", "Time", SerialFieldType.TIME, "Series", "Time", true); + builder.addOptionalField("Series", "Time", SerialFieldType.TIME_SECONDS, "Series", "Time", true); builder.addOptionalField("Series", "Sequence", SerialFieldType.SEQUENCE, "Series", "Sequence", true); builder.addRequiredField("Series", "Expiration", SerialFieldType.DATE); builder.addRequiredField("Series", "Volatility", select(SerialFieldType.DECIMAL)); diff --git a/dxfeed-ipf-filter/pom.xml b/dxfeed-ipf-filter/pom.xml index d432d784c..dda7836af 100644 --- a/dxfeed-ipf-filter/pom.xml +++ b/dxfeed-ipf-filter/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/dxfeed-news/pom.xml b/dxfeed-news/pom.xml index 63a76c562..13bc359ae 100644 --- a/dxfeed-news/pom.xml +++ b/dxfeed-news/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/dxfeed-ondemand/pom.xml b/dxfeed-ondemand/pom.xml index 8944517d7..8ac930f76 100644 --- a/dxfeed-ondemand/pom.xml +++ b/dxfeed-ondemand/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/dxfeed-plotter/pom.xml b/dxfeed-plotter/pom.xml index 0740769a0..0f8a8ba64 100644 --- a/dxfeed-plotter/pom.xml +++ b/dxfeed-plotter/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/dxfeed-promise/pom.xml b/dxfeed-promise/pom.xml index 5edfbe225..60714a8b2 100644 --- a/dxfeed-promise/pom.xml +++ b/dxfeed-promise/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/dxfeed-samples/pom.xml b/dxfeed-samples/pom.xml index affb00ba6..2fd3d7e46 100644 --- a/dxfeed-samples/pom.xml +++ b/dxfeed-samples/pom.xml @@ -14,13 +14,13 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 dxfeed-samples - 3.291 + 3.292 jar diff --git a/dxfeed-tools/pom.xml b/dxfeed-tools/pom.xml index bf538b7a0..3ff01f36d 100644 --- a/dxfeed-tools/pom.xml +++ b/dxfeed-tools/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/dxfeed-viewer-installer/pom.xml b/dxfeed-viewer-installer/pom.xml index 1f7ff3418..f376830c2 100644 --- a/dxfeed-viewer-installer/pom.xml +++ b/dxfeed-viewer-installer/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/dxfeed-viewer/pom.xml b/dxfeed-viewer/pom.xml index 701933dee..9faaa8a3d 100644 --- a/dxfeed-viewer/pom.xml +++ b/dxfeed-viewer/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/dxfeed-webservice-impl/pom.xml b/dxfeed-webservice-impl/pom.xml index 76831d189..20b5ae3a3 100644 --- a/dxfeed-webservice-impl/pom.xml +++ b/dxfeed-webservice-impl/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/dxfeed-webservice/pom.xml b/dxfeed-webservice/pom.xml index 45f2c06a9..bfc03f21f 100644 --- a/dxfeed-webservice/pom.xml +++ b/dxfeed-webservice/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/dxlib-benchmark/pom.xml b/dxlib-benchmark/pom.xml index 32bbecc6b..bd9b37706 100644 --- a/dxlib-benchmark/pom.xml +++ b/dxlib-benchmark/pom.xml @@ -2,7 +2,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/dxlib-qd-sandbox/pom.xml b/dxlib-qd-sandbox/pom.xml index 143f8d4dc..a8b11d723 100644 --- a/dxlib-qd-sandbox/pom.xml +++ b/dxlib-qd-sandbox/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/dxlib/pom.xml b/dxlib/pom.xml index 4ffdaef8b..56dba8177 100644 --- a/dxlib/pom.xml +++ b/dxlib/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/license/pom.xml b/license/pom.xml index ea1023d6b..42b2981b0 100644 --- a/license/pom.xml +++ b/license/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/mars-sample/pom.xml b/mars-sample/pom.xml index 631ce2a51..82449e3ff 100644 --- a/mars-sample/pom.xml +++ b/mars-sample/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/mars/pom.xml b/mars/pom.xml index 4a6df4ada..a5b1ff921 100644 --- a/mars/pom.xml +++ b/mars/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index 4f2e3f9c6..02d24ecec 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ com.devexperts.qd QD pom - 3.291 + 3.292 2002 diff --git a/proto-sample/pom.xml b/proto-sample/pom.xml index 305960f40..a7dffb469 100644 --- a/proto-sample/pom.xml +++ b/proto-sample/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/proto-ssl/pom.xml b/proto-ssl/pom.xml index bb7d94532..f4fcacf82 100644 --- a/proto-ssl/pom.xml +++ b/proto-ssl/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/proto/pom.xml b/proto/pom.xml index 6f965203c..12491fbc8 100644 --- a/proto/pom.xml +++ b/proto/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qd-core/pom.xml b/qd-core/pom.xml index 0e1cb9939..d1ebe08c1 100644 --- a/qd-core/pom.xml +++ b/qd-core/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qd-core/src/main/java/com/devexperts/qd/SerialFieldType.java b/qd-core/src/main/java/com/devexperts/qd/SerialFieldType.java index 414588231..06be347e0 100644 --- a/qd-core/src/main/java/com/devexperts/qd/SerialFieldType.java +++ b/qd-core/src/main/java/com/devexperts/qd/SerialFieldType.java @@ -16,23 +16,28 @@ import com.devexperts.qd.kit.CompactIntField; import com.devexperts.qd.kit.DateField; import com.devexperts.qd.kit.DecimalField; +import com.devexperts.qd.kit.LongField; import com.devexperts.qd.kit.MarshalledObjField; import com.devexperts.qd.kit.PlainIntField; import com.devexperts.qd.kit.SequenceField; import com.devexperts.qd.kit.ShortStringField; import com.devexperts.qd.kit.StringField; -import com.devexperts.qd.kit.TimeField; +import com.devexperts.qd.kit.TimeSecondsField; +import com.devexperts.qd.kit.TimeMillisField; import com.devexperts.qd.kit.VoidIntField; import com.devexperts.qd.kit.WideDecimalField; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_CUSTOM_OBJECT; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_DATE; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_DECIMAL; +import static com.devexperts.qd.SerialFieldType.Bits.FLAG_LONG; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_SEQUENCE; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_SERIAL_OBJECT; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_SHORT_STRING; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_STRING; -import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME; +import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_MILLIS; +import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_NANOS; +import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_SECONDS; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_WIDE_DECIMAL; import static com.devexperts.qd.SerialFieldType.Bits.ID_BYTE; import static com.devexperts.qd.SerialFieldType.Bits.ID_BYTE_ARRAY; @@ -63,9 +68,15 @@ public final class SerialFieldType { public static final SerialFieldType DECIMAL = new SerialFieldType(ID_COMPACT_INT | FLAG_DECIMAL, "DECIMAL"); public static final SerialFieldType SHORT_STRING = new SerialFieldType(ID_COMPACT_INT | FLAG_SHORT_STRING, "SHORT_STRING"); - public static final SerialFieldType TIME = new SerialFieldType(ID_COMPACT_INT | FLAG_TIME, "TIME"); + public static final SerialFieldType TIME_SECONDS = new SerialFieldType(ID_COMPACT_INT | FLAG_TIME_SECONDS, "TIME_SECONDS"); + public static final SerialFieldType TIME_MILLIS = new SerialFieldType(ID_COMPACT_INT | FLAG_TIME_MILLIS, "TIME_MILLIS"); + public static final SerialFieldType TIME_NANOS = new SerialFieldType(ID_COMPACT_INT | FLAG_TIME_NANOS, "TIME_NANOS"); // Reserved + /** @deprecated Use {@link #TIME_SECONDS} instead. */ + @Deprecated + public static final SerialFieldType TIME = TIME_SECONDS; public static final SerialFieldType SEQUENCE = new SerialFieldType(ID_COMPACT_INT | FLAG_SEQUENCE, "SEQUENCE"); public static final SerialFieldType DATE = new SerialFieldType(ID_COMPACT_INT | FLAG_DATE, "DATE"); + public static final SerialFieldType LONG = new SerialFieldType(ID_COMPACT_INT | FLAG_LONG, "LONG"); public static final SerialFieldType WIDE_DECIMAL = new SerialFieldType(ID_COMPACT_INT | FLAG_WIDE_DECIMAL, "WIDE_DECIMAL"); public static final SerialFieldType STRING = new SerialFieldType(ID_BYTE_ARRAY | FLAG_STRING, "STRING"); public static final SerialFieldType CUSTOM_OBJECT = new SerialFieldType(ID_BYTE_ARRAY | FLAG_CUSTOM_OBJECT, "CUSTOM_OBJECT"); @@ -74,6 +85,7 @@ public final class SerialFieldType { // Named instances (use same ids as standard ones) // used by forNamedField method + private static final SerialFieldType ID = LONG.withName("ID"); private static final SerialFieldType MMID = SHORT_STRING.withName("MMID"); private static final SerialFieldType EXCHANGE = UTF_CHAR.withName("EXCHANGE"); private static final SerialFieldType PRICE = DECIMAL.withName("PRICE"); @@ -88,10 +100,10 @@ public final class SerialFieldType { private static final SerialFieldType SALE_FLAGS = COMPACT_INT.withName("SALE_FLAGS"); private static final SerialFieldType PROFILE_FLAGS = COMPACT_INT.withName("PROFILE_FLAGS"); - private static final SerialFieldType BID_TIME = TIME.withName("BID_TIME"); - private static final SerialFieldType ASK_TIME = TIME.withName("ASK_TIME"); - private static final SerialFieldType TRADE_TIME = TIME.withName("TRADE_TIME"); - private static final SerialFieldType CANDLE_TIME = TIME.withName("CANDLE_TIME"); + private static final SerialFieldType BID_TIME = TIME_SECONDS.withName("BID_TIME"); + private static final SerialFieldType ASK_TIME = TIME_SECONDS.withName("ASK_TIME"); + private static final SerialFieldType TRADE_TIME = TIME_SECONDS.withName("TRADE_TIME"); + private static final SerialFieldType CANDLE_TIME = TIME_SECONDS.withName("CANDLE_TIME"); private static final SerialFieldType QUOTE_PRICE = DECIMAL.withName("QUOTE_PRICE"); private static final SerialFieldType TRADE_PRICE = DECIMAL.withName("TRADE_PRICE"); @@ -111,7 +123,8 @@ private SerialFieldType(int id, String name) { this.id = id; this.name = name; this.isObject = (id & SERIAL_TYPE_MASK) == ID_BYTE_ARRAY || (id & SERIAL_TYPE_MASK) == ID_UTF_CHAR_ARRAY; - this.isLong = (id & REPRESENTATION_MASK) == FLAG_WIDE_DECIMAL; + this.isLong = (id & REPRESENTATION_MASK) == FLAG_WIDE_DECIMAL || (id & REPRESENTATION_MASK) == FLAG_LONG || + (id & REPRESENTATION_MASK) == FLAG_TIME_MILLIS; if (isLong && isObject) throw new IllegalArgumentException("conflicting type"); } @@ -172,7 +185,7 @@ public SerialFieldType withName(String name) { /** * Returns a more specific serial type that shall be used for the field with the specified name. - * For {@link #COMPACT_INT}, {@link #DECIMAL}, {@link #SHORT_STRING}, and {@link #TIME} base types this + * For {@link #COMPACT_INT}, {@link #DECIMAL}, {@link #SHORT_STRING}, and {@link #TIME_SECONDS} base types this * method looks at the suffix of the name, for other base types just the base type itself is returned. */ public SerialFieldType forNamedField(String name) { @@ -184,7 +197,7 @@ public SerialFieldType forNamedField(String name) { private SerialFieldType forNamedFieldImpl(String name) { // ----------- time fields ----------- - if (this == COMPACT_INT || this == TIME) { + if (this == COMPACT_INT || this == TIME_SECONDS) { if (name.endsWith("Bid.Time")) return BID_TIME; if (name.endsWith("Ask.Time")) @@ -195,7 +208,7 @@ private SerialFieldType forNamedFieldImpl(String name) { return CANDLE_TIME; } if (this == COMPACT_INT && name.endsWith("Time")) - return TIME; + return TIME_SECONDS; // ----------- price fields ----------- if (this == DECIMAL) { if (name.endsWith("Bid.Price") || name.endsWith("Ask.Price")) @@ -209,7 +222,7 @@ private SerialFieldType forNamedFieldImpl(String name) { if (name.endsWith("High.Price") || name.endsWith("Low.Price") || name.endsWith("Open.Price") || name.endsWith("Close.Price")) return SUMMARY_PRICE; if ((name.startsWith("Trade.") || name.startsWith("Candle.")) && - (name.endsWith("Open") || name.endsWith("High") || name.endsWith("Low") || name.endsWith("Close")) || name.endsWith("VWAP")) + (name.endsWith("Open") || name.endsWith("High") || name.endsWith("Low") || name.endsWith("Close") || name.endsWith("VWAP"))) { return CANDLE_PRICE; } @@ -224,6 +237,8 @@ private SerialFieldType forNamedFieldImpl(String name) { return DATE; if (this == COMPACT_INT && name.endsWith("Sequence")) return SEQUENCE; + if (this == LONG && name.endsWith("Id")) // Except DayId which is handled above + return ID; if (this == SHORT_STRING && name.endsWith("MMID")) return MMID; if (this == UTF_CHAR && name.endsWith("Exchange")) @@ -268,12 +283,16 @@ public DataIntField createDefaultIntInstance(int index, String name) { return new DecimalField(index, name, this); case FLAG_SHORT_STRING: return new ShortStringField(index, name, this); - case FLAG_TIME: - return new TimeField(index, name); + case FLAG_TIME_SECONDS: + return new TimeSecondsField(index, name); + case FLAG_TIME_MILLIS: + return new TimeMillisField(index, name); case FLAG_SEQUENCE: return new SequenceField(index, name); case FLAG_DATE: return new DateField(index, name); + case FLAG_LONG: + return new LongField(index, name); case FLAG_WIDE_DECIMAL: return new WideDecimalField(index, name); default: @@ -348,11 +367,16 @@ private Bits() {} // do not create public static final int FLAG_INT = 0x00; // plain int as int field public static final int FLAG_DECIMAL = 0x10; // decimal representation as int field public static final int FLAG_SHORT_STRING = 0x20; // short (up to 4-character) string representation as int field - public static final int FLAG_TIME = 0x30; // time in seconds in this integer field + public static final int FLAG_TIME_SECONDS = 0x30; // time in seconds as integer field + @Deprecated + public static final int FLAG_TIME = FLAG_TIME_SECONDS; public static final int FLAG_SEQUENCE = 0x40; // sequence in this integer fields (with top 10 bits representing millis) public static final int FLAG_DATE = 0x50; // day id in this integer field + public static final int FLAG_LONG = 0x60; // plain long as two int fields public static final int FLAG_WIDE_DECIMAL = 0x70; // WideDecimal representation as long field public static final int FLAG_STRING = 0x80; // String representation as byte array (for ID_BYTE_ARRAY) + public static final int FLAG_TIME_MILLIS = 0x90; // time in millis as long field + public static final int FLAG_TIME_NANOS = 0xa0; // Reserved for future use: time in nanos as long field public static final int FLAG_CUSTOM_OBJECT = 0xe0; // custom serialized object as byte array (for ID_BYTE_ARRAY) public static final int FLAG_SERIAL_OBJECT = 0xf0; // serialized object as byte array (for ID_BYTE_ARRAY) } diff --git a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/Distribution.java b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/Distribution.java index eb928d5f5..d8e0f3733 100644 --- a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/Distribution.java +++ b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/Distribution.java @@ -65,10 +65,10 @@ static Distribution getInstance() { static final int UPDATED_SNIP_DIST_FLAG = 1 << 30; /** - * Set when this event turned on "snapshotBegin" flag in the history buffer (need to reset KNOWN_TIME). - * Implies that {@code (eventFlags & SNAPSHOT_BEGIN) != 0}. + * Set when this event turned on "snapshotBegin" flag in the history buffer or forced snapshot retransmit. + * Need to reset KNOWN_TIME in agent. Implies that {@code (eventFlags & SNAPSHOT_BEGIN) != 0}. */ - static final int ENABLED_SNAPSHOT_MODE_DIST_FLAG = 1 << 29; + static final int SEND_SNAPSHOT_DIST_FLAG = 1 << 29; /** * Set when record was updated in HB (includes remove of an existing one). diff --git a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/History.java b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/History.java index 2f739ae20..9b35abd3a 100644 --- a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/History.java +++ b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/History.java @@ -15,9 +15,11 @@ import com.devexperts.qd.DataVisitor; import com.devexperts.qd.HistorySubscriptionFilter; import com.devexperts.qd.QDAgent; +import com.devexperts.qd.QDFilter; import com.devexperts.qd.QDHistory; import com.devexperts.qd.SymbolCodec; import com.devexperts.qd.impl.matrix.management.CollectorOperation; +import com.devexperts.qd.kit.RecordOnlyFilter; import com.devexperts.qd.ng.EventFlag; import com.devexperts.qd.ng.RecordBuffer; import com.devexperts.qd.ng.RecordCursor; @@ -31,8 +33,8 @@ import com.devexperts.util.TimePeriod; import static com.devexperts.qd.impl.matrix.Distribution.DEC_PENDING_COUNT_DIST_FLAG; -import static com.devexperts.qd.impl.matrix.Distribution.ENABLED_SNAPSHOT_MODE_DIST_FLAG; import static com.devexperts.qd.impl.matrix.Distribution.HAD_SNAPSHOT_DIST_FLAG; +import static com.devexperts.qd.impl.matrix.Distribution.SEND_SNAPSHOT_DIST_FLAG; import static com.devexperts.qd.impl.matrix.Distribution.TX_END_DIST_FLAG; import static com.devexperts.qd.impl.matrix.Distribution.TX_SWEEP_DIST_FLAG; import static com.devexperts.qd.impl.matrix.Distribution.UPDATED_RECORD_DIST_FLAG; @@ -50,6 +52,13 @@ public class History extends Collector implements QDHistory { static final long STATE_KEEP_TIME = TimePeriod.valueOf(SystemProperties.getProperty( History.class, "stateKeepTime", "60s")).getTime(); + //FIXME Abstraction leak here: History should not know about dxscheme and unconflated Order. + // Flag specifying "conflated" or "unconflated" mode for History (dxscheme.fob=false or true respectively). + static final boolean FOB_FLAG = SystemProperties.getBooleanProperty("dxscheme.fob", false); + // By default, all records except Order are conflated. + static final String CONFLATE_FILTER = SystemProperties.getProperty(History.class, "conflateFilter", + FOB_FLAG ? "!:Order*" : "*"); + /* * Event flags. */ @@ -161,17 +170,27 @@ public class History extends Collector implements QDHistory { private final HistorySubscriptionFilter historyFilter; + // Filter specifying records to conflate in "unconflated" mode + private final QDFilter conflateFilter; + private ProcessVersionTracker processVersion = new ProcessVersionTracker(); //======================================= constructor ======================================= protected History(Builder builder) { + this(builder, null); + } + + protected History(Builder builder, RecordOnlyFilter conflateFilter) { super(builder, true, true); HistorySubscriptionFilter historyFilter = builder.getHistoryFilter(); // If history filter is not specified in builder get it as service. if (historyFilter == null) historyFilter = builder.getScheme().getService(HistorySubscriptionFilter.class); this.historyFilter = historyFilter; + if (conflateFilter == null) + conflateFilter = RecordOnlyFilter.valueOf(CONFLATE_FILTER, builder.getScheme()); + this.conflateFilter = conflateFilter; } //======================================= methods ======================================= @@ -616,14 +635,17 @@ boolean processRecordSourceGLocked(Distributor distributor, Distribution dist, R RecordCursor cursor; while ((cursor = source.next()) != null) { DataRecord record = cursor.getRecord(); + int rid = getRid(record); dist.countIncomingRecord(rid); - int cipher = cursor.getCipher(); - String symbol = cursor.getSymbol(); if (!record.hasTime()) continue; + + int cipher = cursor.getCipher(); + String symbol = cursor.getSymbol(); if (storeEverything && !distributor.filter.accept(contract, record, cipher, symbol)) continue; + int key = getKey(cipher, symbol); int tindex = tsub.getIndex(key, rid, 0); // storeEverything support @@ -668,9 +690,16 @@ boolean processRecordSourceGLocked(Distributor distributor, Distribution dist, R /* * (0) Turn on snapshot mode when we see SNAPSHOT_BEGIN/MODE flag (per QD-895). */ - if ((eventFlags & (SNAPSHOT_BEGIN | SNAPSHOT_MODE)) != 0 && hb.snapshotMode()) { - // mark event that turned on snapshot mode (it will have to be processed by agents) - distFlags |= ENABLED_SNAPSHOT_MODE_DIST_FLAG; + if ((eventFlags & (SNAPSHOT_BEGIN | SNAPSHOT_MODE)) != 0) { + if (hb.enterSnapshotModeFirstTime()) { + // mark event that turned on snapshot mode (it will have to be processed by agents) + distFlags |= SEND_SNAPSHOT_DIST_FLAG; + } else if (!conflateFilter.accept(cursor)) { + // We need to transparently retransmit the snapshot for unconflated events. + hb.enterSnapshotModeForUnconflated(); + // mark event that triggered snapshot retransmission (it will have to be processed by agents) + distFlags |= SEND_SNAPSHOT_DIST_FLAG; + } } /* * (1) Process transaction TX_PENDING logic. This logic is invoked even if subscription has changed and this @@ -799,7 +828,7 @@ boolean processRecordSourceGLocked(Distributor distributor, Distribution dist, R * this agent. */ if (sweepRemovePosition != endRemovePosition && - (!agent.useHistorySnapshot() || (distFlags & ENABLED_SNAPSHOT_MODE_DIST_FLAG) == 0)) + (!agent.useHistorySnapshot() || (distFlags & SEND_SNAPSHOT_DIST_FLAG) == 0)) { /* * Submit snapshot-removed records (if subscribed). Note, that they constitute a part of @@ -832,7 +861,7 @@ boolean processRecordSourceGLocked(Distributor distributor, Distribution dist, R if (time >= timeSub && // (1) ((distFlags & (UPDATED_RECORD_DIST_FLAG | UPDATED_SNIP_DIST_FLAG)) != 0) || // (1a, 1b) agent.useHistorySnapshot() && - (((distFlags & (TX_END_DIST_FLAG | ENABLED_SNAPSHOT_MODE_DIST_FLAG)) != 0) || // (2a, 2b) + (((distFlags & (TX_END_DIST_FLAG | SEND_SNAPSHOT_DIST_FLAG)) != 0) || // (2a, 2b) (time < prevSnapshotTime && prevSnapshotTime > timeSub && hb.wasEverSnapshotMode()))) // (2c) { dist.add(nagent, position, distFlags, rid); @@ -953,7 +982,7 @@ int processAgentDataUpdate(Distribution dist, RecordSource buffer, Agent agent) * then reset agent's known time, so that it starts delivering snapshot from HB and clear everything from * the local buffer (which might break snapshot consistency) */ - if (agent.useHistorySnapshot() && (distFlags & ENABLED_SNAPSHOT_MODE_DIST_FLAG) != 0) { + if (agent.useHistorySnapshot() && (distFlags & SEND_SNAPSHOT_DIST_FLAG) != 0) { timeKnown = Long.MAX_VALUE; asub.setLong(aindex + TIME_KNOWN, timeKnown); // Drop everything queued in agent buffer @@ -1099,7 +1128,7 @@ int processAgentDataUpdate(Distribution dist, RecordSource buffer, Agent agent) * 2) event was received by history buffer in snapshot state * * This is so, because here we have an event that updates already retrieved part of the snapshot - * (time >= timeKnow) which may have happened after an update to the part of the snapshot that is yet + * (time >= timeKnown) which may have happened after an update to the part of the snapshot that is yet * to be retrieved, so the resulting snapshot may end up being inconsistent * (see HistoryTxTest.testPartialRetrieveUpdateInconsistency) * @@ -1125,7 +1154,9 @@ int processAgentDataUpdate(Distribution dist, RecordSource buffer, Agent agent) */ if (lastRecordInBuffer) { RecordCursor writeCursor = agent.buffer.writeCursorAtPersistentPosition(lastRecord); - if (writeCursor.getTime() == (virtualTime ? VIRTUAL_TIME : time)) { + if (writeCursor.getTime() == (virtualTime ? VIRTUAL_TIME : time) && + conflateFilter.accept(writeCursor)) + { // Conflate update to the most recently updated item and that's it conflateLastRecord(cursor, writeCursor, virtualTime, eventFlags); continue; diff --git a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/HistoryBuffer.java b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/HistoryBuffer.java index 37f5eb282..5ecd305aa 100644 --- a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/HistoryBuffer.java +++ b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/HistoryBuffer.java @@ -532,15 +532,19 @@ void resetSnapshot() { // (0) Is invoked from processRecordSource when SNAPSHOT_BEGIN/MODE flag is set // Returns true when EVER_SNAPSHOT_MODE_FLAG is just set - boolean snapshotMode() { + boolean enterSnapshotModeFirstTime() { assert validTimes(); if (wasEverSnapshotMode()) return false; - // pretend as if we've see the actual SNAPSHOT_BEGIN (even if not). + // pretend as if we have seen the actual SNAPSHOT_BEGIN (even if not). flags |= EVER_SNAPSHOT_MODE_FLAG | SNAPSHOT_BEGIN_SEEN_FLAG; return true; } + void enterSnapshotModeForUnconflated() { + flags |= EVER_SNAPSHOT_MODE_FLAG | SNAPSHOT_BEGIN_SEEN_FLAG; + } + // (1) Is invoked from processRecordSource with the value of TX_PENDING flag // returns true when this event is the END of transaction (TX_END) // Note, txPending flag will have to cleared later by invoking txEnd method. @@ -568,13 +572,9 @@ boolean updateExplicitTx(boolean txPending) { void snapshotBegin() { snapshotTime = Long.MAX_VALUE; assert validTimes(); - if (!wasSnapshotBeginSeen()) { - // set snapshot begin flag first time we see it - flags |= SNAPSHOT_BEGIN_SEEN_FLAG; - } else { - // we already have snapshot -- reset snapshot end flag - flags &= ~SNAPSHOT_END_SEEN_FLAG; - } + + flags |= SNAPSHOT_BEGIN_SEEN_FLAG; + flags &= ~SNAPSHOT_END_SEEN_FLAG; } // (3) Is invoked from processRecordSource when SNAPSHOT_SNIP flag is set diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/DateField.java b/qd-core/src/main/java/com/devexperts/qd/kit/DateField.java index f38e19cf9..030ea59d1 100644 --- a/qd-core/src/main/java/com/devexperts/qd/kit/DateField.java +++ b/qd-core/src/main/java/com/devexperts/qd/kit/DateField.java @@ -16,7 +16,7 @@ public class DateField extends CompactIntField { public DateField(int index, String name) { - super(index, name, SerialFieldType.DATE.forNamedField(name)); + this(index, name, SerialFieldType.DATE.forNamedField(name)); } public DateField(int index, String name, SerialFieldType serialType) { diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/LongField.java b/qd-core/src/main/java/com/devexperts/qd/kit/LongField.java new file mode 100644 index 000000000..06223e97d --- /dev/null +++ b/qd-core/src/main/java/com/devexperts/qd/kit/LongField.java @@ -0,0 +1,82 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 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.devexperts.qd.kit; + +import com.devexperts.io.BufferedInput; +import com.devexperts.io.BufferedOutput; +import com.devexperts.qd.SerialFieldType; +import com.devexperts.qd.ng.RecordCursor; + +import java.io.IOException; + +/** + * The LongField represents a long integer field with compact serialized form. + */ +public class LongField extends CompactIntField { + public LongField(int index, String name) { + this(index, name, SerialFieldType.LONG.forNamedField(name)); + } + + public LongField(int index, String name, SerialFieldType serialType) { + super(index, name, serialType); + if (!serialType.hasSameRepresentationAs(SerialFieldType.LONG)) + throw new IllegalArgumentException("Invalid serialType: " + serialType); + } + + @Override + public String getString(RecordCursor cursor) { + return toStringLong(cursor.getLong(getIndex())); + } + + @Override + public void setString(RecordCursor cursor, String value) { + cursor.setLong(getIndex(), parseStringLong(value)); + } + + @Override + public void write(BufferedOutput out, RecordCursor cursor) throws IOException { + out.writeCompactLong(cursor.getLong(getIndex())); + } + + @Override + public void read(BufferedInput in, RecordCursor cursor) throws IOException { + cursor.setLong(getIndex(), in.readCompactLong()); + } + + @Override + public String toString(int value) { + return toStringLong(value); + } + + @Override + public int parseString(String value) { + return (int) parseStringLong(value); + } + + @Override + public double toDouble(int value) { + return value; + } + + @Override + public int toInt(double value) { + return (int) value; + } + + protected String toStringLong(long value) { + return Long.toString(value); + } + + protected long parseStringLong(String value) { + return Long.parseLong(value); + } +} diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/SequenceField.java b/qd-core/src/main/java/com/devexperts/qd/kit/SequenceField.java index c06c03b3d..6bf0d9fc8 100644 --- a/qd-core/src/main/java/com/devexperts/qd/kit/SequenceField.java +++ b/qd-core/src/main/java/com/devexperts/qd/kit/SequenceField.java @@ -18,7 +18,7 @@ public class SequenceField extends CompactIntField { private static final int SEQUENCE_MASK = (1 << MILLIS_SHIFT) - 1; public SequenceField(int index, String name) { - super(index, name, SerialFieldType.SEQUENCE.forNamedField(name)); + this(index, name, SerialFieldType.SEQUENCE.forNamedField(name)); } public SequenceField(int index, String name, SerialFieldType serialType) { diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/TimeField.java b/qd-core/src/main/java/com/devexperts/qd/kit/TimeField.java index 54b38ddbe..ed565f2fd 100644 --- a/qd-core/src/main/java/com/devexperts/qd/kit/TimeField.java +++ b/qd-core/src/main/java/com/devexperts/qd/kit/TimeField.java @@ -14,14 +14,18 @@ import com.devexperts.qd.SerialFieldType; import com.devexperts.util.TimeFormat; +/** + * @deprecated Use {@link TimeSecondsField} instead. + */ +@Deprecated() public class TimeField extends CompactIntField { public TimeField(int index, String name) { - super(index, name, SerialFieldType.TIME.forNamedField(name)); + this(index, name, SerialFieldType.TIME_SECONDS.forNamedField(name)); } public TimeField(int index, String name, SerialFieldType serialType) { super(index, name, serialType); - if (!serialType.hasSameRepresentationAs(SerialFieldType.TIME)) + if (!serialType.hasSameRepresentationAs(SerialFieldType.TIME_SECONDS)) throw new IllegalArgumentException("Invalid serialType: " + serialType); } diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/TimeMillisField.java b/qd-core/src/main/java/com/devexperts/qd/kit/TimeMillisField.java new file mode 100644 index 000000000..b7e875bac --- /dev/null +++ b/qd-core/src/main/java/com/devexperts/qd/kit/TimeMillisField.java @@ -0,0 +1,80 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 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.devexperts.qd.kit; + +import com.devexperts.io.BufferedInput; +import com.devexperts.io.BufferedOutput; +import com.devexperts.qd.SerialFieldType; +import com.devexperts.qd.ng.RecordCursor; +import com.devexperts.util.TimeFormat; + +import java.io.IOException; + +public class TimeMillisField extends CompactIntField { + public TimeMillisField(int index, String name) { + this(index, name, SerialFieldType.TIME_MILLIS.forNamedField(name)); + } + + public TimeMillisField(int index, String name, SerialFieldType serialType) { + super(index, name, serialType); + if (!serialType.hasSameRepresentationAs(SerialFieldType.TIME_MILLIS)) + throw new IllegalArgumentException("Invalid serialType: " + serialType); + } + + @Override + public String getString(RecordCursor cursor) { + return toStringLong(cursor.getLong(getIndex())); + } + + @Override + public void setString(RecordCursor cursor, String value) { + cursor.setLong(getIndex(), parseStringLong(value)); + } + + @Override + public void write(BufferedOutput out, RecordCursor cursor) throws IOException { + out.writeCompactLong(cursor.getLong(getIndex())); + } + + @Override + public void read(BufferedInput in, RecordCursor cursor) throws IOException { + cursor.setLong(getIndex(), in.readCompactLong()); + } + + @Override + public String toString(int value) { + return toStringLong(value); + } + + @Override + public int parseString(String value) { + return (int) parseStringLong(value); + } + + @Override + public double toDouble(int value) { + return value; + } + + @Override + public int toInt(double value) { + return (int) value; + } + + protected String toStringLong(long value) { + return TimeFormat.DEFAULT.withTimeZone().withMillis().format(value); + } + + protected long parseStringLong(String value) { + return TimeFormat.DEFAULT.parse(value).getTime(); + } +} diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/TimeSecondsField.java b/qd-core/src/main/java/com/devexperts/qd/kit/TimeSecondsField.java new file mode 100644 index 000000000..55cd7ea58 --- /dev/null +++ b/qd-core/src/main/java/com/devexperts/qd/kit/TimeSecondsField.java @@ -0,0 +1,25 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 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.devexperts.qd.kit; + +import com.devexperts.qd.SerialFieldType; + +@SuppressWarnings("deprecation") +public class TimeSecondsField extends TimeField { + public TimeSecondsField(int index, String name) { + super(index, name); + } + + public TimeSecondsField(int index, String name, SerialFieldType serialType) { + super(index, name, serialType); + } +} diff --git a/qd-core/src/main/java/com/devexperts/qd/ng/RecordMapping.java b/qd-core/src/main/java/com/devexperts/qd/ng/RecordMapping.java index 566709768..5622be80a 100644 --- a/qd-core/src/main/java/com/devexperts/qd/ng/RecordMapping.java +++ b/qd-core/src/main/java/com/devexperts/qd/ng/RecordMapping.java @@ -17,6 +17,7 @@ import com.devexperts.qd.SerialFieldType; import com.devexperts.qd.kit.AbstractDataField; import com.devexperts.qd.util.Decimal; +import com.devexperts.util.TimeUtil; import com.devexperts.util.WideDecimal; import java.util.HashMap; @@ -75,20 +76,28 @@ public final DataRecord getRecord() { return record; } - protected final int getInt(RecordCursor cursor, int int_field_index) { - return cursor.getIntMappedImpl(record, int_field_index); + protected final int getInt(RecordCursor cursor, int intFieldIndex) { + return cursor.getIntMappedImpl(record, intFieldIndex); } - protected final Object getObj(RecordCursor cursor, int obj_field_index) { - return cursor.getObjMappedImpl(record, obj_field_index); + protected final long getLong(RecordCursor cursor, int intFieldIndex) { + return cursor.getLongMappedImpl(record, intFieldIndex); } - protected final void setInt(RecordCursor cursor, int int_field_index, int value) { - cursor.setIntMappedImpl(record, int_field_index, value); + protected final Object getObj(RecordCursor cursor, int objFieldIndex) { + return cursor.getObjMappedImpl(record, objFieldIndex); } - protected final void setObj(RecordCursor cursor, int obj_field_index, Object value) { - cursor.setObjMappedImpl(record, obj_field_index, value); + protected final void setInt(RecordCursor cursor, int intFieldIndex, int value) { + cursor.setIntMappedImpl(record, intFieldIndex, value); + } + + protected final void setLong(RecordCursor cursor, int intFieldIndex, long value) { + cursor.setLongMappedImpl(record, intFieldIndex, value); + } + + protected final void setObj(RecordCursor cursor, int objFieldIndex, Object value) { + cursor.setObjMappedImpl(record, objFieldIndex, value); } protected final int findIntField(String localName, boolean required) { @@ -100,10 +109,14 @@ protected final int findIntField(String localName, boolean required) { return Integer.MIN_VALUE; } + // Integer, Long and Decimal field converters + protected final int getAsInt(RecordCursor cursor, int fieldId) { switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) { case SerialFieldType.Bits.FLAG_INT: return cursor.getIntMappedImpl(record, fieldId >> 8); + case SerialFieldType.Bits.FLAG_LONG: + return (int) cursor.getLongMappedImpl(record, fieldId >> 8); case SerialFieldType.Bits.FLAG_DECIMAL: return (int) Decimal.toDouble(cursor.getIntMappedImpl(record, fieldId >> 8)); case SerialFieldType.Bits.FLAG_WIDE_DECIMAL: @@ -117,6 +130,8 @@ protected final long getAsLong(RecordCursor cursor, int fieldId) { switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) { case SerialFieldType.Bits.FLAG_INT: return cursor.getIntMappedImpl(record, fieldId >> 8); + case SerialFieldType.Bits.FLAG_LONG: + return cursor.getLongMappedImpl(record, fieldId >> 8); case SerialFieldType.Bits.FLAG_DECIMAL: return (long) Decimal.toDouble(cursor.getIntMappedImpl(record, fieldId >> 8)); case SerialFieldType.Bits.FLAG_WIDE_DECIMAL: @@ -130,6 +145,8 @@ protected final double getAsDouble(RecordCursor cursor, int fieldId) { switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) { case SerialFieldType.Bits.FLAG_INT: return cursor.getIntMappedImpl(record, fieldId >> 8); + case SerialFieldType.Bits.FLAG_LONG: + return cursor.getLongMappedImpl(record, fieldId >> 8); case SerialFieldType.Bits.FLAG_DECIMAL: return Decimal.toDouble(cursor.getIntMappedImpl(record, fieldId >> 8)); case SerialFieldType.Bits.FLAG_WIDE_DECIMAL: @@ -143,6 +160,8 @@ protected final int getAsTinyDecimal(RecordCursor cursor, int fieldId) { switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) { case SerialFieldType.Bits.FLAG_INT: return Decimal.composeDecimal(cursor.getIntMappedImpl(record, fieldId >> 8), 0); + case SerialFieldType.Bits.FLAG_LONG: + return Decimal.composeDecimal(cursor.getLongMappedImpl(record, fieldId >> 8), 0); case SerialFieldType.Bits.FLAG_DECIMAL: return cursor.getIntMappedImpl(record, fieldId >> 8); case SerialFieldType.Bits.FLAG_WIDE_DECIMAL: @@ -156,6 +175,8 @@ protected final long getAsWideDecimal(RecordCursor cursor, int fieldId) { switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) { case SerialFieldType.Bits.FLAG_INT: return WideDecimal.composeWide(cursor.getIntMappedImpl(record, fieldId >> 8), 0); + case SerialFieldType.Bits.FLAG_LONG: + return WideDecimal.composeWide(cursor.getLongMappedImpl(record, fieldId >> 8), 0); case SerialFieldType.Bits.FLAG_DECIMAL: return Decimal.tinyToWide(cursor.getIntMappedImpl(record, fieldId >> 8)); case SerialFieldType.Bits.FLAG_WIDE_DECIMAL: @@ -170,6 +191,9 @@ protected final void setAsInt(RecordCursor cursor, int fieldId, int value) { case SerialFieldType.Bits.FLAG_INT: cursor.setIntMappedImpl(record, fieldId >> 8, value); break; + case SerialFieldType.Bits.FLAG_LONG: + cursor.setLongMappedImpl(record, fieldId >> 8, value); + break; case SerialFieldType.Bits.FLAG_DECIMAL: cursor.setIntMappedImpl(record, fieldId >> 8, Decimal.composeDecimal(value, 0)); break; @@ -186,6 +210,9 @@ protected final void setAsLong(RecordCursor cursor, int fieldId, long value) { case SerialFieldType.Bits.FLAG_INT: cursor.setIntMappedImpl(record, fieldId >> 8, (int) value); break; + case SerialFieldType.Bits.FLAG_LONG: + cursor.setLongMappedImpl(record, fieldId >> 8, value); + break; case SerialFieldType.Bits.FLAG_DECIMAL: cursor.setIntMappedImpl(record, fieldId >> 8, Decimal.composeDecimal(value, 0)); break; @@ -202,6 +229,9 @@ protected final void setAsDouble(RecordCursor cursor, int fieldId, double value) case SerialFieldType.Bits.FLAG_INT: cursor.setIntMappedImpl(record, fieldId >> 8, (int) value); break; + case SerialFieldType.Bits.FLAG_LONG: + cursor.setLongMappedImpl(record, fieldId >> 8, (long) value); + break; case SerialFieldType.Bits.FLAG_DECIMAL: cursor.setIntMappedImpl(record, fieldId >> 8, Decimal.compose(value)); break; @@ -218,6 +248,9 @@ protected final void setAsTinyDecimal(RecordCursor cursor, int fieldId, int valu case SerialFieldType.Bits.FLAG_INT: cursor.setIntMappedImpl(record, fieldId >> 8, (int) Decimal.toDouble(value)); break; + case SerialFieldType.Bits.FLAG_LONG: + cursor.setLongMappedImpl(record, fieldId >> 8, (long) Decimal.toDouble(value)); + break; case SerialFieldType.Bits.FLAG_DECIMAL: cursor.setIntMappedImpl(record, fieldId >> 8, value); break; @@ -234,6 +267,9 @@ protected final void setAsWideDecimal(RecordCursor cursor, int fieldId, long val case SerialFieldType.Bits.FLAG_INT: cursor.setIntMappedImpl(record, fieldId >> 8, (int) WideDecimal.toLong(value)); break; + case SerialFieldType.Bits.FLAG_LONG: + cursor.setLongMappedImpl(record, fieldId >> 8, WideDecimal.toLong(value)); + break; case SerialFieldType.Bits.FLAG_DECIMAL: cursor.setIntMappedImpl(record, fieldId >> 8, Decimal.wideToTiny(value)); break; @@ -244,4 +280,54 @@ protected final void setAsWideDecimal(RecordCursor cursor, int fieldId, long val throw new IllegalArgumentException(); } } + + // Time field converters + + protected final int getAsTimeSeconds(RecordCursor cursor, int fieldId) { + switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) { + case SerialFieldType.Bits.FLAG_TIME_SECONDS: + return cursor.getIntMappedImpl(record, fieldId >> 8); + case SerialFieldType.Bits.FLAG_TIME_MILLIS: + return TimeUtil.getSecondsFromTime(cursor.getLongMappedImpl(record, fieldId >> 8)); + default: + throw new IllegalArgumentException(); + } + } + + protected final void setAsTimeSeconds(RecordCursor cursor, int fieldId, int value) { + switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) { + case SerialFieldType.Bits.FLAG_TIME_SECONDS: + cursor.setIntMappedImpl(record, fieldId >> 8, value); + break; + case SerialFieldType.Bits.FLAG_TIME_MILLIS: + cursor.setLongMappedImpl(record, fieldId >> 8, value * 1000L); + break; + default: + throw new IllegalArgumentException(); + } + } + + protected final long getAsTimeMillis(RecordCursor cursor, int fieldId) { + switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) { + case SerialFieldType.Bits.FLAG_TIME_SECONDS: + return cursor.getIntMappedImpl(record, fieldId >> 8) * 1000L; + case SerialFieldType.Bits.FLAG_TIME_MILLIS: + return cursor.getLongMappedImpl(record, fieldId >> 8); + default: + throw new IllegalArgumentException(); + } + } + + protected final void setAsTimeMillis(RecordCursor cursor, int fieldId, long value) { + switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) { + case SerialFieldType.Bits.FLAG_TIME_SECONDS: + cursor.setIntMappedImpl(record, fieldId >> 8, TimeUtil.getSecondsFromTime(value)); + break; + case SerialFieldType.Bits.FLAG_TIME_MILLIS: + cursor.setLongMappedImpl(record, fieldId >> 8, value); + break; + default: + throw new IllegalArgumentException(); + } + } } diff --git a/qd-core/src/main/java/com/devexperts/qd/qtp/BinaryRecordDesc.java b/qd-core/src/main/java/com/devexperts/qd/qtp/BinaryRecordDesc.java index 050310e5c..3004db1bd 100644 --- a/qd-core/src/main/java/com/devexperts/qd/qtp/BinaryRecordDesc.java +++ b/qd-core/src/main/java/com/devexperts/qd/qtp/BinaryRecordDesc.java @@ -29,6 +29,7 @@ import com.devexperts.qd.ng.RecordCursor; import com.devexperts.qd.util.Decimal; import com.devexperts.qd.util.TimeSequenceUtil; +import com.devexperts.util.TimeUtil; import com.devexperts.util.WideDecimal; import java.io.IOException; @@ -36,8 +37,10 @@ import static com.devexperts.qd.SerialFieldType.Bits.FLAG_DECIMAL; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_INT; +import static com.devexperts.qd.SerialFieldType.Bits.FLAG_LONG; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_SEQUENCE; -import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME; +import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_MILLIS; +import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_SECONDS; import static com.devexperts.qd.SerialFieldType.Bits.FLAG_WIDE_DECIMAL; import static com.devexperts.qd.SerialFieldType.Bits.ID_BYTE; import static com.devexperts.qd.SerialFieldType.Bits.ID_BYTE_ARRAY; @@ -54,19 +57,18 @@ public class BinaryRecordDesc { /* * DESC integer bits layout * - * 4 bits 4 bits 24 bits + * 6 bits 4 bits 22 bits * +-------+-------+-----------------+ * | FLD | SER | index | * +-------+-------+-----------------+ - * 32 28 24 ..... 0 + * 32 26 22 ... 0 * * FLD -- defines the general operation and flips between fast/slow path * SER -- defines serialization format in stream. * index -- the index of this field in record. - * */ - protected static final int SER_SHIFT = 24; + protected static final int SER_SHIFT = 22; protected static final int SER_MASK = 0xf; // after shift protected static final int SER_OTHER = 0; // never appears in desc array, used for special DESC_VOID and DESC_INVALID @@ -86,25 +88,38 @@ public class BinaryRecordDesc { protected static final int SER_MARSHALLED = 9; protected static final int SER_PLAIN_OBJECT = 10; // deprecated, but still supported - protected static final int FLD_SHIFT = 28; + protected static final int FLD_SHIFT = 26; protected static final int FLD_SKIP = 0; // just skip the field that was read (never used for Write) protected static final int FLD_INT = 1; // regular int field (w/o conversion) protected static final int FLD_LONG = 2; // regular long field (w/o conversion) protected static final int FLD_OBJ = 3; // regular obj field + // Int protected static final int FLD_DECIMAL_TO_INT = 4; // conversion decimal->int protected static final int FLD_INT_TO_DECIMAL = 5; // conversion int->decimal protected static final int FLD_WIDE_DECIMAL_TO_INT = 6; // conversion WideDecimal->int protected static final int FLD_INT_TO_WIDE_DECIMAL = 7; // conversion int->WideDecimal + // Decimal protected static final int FLD_WIDE_DECIMAL_TO_DECIMAL = 8; // conversion WideDecimal->decimal protected static final int FLD_DECIMAL_TO_WIDE_DECIMAL = 9; // conversion decimal->WideDecimal protected static final int FLD_DECIMAL_TO_SHARES = 10; // conversion decimal->decimal shares (/1000) protected static final int FLD_SHARES_TO_DECIMAL = 11; // conversion decimal shares->decimal (*1000) protected static final int FLD_WIDE_DECIMAL_TO_SHARES = 12; // conversion WideDecimal->decimal shares (/1000) protected static final int FLD_SHARES_TO_WIDE_DECIMAL = 13; // conversion decimal shares->WideDecimal (*1000) + // Special protected static final int FLD_EVENT_TIME = 14; // RecordCursor.get/setEventTime (does not have index) protected static final int FLD_EVENT_SEQUENCE = 15; // RecordCursor.get/setEventSequence (does not have index) - // Note: FLD_VALUE of 16 can't be used unless FLD_SHIFT is changed + // Long + protected static final int FLD_DECIMAL_TO_LONG = 16; + protected static final int FLD_LONG_TO_DECIMAL = 17; + protected static final int FLD_WIDE_DECIMAL_TO_LONG = 18; + protected static final int FLD_LONG_TO_WIDE_DECIMAL = 19; + // Time + protected static final int FLD_TIME_MILLIS_TO_TIME_SECONDS = 20; + protected static final int FLD_TIME_SECONDS_TO_TIME_MILLIS = 21; + protected static final int FLD_TIME_MILLIS_TO_EVENT_TIME_SEQUENCE = 22; //TODO + protected static final int FLD_EVENT_TIME_SEQUENCE_TO_TIME_MILLIS = 23; //TODO + // Note: FLD_VALUE of 64 can't be used unless FLD_SHIFT is changed protected static final int FLAG_SHARES = -1; // denotes decimal shares which are expressed in thousands @@ -156,7 +171,7 @@ protected BinaryRecordDesc(DataRecord record, boolean eventTimeSequence, int dir int nDesc = 0; if (eventTimeSequence) { names[nDesc] = BuiltinFields.EVENT_TIME_FIELD_NAME; - types[nDesc] = SerialFieldType.TIME.getId(); + types[nDesc] = SerialFieldType.TIME_SECONDS.getId(); descs[nDesc++] = DESC_EVENT_TIME; names[nDesc] = BuiltinFields.EVENT_SEQUENCE_FIELD_NAME; types[nDesc] = SerialFieldType.SEQUENCE.getId(); @@ -216,7 +231,7 @@ protected BinaryRecordDesc(DataRecord record, int nFld, String[] namesIn, int[] int i = 0; if (eventTimeSequence && nFld >= 1 && names[0].equals(BuiltinFields.EVENT_TIME_FIELD_NAME) && - types[0] == (ID_COMPACT_INT | FLAG_TIME)) + types[0] == (ID_COMPACT_INT | FLAG_TIME_SECONDS)) { i++; descs[nDesc++] = DESC_EVENT_TIME; @@ -377,12 +392,14 @@ protected void readFields(BufferedInput msg, RecordCursor cur, int nDesc) throws setIntValue(cur, d & INDEX_MASK, (int) Decimal.toDouble((int) iVal), msg); break; case FLD_INT_TO_DECIMAL: + case FLD_LONG_TO_DECIMAL: setIntValue(cur, d & INDEX_MASK, Decimal.composeDecimal(iVal, 0), msg); break; case FLD_WIDE_DECIMAL_TO_INT: setIntValue(cur, d & INDEX_MASK, (int) WideDecimal.toLong(iVal), msg); break; case FLD_INT_TO_WIDE_DECIMAL: + case FLD_LONG_TO_WIDE_DECIMAL: setLongValue(cur, d & INDEX_MASK, WideDecimal.composeWide(iVal, 0), msg); break; case FLD_WIDE_DECIMAL_TO_DECIMAL: @@ -393,7 +410,6 @@ protected void readFields(BufferedInput msg, RecordCursor cur, int nDesc) throws break; case FLD_SHARES_TO_DECIMAL: setIntValue(cur, d & INDEX_MASK, Decimal.compose(Decimal.toDouble((int) iVal) * 1000.0), msg); - iVal = Decimal.compose(Decimal.toDouble(cur.getInt(d & INDEX_MASK)) / 1000.0); break; case FLD_SHARES_TO_WIDE_DECIMAL: setLongValue(cur, d & INDEX_MASK, WideDecimal.composeWide(Decimal.toDouble((int) iVal) * 1000.0), msg); @@ -404,6 +420,18 @@ protected void readFields(BufferedInput msg, RecordCursor cur, int nDesc) throws case FLD_EVENT_SEQUENCE: cur.setEventSequence((int) iVal); break; + case FLD_DECIMAL_TO_LONG: + setLongValue(cur, d & INDEX_MASK, (long) Decimal.toDouble((int) iVal), msg); + break; + case FLD_WIDE_DECIMAL_TO_LONG: + setLongValue(cur, d & INDEX_MASK, WideDecimal.toLong(iVal), msg); + break; + case FLD_TIME_MILLIS_TO_TIME_SECONDS: + setIntValue(cur, d & INDEX_MASK, TimeUtil.getSecondsFromTime(iVal), msg); + break; + case FLD_TIME_SECONDS_TO_TIME_MILLIS: + setLongValue(cur, d & INDEX_MASK, iVal * 1000L, msg); + break; default: throw new AssertionError(); } @@ -459,12 +487,14 @@ private void writeFields(BufferedOutput msg, RecordCursor cur, int nDesc, long e oVal = cur.getObj(d & INDEX_MASK); break; case FLD_DECIMAL_TO_INT: + case FLD_DECIMAL_TO_LONG: iVal = (long) Decimal.toDouble(cur.getInt(d & INDEX_MASK)); break; case FLD_INT_TO_DECIMAL: iVal = Decimal.composeDecimal(cur.getInt(d & INDEX_MASK), 0); break; case FLD_WIDE_DECIMAL_TO_INT: + case FLD_WIDE_DECIMAL_TO_LONG: iVal = WideDecimal.toLong(cur.getLong(d & INDEX_MASK)); break; case FLD_INT_TO_WIDE_DECIMAL: @@ -488,6 +518,18 @@ private void writeFields(BufferedOutput msg, RecordCursor cur, int nDesc, long e case FLD_EVENT_SEQUENCE: iVal = TimeSequenceUtil.getSequenceFromTimeSequence(eventTimeSequence); break; + case FLD_LONG_TO_DECIMAL: + iVal = Decimal.composeDecimal(cur.getLong(d & INDEX_MASK), 0); + break; + case FLD_LONG_TO_WIDE_DECIMAL: + iVal = WideDecimal.composeWide(cur.getLong(d & INDEX_MASK), 0); + break; + case FLD_TIME_MILLIS_TO_TIME_SECONDS: + iVal = TimeUtil.getSecondsFromTime(cur.getLong(d & INDEX_MASK)); + break; + case FLD_TIME_SECONDS_TO_TIME_MILLIS: + iVal = cur.getInt(d & INDEX_MASK) * 1000L; + break; default: throw new AssertionError(); } @@ -621,8 +663,13 @@ private static int field2Desc(String name, int type, DataField f, int dir, boole } private static int getIntConverterType(int from, int to) { + // Most fields will not need conversion if (from == to) return FLD_INT; + // Specify conversion + //TODO + // This code is repetitive and error-prone + // Refactor into either sparse matrix or (from+to) encoded integer if (from == FLAG_DECIMAL && to == FLAG_INT) return FLD_DECIMAL_TO_INT; if (from == FLAG_INT && to == FLAG_DECIMAL) @@ -643,6 +690,18 @@ private static int getIntConverterType(int from, int to) { return FLD_WIDE_DECIMAL_TO_SHARES; if (from == FLAG_SHARES && to == FLAG_WIDE_DECIMAL) return FLD_SHARES_TO_WIDE_DECIMAL; + if (from == FLAG_DECIMAL && to == FLAG_LONG) + return FLD_DECIMAL_TO_LONG; + if (from == FLAG_LONG && to == FLAG_DECIMAL) + return FLD_LONG_TO_DECIMAL; + if (from == FLAG_WIDE_DECIMAL && to == FLAG_LONG) + return FLD_WIDE_DECIMAL_TO_LONG; + if (from == FLAG_LONG && to == FLAG_WIDE_DECIMAL) + return FLD_LONG_TO_WIDE_DECIMAL; + if (from == FLAG_TIME_MILLIS && to == FLAG_TIME_SECONDS) + return FLD_TIME_MILLIS_TO_TIME_SECONDS; + if (from == FLAG_TIME_SECONDS && to == FLAG_TIME_MILLIS) + return FLD_TIME_SECONDS_TO_TIME_MILLIS; return FLD_SKIP; } } diff --git a/qd-core/src/main/java/com/devexperts/qd/qtp/ConnectionQTPComposer.java b/qd-core/src/main/java/com/devexperts/qd/qtp/ConnectionQTPComposer.java index 6c4ba1441..2e39464da 100644 --- a/qd-core/src/main/java/com/devexperts/qd/qtp/ConnectionQTPComposer.java +++ b/qd-core/src/main/java/com/devexperts/qd/qtp/ConnectionQTPComposer.java @@ -125,6 +125,7 @@ protected BinaryRecordDesc getRequestedRecordDesc(DataRecord record) { } boolean wideDecimalSupported = true; + //FIXME preciseTimeSupported @Override protected boolean isWideDecimalSupported() { diff --git a/qd-core/src/test/java/com/devexperts/qd/test/HistorySnapshotMTStressTest.java b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistorySnapshotMTStressTest.java similarity index 92% rename from qd-core/src/test/java/com/devexperts/qd/test/HistorySnapshotMTStressTest.java rename to qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistorySnapshotMTStressTest.java index 98a07edb2..951def028 100644 --- a/qd-core/src/test/java/com/devexperts/qd/test/HistorySnapshotMTStressTest.java +++ b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistorySnapshotMTStressTest.java @@ -9,7 +9,7 @@ * http://mozilla.org/MPL/2.0/. * !__ */ -package com.devexperts.qd.test; +package com.devexperts.qd.impl.matrix; import com.devexperts.logging.Logging; import com.devexperts.logging.TraceLogging; @@ -21,11 +21,11 @@ import com.devexperts.qd.QDAgent; import com.devexperts.qd.QDDistributor; import com.devexperts.qd.QDFactory; -import com.devexperts.qd.QDHistory; import com.devexperts.qd.kit.CompactIntField; import com.devexperts.qd.kit.DefaultRecord; import com.devexperts.qd.kit.DefaultScheme; import com.devexperts.qd.kit.PentaCodec; +import com.devexperts.qd.kit.RecordOnlyFilter; import com.devexperts.qd.ng.AbstractRecordSink; import com.devexperts.qd.ng.EventFlag; import com.devexperts.qd.ng.RecordBuffer; @@ -38,9 +38,13 @@ import com.devexperts.qd.qtp.MessageType; import com.devexperts.qd.stats.QDStats; import junit.framework.Assert; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -55,7 +59,12 @@ import java.util.concurrent.locks.LockSupport; import javax.annotation.Nonnull; -public class HistorySnapshotMTStressTest extends TestCase { +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(Parameterized.class) +public class HistorySnapshotMTStressTest { private static final Logging log = Logging.getLogging(HistorySnapshotMTStressTest.class); private static final int N_SECS = 5; // "production" mode -- just run for 5 seconds @@ -89,26 +98,57 @@ public class HistorySnapshotMTStressTest extends TestCase { private static final PentaCodec CODEC = PentaCodec.INSTANCE; private static final DataScheme SCHEME = new DefaultScheme(CODEC, RECORD); + private final boolean unconflated; + private volatile boolean stopped; - private final HSF hsf = new HSF(); - private final QDHistory history = QDFactory.getDefaultFactory().createHistory(SCHEME, QDStats.VOID, hsf); - private final DistributorThread distributorThread = new DistributorThread(history.distributorBuilder().build()); - private final String[] symbols = new String[N_SYMBOLS]; // all have zero ciphers + private DistributorThread distributorThread; + private String[] symbols; // all have zero ciphers + + private volatile BlockingQueue uncaughtException; + private List stackTraces; - private volatile BlockingQueue uncaughtException = new ArrayBlockingQueue<>(1); - private final List stackTraces = new ArrayList<>(); + private List threads; + private List agents; - private final List threads = new ArrayList<>(); - private final List agents = new ArrayList<>(); + @Parameterized.Parameters(name = "unconflated={0}") + public static Iterable data() { + return Arrays.asList(new Object[][] { + { false }, + { true }, + }); + } + + public HistorySnapshotMTStressTest(boolean unconflated) { + this.unconflated = unconflated; + } + + @Before + public void setUp() throws Exception { + History history = new History(QDFactory.getDefaultFactory().historyBuilder() + .withScheme(SCHEME).withStats(QDStats.VOID).withHistoryFilter(new HSF()), + new RecordOnlyFilter(SCHEME) { + @Override + public boolean acceptRecord(DataRecord record) { + return !unconflated; + } + }); + distributorThread = new DistributorThread(history.distributorBuilder().build()); + stopped = false; - @Override - protected void setUp() throws Exception { TraceLogging.restart(); + symbols = new String[N_SYMBOLS]; for (int i = 0; i < N_SYMBOLS; i++) { symbols[i] = "SYMBOL_" + i; - Assert.assertEquals(0, CODEC.encode(symbols[i])); + assertEquals(0, CODEC.encode(symbols[i])); } + + uncaughtException = new ArrayBlockingQueue<>(1); + stackTraces = new ArrayList<>(); + + threads = new ArrayList<>(); + agents = new ArrayList<>(); + threads.add(distributorThread); for (int i = 0; i < N_AGENTS; i++) { AgentThread agent = new AgentThread(i, history.agentBuilder() @@ -124,6 +164,7 @@ protected void setUp() throws Exception { } } + @Test public void testStress() throws InterruptedException { for (Thread thread : threads) { thread.start(); diff --git a/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxBlockingTest.java b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxBlockingTest.java deleted file mode 100644 index 808291f09..000000000 --- a/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxBlockingTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * !++ - * QDS - Quick Data Signalling Library - * !- - * Copyright (C) 2002 - 2020 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.devexperts.qd.impl.matrix; - -import com.devexperts.qd.QDAgent; -import com.devexperts.qd.ng.AbstractRecordProvider; -import com.devexperts.qd.ng.AbstractRecordSink; -import com.devexperts.qd.ng.RecordBuffer; -import com.devexperts.qd.ng.RecordCursor; -import com.devexperts.qd.ng.RecordListener; -import com.devexperts.qd.ng.RecordMode; -import com.devexperts.qd.ng.RecordProvider; -import com.devexperts.qd.ng.RecordSink; - -/** - * The same test suite as in {@link HistoryTxTest}, but buffer size is limited to 1 - * and blocking is configured, while the actual buffer is off-loaded to a separate buffer. - * This way buffer blocking in History is stress-tested. - */ -public class HistoryTxBlockingTest extends HistoryTxTest { - private RecordBuffer buf; - private RecordListener delegateListener; - private AbstractRecordProvider provider; - - public HistoryTxBlockingTest() { - blocking = true; - } - - @Override - RecordProvider getProvider(final QDAgent agent) { - agent.setBufferOverflowStrategy(QDAgent.BufferOverflowStrategy.BLOCK); - agent.setMaxBufferSize(1); - // our buffer - buf = RecordBuffer.getInstance(agent.getMode()); - // our provider - provider = new AbstractRecordProvider() { - @Override - public RecordMode getMode() { - return agent.getMode(); - } - - @Override - public boolean retrieve(RecordSink sink) { - return buf.retrieve(sink); - } - - @Override - public void setRecordListener(RecordListener listener) { - delegateListener = listener; - if (listener != null && buf.hasNext()) - listener.recordsAvailable(provider); - } - }; - // will mimic conflation logic - final RecordSink conflatingSink = new AbstractRecordSink() { - long lastPosition = -1; - @Override - public void append(RecordCursor cursor) { - if (lastPosition >= buf.getPosition()) { - RecordCursor writeCursor = buf.writeCursorAt(lastPosition); - if (writeCursor.getTime() == cursor.getTime()) { - // conflate - writeCursor.setEventFlags(cursor.getEventFlags()); - writeCursor.copyDataFrom(cursor); - return; - } - } - lastPosition = buf.getLimit(); - buf.append(cursor); - } - }; - // install agent's listener - agent.setRecordListener(p -> { - boolean wasEmpty = !buf.hasNext(); - agent.retrieve(conflatingSink); - if (wasEmpty && buf.hasNext() && delegateListener != null) - delegateListener.recordsAvailable(provider); - }); - return provider; - } - - @Override - void closeAgent() { - super.closeAgent(); - delegateListener = null; - } -} diff --git a/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxTest.java b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxTest.java index b7993123e..d5aa7c2e8 100644 --- a/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxTest.java +++ b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxTest.java @@ -27,20 +27,29 @@ import com.devexperts.qd.kit.DefaultRecord; import com.devexperts.qd.kit.DefaultScheme; import com.devexperts.qd.kit.PentaCodec; +import com.devexperts.qd.kit.RecordOnlyFilter; +import com.devexperts.qd.ng.AbstractRecordProvider; import com.devexperts.qd.ng.AbstractRecordSink; import com.devexperts.qd.ng.EventFlag; import com.devexperts.qd.ng.RecordBuffer; import com.devexperts.qd.ng.RecordCursor; +import com.devexperts.qd.ng.RecordListener; import com.devexperts.qd.ng.RecordMode; import com.devexperts.qd.ng.RecordProvider; +import com.devexperts.qd.ng.RecordSink; import com.devexperts.qd.stats.QDStats; -import com.devexperts.test.TraceRunner; +import com.devexperts.test.TraceRunnerWithParametersFactory; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -48,7 +57,8 @@ * A suite of small single-agent single-distributor tests of {@link QDHistory} snapshot, * update, and transaction logic. */ -@RunWith(TraceRunner.class) +@RunWith(Parameterized.class) +@Parameterized.UseParametersRunnerFactory(TraceRunnerWithParametersFactory.class) public class HistoryTxTest { private static final int TX_PENDING = EventFlag.TX_PENDING.flag(); private static final int REMOVE_EVENT = EventFlag.REMOVE_EVENT.flag(); @@ -68,25 +78,46 @@ public class HistoryTxTest { private static final int CIPHER = CODEC.encode("TST"); private static final DataScheme SCHEME = new DefaultScheme(CODEC, RECORD); - private final HistoryImpl history = new HistoryImpl(QDFactory.getDefaultFactory().historyBuilder() - .withScheme(SCHEME) - .withStats(QDStats.VOID) - .withHistoryFilter(new HSF())); + @Parameterized.Parameters(name= "blocking={0}, unconflated={1}") + public static Iterable data() { + return Arrays.asList(new Object[][] { + { false, false }, { true, false }, + { false, true }, { true, true }, + }); + } + + private final boolean blocking; + private final boolean unconflated; - private final QDDistributor distributor = history.distributorBuilder().build(); + private HistoryImpl history; + private QDDistributor distributor; private QDAgent agent; private QDAgent agent2; private boolean available; - RecordProvider provider; // HistoryTxBlockingTest overrides it + RecordProvider provider; + // Blocking mode related fields + RecordProvider blockingProvider; + RecordListener blockingListener; + RecordBuffer retrieveBuf = new RecordBuffer(RecordMode.FLAGGED_DATA); RecordBuffer distributeBuf = new RecordBuffer(RecordMode.FLAGGED_DATA); boolean distributeBatch; - boolean blocking; // HistoryTxBlockingTest sets to true Runnable betweenProcessPhases; + public HistoryTxTest(boolean blocking, boolean unconflated) { + this.blocking = blocking; + this.unconflated = unconflated; + } + @Before public void setUp() throws Exception { + history = new HistoryImpl(QDFactory.getDefaultFactory().historyBuilder() + .withScheme(SCHEME) + .withStats(QDStats.VOID) + .withHistoryFilter(new HSF())); + distributor = history.distributorBuilder().build(); + history.setErrorHandler(new QDErrorHandler() { @Override public void handleDataError(DataProvider provider, Throwable t) { @@ -118,7 +149,7 @@ public void testLegacyToSnapshotUpdate() { // --- // History snapshot supporting agent just receives fresh snapshot and also removes legacy data. if (blocking) { - // in blocking mode all them are TX_PENDING (starts retrieval from non-complete HB) + // in blocking mode all of them are TX_PENDING (starts retrieval from non-complete HB) expectMore(3, 13, TX_PENDING | SNAPSHOT_BEGIN); // now there is a snapshot(!) expectMore(1, 14, TX_PENDING); expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); @@ -316,7 +347,11 @@ public void testSimpleSnapshotUpdate() { // update snapshot distribute(4, 13, SNAPSHOT_BEGIN); // --- - expectJust(4, 13, TX_PENDING); + if (unconflated) { + expectJust(4, 13, TX_PENDING | SNAPSHOT_BEGIN); + } else { + expectJust(4, 13, TX_PENDING); + } // --- distribute(3, 14, 0); // --- @@ -325,7 +360,12 @@ public void testSimpleSnapshotUpdate() { distribute(2, 11, 0); distribute(1, 12, 0); // --- - expectNothing(); + if (unconflated) { + expectMore(2, 11, TX_PENDING); + expectJust(1, 12, TX_PENDING); + } else { + expectNothing(); + } // --- distribute(0, 0, SNAPSHOT_END | REMOVE_EVENT); expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); // gets augmented with snapshot end (it goes to the sub time) @@ -350,10 +390,15 @@ public void testSnapshotSweepRemoveClean() { distribute(1, 13, SNAPSHOT_BEGIN); distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END); // --- - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - expectMore(3, 0, REMOVE_EVENT | TX_PENDING); - expectMore(2, 0, REMOVE_EVENT | TX_PENDING); - expectJust(0, 0, REMOVE_EVENT); + if (unconflated) { + expectMore(1, 13, (blocking ? TX_PENDING : 0) | SNAPSHOT_BEGIN); + expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END); + } else { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); + expectMore(2, 0, REMOVE_EVENT | TX_PENDING); + expectJust(0, 0, REMOVE_EVENT); + } } @Test @@ -403,12 +448,20 @@ public void testSnapshotSweepRemoveDirty() { // send snapshot with only 1 items left (confirm it), but don't end it distribute(1, 13, SNAPSHOT_BEGIN); // --- - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - expectMore(3, 0, REMOVE_EVENT | TX_PENDING); - expectJust(2, 0, REMOVE_EVENT | TX_PENDING); + if (unconflated) { + expectJust(1, 13, TX_PENDING | SNAPSHOT_BEGIN); + } else { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); + expectJust(2, 0, REMOVE_EVENT | TX_PENDING); + } // --- distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END); - expectJust(0, 0, REMOVE_EVENT); + if (unconflated) { + expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END); + } else { + expectJust(0, 0, REMOVE_EVENT); + } } @Test @@ -453,12 +506,16 @@ public void testSnapshotSweepRemovePartSub1() { // send snapshot with only 1 items left (confirm it), but outside of sub (!) distribute(1, 13, SNAPSHOT_BEGIN); // also implicit snapshot end for timeSub=2 // --- - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - expectMore(3, 0, REMOVE_EVENT | TX_PENDING); - if (blocking) { + if (unconflated) { + expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_END); + } else if (blocking) { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); expectMore(2, 0, REMOVE_EVENT | TX_PENDING); expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); } else { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); expectJust(2, 0, REMOVE_EVENT); } // --- @@ -481,11 +538,14 @@ public void testSnapshotSweepRemovePartSub2() { // send snapshot with only 1 items left (confirm it), but outside of sub (!) distribute(1, 13, SNAPSHOT_BEGIN); // --- - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - if (blocking) { + if (unconflated) { + expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_END); + } else if (blocking) { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); expectMore(3, 0, REMOVE_EVENT | TX_PENDING); expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); } else { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); expectJust(3, 0, REMOVE_EVENT); // end if TX was optimized to the last buffered event } // --- @@ -511,13 +571,21 @@ public void testSnapshotSweepRemoveUpdate() { // send snapshot with only 1 items left (update it!) distribute(1, 15, SNAPSHOT_BEGIN); // --- - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - expectMore(3, 0, REMOVE_EVENT | TX_PENDING); - expectMore(2, 0, REMOVE_EVENT | TX_PENDING); - expectJust(1, 15, TX_PENDING); + if (unconflated) { + expectJust(1, 15, SNAPSHOT_BEGIN | TX_PENDING); + } else { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); + expectMore(2, 0, REMOVE_EVENT | TX_PENDING); + expectJust(1, 15, TX_PENDING); + } // --- distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END); - expectJust(0, 0, REMOVE_EVENT); + if (unconflated) { + expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); + } else { + expectJust(0, 0, REMOVE_EVENT); + } } @Test @@ -536,12 +604,16 @@ public void testSnapshotSweepRemoveUpdatePartSub() { // send snapshot with only 1 items left (update it!) distribute(1, 15, SNAPSHOT_BEGIN); // --- - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - expectMore(3, 0, REMOVE_EVENT | TX_PENDING); - if (blocking) { + if (unconflated) { + expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_END); + } else if (blocking) { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); expectMore(2, 0, REMOVE_EVENT | TX_PENDING); expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); } else { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); expectJust(2, 0, REMOVE_EVENT); } // --- @@ -553,26 +625,40 @@ public void testSnapshotSweepRemoveUpdatePartSub() { public void testSnapshotInsert() { createAgent(0, true); // send snapshot (3 even items) - distribute(4, 10, SNAPSHOT_BEGIN); - distribute(2, 11, 0); - distribute(0, 12, SNAPSHOT_END); + distribute(6, 10, SNAPSHOT_BEGIN); + distribute(4, 11, 0); + distribute(2, 12, 0); + distribute(0, 13, SNAPSHOT_END); // --- - expectMore(4, 10, SNAPSHOT_BEGIN); - expectMore(2, 11, 0); - expectJust(0, 12, SNAPSHOT_END); + expectMore(6, 10, SNAPSHOT_BEGIN); + expectMore(4, 11, 0); + expectMore(2, 12, 0); + expectJust(0, 13, SNAPSHOT_END); // send snapshot with 5 items (add two more) - distribute(4, 10, SNAPSHOT_BEGIN); - distribute(3, 13, 0); - distribute(2, 11, 0); - distribute(1, 14, 0); - distribute(0, 12, SNAPSHOT_END); + distribute(6, 10, SNAPSHOT_BEGIN); + distribute(4, 11, 0); + distribute(3, 14, 0); + distribute(2, 12, 0); + distribute(1, 15, 0); + distribute(0, 13, SNAPSHOT_END); // --- - expectMore(3, 13, TX_PENDING); - if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) - expectMore(1, 14, TX_PENDING); // still pending - expectJust(0, 12, 0); // explicit TX_END - } else - expectJust(1, 14, 0); // optimized end of transaction + if (unconflated) { + // Snapshot repeats previously retrieved data + expectMore(6, 10, SNAPSHOT_BEGIN); + expectMore(4, 11, 0); + // In blocking mode updated event cause TX_PENDING flag to be set + expectMore(3, 14, blocking ? TX_PENDING : 0); + expectMore(2, 12, blocking ? TX_PENDING : 0); + expectMore(1, 15, blocking ? TX_PENDING : 0); + expectJust(0, 13, SNAPSHOT_END); // explicit TX_END + } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + expectMore(3, 14, TX_PENDING); + expectMore(1, 15, TX_PENDING); // still pending + expectJust(0, 13, 0); // explicit TX_END + } else { + expectMore(3, 14, TX_PENDING); + expectJust(1, 15, 0); // optimized end of transaction + } } @Test @@ -585,6 +671,7 @@ public void testSnapshotInsertPartSub1() { // --- expectMore(4, 10, SNAPSHOT_BEGIN); expectJust(2, 11, SNAPSHOT_END); + // send snapshot with 5 items (add two more) distribute(4, 10, SNAPSHOT_BEGIN); distribute(3, 13, 0); @@ -592,11 +679,16 @@ public void testSnapshotInsertPartSub1() { distribute(1, 14, 0); distribute(0, 12, SNAPSHOT_END); // --- - if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + if (unconflated) { + expectMore(4, 10, SNAPSHOT_BEGIN); + expectMore(3, 13, blocking ? TX_PENDING : 0); + expectJust(2, 11, SNAPSHOT_END); + } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) expectMore(3, 13, TX_PENDING); // still pending expectJust(2, 11, 0); // this event delivered to end tx, even though it is not updated and outside sub - } else + } else { expectJust(3, 13, 0); // optimized TX_END here, because that item at time=2 did not update + } } @Test @@ -616,7 +708,12 @@ public void testSnapshotInsertPartSub2() { distribute(1, 14, 0); distribute(0, 12, SNAPSHOT_END); // --- - expectJust(3, 13, 0); // only one items updates in sub range (no TX) + if (unconflated) { + expectMore(4, 10, SNAPSHOT_BEGIN); + expectJust(3, 13, SNAPSHOT_END); + } else { + expectJust(3, 13, 0); // only one items updates in sub range (no TX) + } } @Test @@ -637,8 +734,15 @@ public void testSnapshotInsertPartSub3() { distribute(1, 14, 0); distribute(0, 12, SNAPSHOT_END); // --- - expectMore(3, 13, TX_PENDING); - expectJust(1, 14, 0); + if (unconflated) { + expectMore(4, 10, SNAPSHOT_BEGIN); + expectMore(3, 13, blocking ? TX_PENDING : 0); + expectMore(2, 11, blocking ? TX_PENDING : 0); + expectJust(1, 14, SNAPSHOT_END); + } else { + expectMore(3, 13, TX_PENDING); + expectJust(1, 14, 0); + } } @Test @@ -659,11 +763,17 @@ public void testSnapshotInsertPartSub4() { distribute(2, 14, 0); // new, no sub distribute(0, 12, SNAPSHOT_END); // confirm, no sub // --- - if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + if (unconflated) { + expectMore(8, 10, SNAPSHOT_BEGIN); + expectMore(6, 13, blocking ? TX_PENDING : 0); + expectMore(4, 11, blocking ? TX_PENDING : 0); + expectJust(3, 0, SNAPSHOT_END | REMOVE_EVENT); + } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) expectMore(6, 13, TX_PENDING); // still pending expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); // this event ends tx even though it is below sub - } else + } else { expectJust(6, 13, 0); // optimized tx end + } } @Test @@ -684,12 +794,20 @@ public void testSnapshotInsertPartSub5() { distribute(2, 14, 0); // new distribute(0, 12, SNAPSHOT_END); // confirm, no sub // --- - expectMore(6, 13, TX_PENDING); - if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + if (unconflated) { + expectMore(8, 10, SNAPSHOT_BEGIN); + expectMore(6, 13, blocking ? TX_PENDING : 0); + expectMore(4, 11, blocking ? TX_PENDING : 0); + expectMore(2, 14, blocking ? TX_PENDING : 0); + expectJust(1, 0, SNAPSHOT_END | REMOVE_EVENT); + } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + expectMore(6, 13, TX_PENDING); expectMore(2, 14, TX_PENDING); // still pending expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); // tx end, even though it is below sub AND did not update anything - } else + } else { + expectMore(6, 13, TX_PENDING); expectJust(2, 14, 0); // optimized tx end + } } @Test @@ -736,7 +854,12 @@ public void testTxConflate() { distribute(4, 13, TX_PENDING); distribute(4, 14, 0); //-- - expectJust(4, 14, 0); + if (unconflated) { + expectMore(4, 13, TX_PENDING); + expectJust(4, 14, 0); + } else { + expectJust(4, 14, 0); + } } @Test @@ -1032,13 +1155,17 @@ public void testSnapshotUpdateTxEndDeliveryBelowTimeSub1() { // update snapshot (drop all items) distribute(0, 0, SNAPSHOT_BEGIN | REMOVE_EVENT | SNAPSHOT_END); // -- - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - expectMore(3, 0, REMOVE_EVENT | TX_PENDING); - if (blocking) { + if (unconflated) { + expectJust(2, 0, SNAPSHOT_BEGIN | REMOVE_EVENT | SNAPSHOT_END); + } else if (blocking) { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); // will be split into two events expectMore(2, 0, REMOVE_EVENT | TX_PENDING); expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); } else { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); // tx end will collapse into one event expectJust(2, 0, REMOVE_EVENT); // no more dirty at this item } @@ -1058,12 +1185,16 @@ public void testSnapshotUpdateTxEndDeliveryBelowTimeSub2() { // update snapshot (drop all items) distribute(0, 0, SNAPSHOT_BEGIN | REMOVE_EVENT | SNAPSHOT_END); // -- - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + if (unconflated) { + expectJust(2, 0, SNAPSHOT_BEGIN | REMOVE_EVENT | SNAPSHOT_END); + } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); expectMore(3, 0, REMOVE_EVENT | TX_PENDING); // still pending expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); // event to end tx - } else + } else { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); expectJust(3, 0, REMOVE_EVENT); // optimized tx end here + } } @Test @@ -1093,12 +1224,30 @@ public void testFlagsOnLastEventConflationWithRebase() { history.forceRebase(agent); distribute(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end // now retrieve updates - expectMore(2, 14, TX_PENDING); // this was in TX - if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + if (unconflated && blocking) { + expectMore(2, 14, TX_PENDING); + expectMore(1, 15, 0); + expectMore(4, 10, SNAPSHOT_BEGIN); + expectMore(3, 11, 0); + expectMore(2, 14, 0); + expectMore(1, 16, TX_PENDING); + expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); + } else if (unconflated) { + expectMore(4, 10, SNAPSHOT_BEGIN); + expectMore(3, 11, 0); + expectMore(2, 14, 0); + expectMore(1, 16, 0); + expectMore(0, 0, SNAPSHOT_END | REMOVE_EVENT); + // tricky case, where "available" is true, but nothing retrieves, because events were unlinked + expectNothingRetrieves(); + } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + expectMore(2, 14, TX_PENDING); // this was in TX expectMore(1, 16, TX_PENDING); // still pending expectJust(0, 0, REMOVE_EVENT); // explicit remove event to end tx - } else + } else { + expectMore(2, 14, TX_PENDING); // this was in TX expectJust(1, 16, 0); // and this in TX (conflated to a single update and optimized to end tx) + } } @Test @@ -1131,22 +1280,39 @@ public void testComplexTxUpdateSequence() { // and one more update separately on the last updated time distribute(2, 17, 0); // --- - expectMore(3, 17, TX_PENDING); // update this - if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) - expectMore(2, 16, TX_PENDING); // pending - expectMore(0, 0, REMOVE_EVENT); // explicit tx end with remove event - expectJust(2, 17, 0); // one more update - } else - expectJust(2, 17, 0); // and this ends transaction (optimization and conflation) - // do transaction that if finished by remove event - distribute(2, 18, TX_PENDING); - distribute(0, 0, REMOVE_EVENT); - // --- - if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) - expectMore(2, 18, TX_PENDING); // pending - expectJust(0, 0, REMOVE_EVENT); // delivered tx end - } else - expectJust(2, 18, 0); // optimized end of transaction + if (unconflated && blocking) { + expectMore(4, 10, SNAPSHOT_BEGIN); + expectMore(3, 17, TX_PENDING); + expectMore(2, 16, TX_PENDING); + expectMore(1, 15, TX_PENDING); + expectMore(0, 0, REMOVE_EVENT | SNAPSHOT_END); + expectJust(2, 17, 0); + } else if (unconflated) { + expectMore(4, 10, SNAPSHOT_BEGIN); + expectMore(3, 17, 0); + expectMore(2, 17, 0); + expectMore(1, 15, 0); + expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END); + } else { + expectMore(3, 17, TX_PENDING); // update this + if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + expectMore(2, 16, TX_PENDING); // pending + expectMore(0, 0, REMOVE_EVENT); // explicit tx end with remove event + expectJust(2, 17, 0); // one more update + } else { + expectJust(2, 17, 0); // and this ends transaction (optimization and conflation) + } + // do transaction that if finished by remove event + distribute(2, 18, TX_PENDING); + distribute(0, 0, REMOVE_EVENT); + // --- + if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer) + expectMore(2, 18, TX_PENDING); // pending + expectJust(0, 0, REMOVE_EVENT); // delivered tx end + } else { + expectJust(2, 18, 0); // optimized end of transaction + } + } } @Test @@ -1183,8 +1349,16 @@ public void testTxUnSubPart() { distribute(1, 13, 0); distribute(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end // --- - expectMore(1, 13, 0); - expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end + if (unconflated) { + expectMore(4, 10, SNAPSHOT_BEGIN); + expectMore(3, 11, 0); + expectMore(2, 12, 0); + expectMore(1, 13, 0); + expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end + } else { + expectMore(1, 13, 0); + expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end + } } @Test @@ -1343,6 +1517,9 @@ public void testSnapshotModeThenSnip() { // test that HB resize and buffer refilter logic all works correctly @Test public void testBigSnapshot() { + if (unconflated) + return; + createAgent(0, true); // submit big snapshot int n = 100; @@ -1598,59 +1775,78 @@ public void testExamineDuringSnapshotUpdate() { distribute(2, 11, 0); distribute(1, 13, 0); // --- - expectJust(1, 13, TX_PENDING); + if (unconflated) { + // Snapshot is not yet completed - TX_PENDING flag is set (similar to examineAll() below) + expectMore(3, 10, (blocking ? 0 : TX_PENDING) | SNAPSHOT_BEGIN); + expectMore(2, 11, (blocking ? 0 : TX_PENDING)); + expectJust(1, 13, TX_PENDING); + } else { + expectJust(1, 13, TX_PENDING); + } + // now examine all data examineAll(); expectMore(3, 10, TX_PENDING | SNAPSHOT_BEGIN); expectMore(2, 11, TX_PENDING); expectMore(1, 13, TX_PENDING); expectJust(0, 0, TX_PENDING | REMOVE_EVENT | SNAPSHOT_END); + // now examine by subscription examineBySubscription(0); expectMore(3, 10, TX_PENDING | SNAPSHOT_BEGIN); expectMore(2, 11, TX_PENDING); expectMore(1, 13, TX_PENDING); expectJust(0, 0, TX_PENDING | REMOVE_EVENT | SNAPSHOT_END); + // now examine by subscription (time 2) examineBySubscription(2); expectMore(3, 10, TX_PENDING | SNAPSHOT_BEGIN); expectJust(2, 11, TX_PENDING | SNAPSHOT_END); + // now examine data range 0->0 examineRange(0, 0); expectJust(0, 0, REMOVE_EVENT | TX_PENDING); + // now examine data range LTR (all) examineRange(0, Long.MAX_VALUE); expectMore(0, 0, REMOVE_EVENT | TX_PENDING); expectMore(1, 13, TX_PENDING); expectMore(2, 11, TX_PENDING); expectJust(3, 10, TX_PENDING); + // now examine data range LTR 1->2 examineRange(1, 2); expectMore(1, 13, TX_PENDING); expectJust(2, 11, TX_PENDING); + // now examine data range LTR 2->3 examineRange(2, 3); expectMore(2, 11, TX_PENDING); expectJust(3, 10, TX_PENDING); + // now examine data range RTL (all) examineRange(Long.MAX_VALUE, 0); expectMore(3, 10, TX_PENDING); expectMore(2, 11, TX_PENDING); expectMore(1, 13, TX_PENDING); expectJust(0, 0, REMOVE_EVENT | TX_PENDING); + // now examine data range RTL 2->1 examineRange(2, 1); expectMore(2, 11, TX_PENDING); expectJust(1, 13, TX_PENDING); + // now examine data range RTL 2->0 examineRange(2, 0); expectMore(2, 11, TX_PENDING); expectMore(1, 13, TX_PENDING); expectJust(0, 0, REMOVE_EVENT | TX_PENDING); + // now examine data range RTL 3->2 examineRange(3, 2); expectMore(3, 10, TX_PENDING); expectJust(2, 11, TX_PENDING); + // close agent and examine what was there closeAgentAndExamine(); expectMore(3, 10, TX_PENDING | SNAPSHOT_BEGIN); @@ -1672,23 +1868,41 @@ public void testSnapshotThenSnip() { expectMore(2, 11, 0); expectMore(1, 12, 0); expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END); + // resend previous snapshot, but snip distribute(3, 10, SNAPSHOT_BEGIN); distribute(2, 11, 0); distribute(1, 12, SNAPSHOT_SNIP); // --- - expectJust(1, 12, SNAPSHOT_SNIP); + if (unconflated) { + expectMore(3, 10, SNAPSHOT_BEGIN); + expectMore(2, 11, 0); + expectJust(1, 12, SNAPSHOT_SNIP); + } else { + expectJust(1, 12, SNAPSHOT_SNIP); + } + // snip even more distribute(3, 10, SNAPSHOT_BEGIN | SNAPSHOT_SNIP); // --- - expectJust(3, 10, SNAPSHOT_SNIP); + if (unconflated) { + expectJust(3, 10, SNAPSHOT_BEGIN | SNAPSHOT_SNIP); + } else { + expectJust(3, 10, SNAPSHOT_SNIP); + } // then increase snapshot again (send more data after snip) distribute(3, 10, SNAPSHOT_BEGIN); distribute(2, 11, 0); distribute(1, 12, SNAPSHOT_SNIP); // should be treated as snapshot_end (terminate snapshot update tx) // --- - expectMore(2, 11, 0); // extending previous snapshot (no tx needed) - expectJust(1, 12, SNAPSHOT_SNIP); // snapshot snip makes it consistent + if (unconflated) { + expectMore(3, 10, (blocking ? SNAPSHOT_SNIP : 0) | SNAPSHOT_BEGIN); + expectMore(2, 11, 0); + expectJust(1, 12, SNAPSHOT_SNIP); + } else { + expectMore(2, 11, 0); // extending previous snapshot (no tx needed) + expectJust(1, 12, SNAPSHOT_SNIP); // snapshot snip makes it consistent + } // increase snapshot to sub time (no longer snip) distribute(0, 13, SNAPSHOT_END); // --- @@ -1885,17 +2099,24 @@ public void testSnipTimeKnown() { distribute(2, 14, 0); distribute(1, 15, 0); distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END); - // it must be received from local buffer as transactional update to the current snapshot - expectMore(6, 0, REMOVE_EVENT | TX_PENDING); - expectMore(5, 0, REMOVE_EVENT | TX_PENDING); - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - expectMore(3, 13, TX_PENDING); - expectMore(2, 14, TX_PENDING); - if (blocking) { - expectMore(1, 15, TX_PENDING); - expectJust(0, 0, REMOVE_EVENT); + if (unconflated) { + expectMore(3, 13, (blocking ? TX_PENDING : 0) | SNAPSHOT_BEGIN); + expectMore(2, 14, (blocking ? TX_PENDING : 0)); + expectMore(1, 15, (blocking ? TX_PENDING : 0)); + expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END); } else { - expectJust(1, 15, 0); // last event ends transaction when non-blocking retrieve (from HB) + // it must be received from local buffer as transactional update to the current snapshot + expectMore(6, 0, REMOVE_EVENT | TX_PENDING); + expectMore(5, 0, REMOVE_EVENT | TX_PENDING); + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 13, TX_PENDING); + expectMore(2, 14, TX_PENDING); + if (blocking) { + expectMore(1, 15, TX_PENDING); + expectJust(0, 0, REMOVE_EVENT); + } else { + expectJust(1, 15, 0); // last event ends transaction when non-blocking retrieve (from HB) + } } } @@ -1914,11 +2135,17 @@ public void testSnipWithPartialRetrieve() { // send different snapshot, up to time=4 distribute(6, 13, SNAPSHOT_BEGIN); distribute(5, 14, 0); - distribute(4, 15, SNAPSHOT_SNIP); // snip to time > timeKnow must advance timeKnow to timeSub + distribute(4, 15, SNAPSHOT_SNIP); // snip to time > timeKnown must advance timeKnown to timeSub // --- - expectMore(6, 13, TX_PENDING); - expectMore(5, 14, TX_PENDING); - expectJust(4, 15, SNAPSHOT_SNIP); // must end transaction + if (unconflated) { + expectMore(6, 13, SNAPSHOT_BEGIN); + expectMore(5, 14, 0); + expectJust(4, 15, SNAPSHOT_SNIP); // must end transaction + } else { + expectMore(6, 13, TX_PENDING); + expectMore(5, 14, TX_PENDING); + expectJust(4, 15, SNAPSHOT_SNIP); // must end transaction + } } @Test @@ -1954,12 +2181,20 @@ public void testSnipSubSnapshotSweepRemove() { // send snapshot with only 1 items left (confirm it) distribute(1, 13, SNAPSHOT_BEGIN); // --- - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - expectMore(3, 0, REMOVE_EVENT | TX_PENDING); - expectJust(2, 0, REMOVE_EVENT | TX_PENDING); + if (unconflated) { + expectJust(1, 13, TX_PENDING | SNAPSHOT_BEGIN); + } else { + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); + expectJust(2, 0, REMOVE_EVENT | TX_PENDING); + } // --- distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END); - expectJust(0, 0, REMOVE_EVENT); + if (unconflated) { + expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_SNIP); + } else { + expectJust(0, 0, REMOVE_EVENT); + } } @Test @@ -2032,11 +2267,16 @@ public void testSnipAndSweep() { expectJust(0, 14, SNAPSHOT_END); // now sweep-remove and snip in the middle distribute(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_SNIP); - // remove old events in transaction and snip - // todo: can be further optimized - expectMore(4, 0, REMOVE_EVENT | TX_PENDING); - expectMore(3, 0, REMOVE_EVENT | TX_PENDING); - expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_SNIP); + + if (unconflated) { + expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_SNIP); + } else { + // remove old events in transaction and snip + // todo: can be further optimized + expectMore(4, 0, REMOVE_EVENT | TX_PENDING); + expectMore(3, 0, REMOVE_EVENT | TX_PENDING); + expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_SNIP); + } } @Test @@ -2279,7 +2519,7 @@ public void testLostTxPending3() { // this case was found by MTStressTest @Test public void testLostTxPending4() { - if (blocking) + if (blocking || unconflated) return; // cannot do this test in blocking mode -- it explicitly tests proper buffering createAgent(-2, true); // agent subscribes from -2, but subscription gets snipped to zero // snapshot begin @@ -2367,6 +2607,7 @@ public void testLostTxPending6() { expectMore(3, 10, SNAPSHOT_BEGIN); expectMore(2, 11, 0); expectJust(1, 12, SNAPSHOT_END); + // start sending the other snapshot (without update on the first items) distribute(3, 10, SNAPSHOT_BEGIN); // no update at time=3 // unsubscribe the other agent @@ -2375,22 +2616,40 @@ public void testLostTxPending6() { distribute(2, 13, 0); distribute(1, 14, 0); distribute(0, 15, SNAPSHOT_END); // no sub any more + // do update transaction distribute(2, 16, TX_PENDING); distribute(3, 17, TX_PENDING); // now update at time=3 (the only non-ignored event) distribute(1, 18, 0); // txEnd + // and finally distribute a snapshot distribute(3, 17, SNAPSHOT_BEGIN); // same as in tx above distribute(2, 19, 0); distribute(1, 20, SNAPSHOT_END); // -- - expectMore(2, 13, TX_PENDING); - expectMore(1, 14, TX_PENDING); - expectMore(2, 16, TX_PENDING); - expectMore(3, 17, TX_PENDING); - expectMore(1, 18, TX_PENDING); - expectMore(2, 19, TX_PENDING); - expectJust(1, 20, 0); + if (unconflated && blocking) { + expectMore(3, 10, SNAPSHOT_BEGIN); + expectMore(2, 13, TX_PENDING); + expectMore(1, 14, TX_PENDING | SNAPSHOT_END); + expectMore(2, 16, TX_PENDING); + expectMore(3, 17, TX_PENDING); + expectMore(1, 18, TX_PENDING); + expectMore(3, 17, TX_PENDING | SNAPSHOT_BEGIN); + expectMore(2, 19, TX_PENDING); + expectJust(1, 20, SNAPSHOT_END); + } else if (unconflated) { + expectMore(3, 17, SNAPSHOT_BEGIN); + expectMore(2, 19, 0); + expectJust(1, 20, SNAPSHOT_END); + } else { + expectMore(2, 13, TX_PENDING); + expectMore(1, 14, TX_PENDING); + expectMore(2, 16, TX_PENDING); + expectMore(3, 17, TX_PENDING); + expectMore(1, 18, TX_PENDING); + expectMore(2, 19, TX_PENDING); + expectJust(1, 20, 0); + } } // this case was found by MTStressTest @@ -2473,7 +2732,12 @@ public void testAgent2WithLargerSub() { distribute(1, 12, 0); distribute(0, 13, SNAPSHOT_END); // --- - expectNothing(); + if (unconflated) { + expectMore(3, 10, SNAPSHOT_BEGIN); + expectJust(2, 11, SNAPSHOT_END); + } else { + expectNothing(); + } } @Test @@ -2488,6 +2752,7 @@ public void testAgent2StillConsistentOnTotalSubReduce() { expectMore(4, 10, SNAPSHOT_BEGIN); expectMore(3, 11, 0); expectJust(2, 12, SNAPSHOT_END); + // now agent2 comes and subscribes for larger time interval createAgent2(0, true); // data source still sends updates on smaller subscription and they must be received by original agent @@ -2498,14 +2763,22 @@ public void testAgent2StillConsistentOnTotalSubReduce() { expectMore(4, 13, TX_PENDING); expectMore(3, 14, TX_PENDING); expectJust(2, 15, 0); + // now snapshot for agent2 is sent distribute(4, 13, SNAPSHOT_BEGIN); distribute(3, 14, 0); distribute(2, 15, 0); distribute(1, 16, 0); distribute(0, 17, SNAPSHOT_END); - // nothing new for original agent, though - expectNothing(); + if (unconflated) { + expectMore(4, 13, SNAPSHOT_BEGIN); + expectMore(3, 14, 0); + expectJust(2, 15, SNAPSHOT_END); + } else { + // nothing new for original agent, though + expectNothing(); + } + // now agent2 resubscribes for smaller time interval setSubTime2(10); // data source still sends updates and they must be received by original agent @@ -2516,12 +2789,19 @@ public void testAgent2StillConsistentOnTotalSubReduce() { expectMore(4, 18, TX_PENDING); expectMore(3, 19, TX_PENDING); expectJust(2, 20, 0); + // now snapshot is resent because of subscription change distribute(4, 18, SNAPSHOT_BEGIN); distribute(3, 19, 0); distribute(2, 20, SNAPSHOT_END); - // nothing new for original agent, though - expectNothing(); + if (unconflated) { + expectMore(4, 18, SNAPSHOT_BEGIN); + expectMore(3, 19, 0); + expectJust(2, 20, SNAPSHOT_END); + } else { + // nothing new for original agent, though + expectNothing(); + } } @Test @@ -2547,6 +2827,9 @@ public void testNonSubscribedUpdates() { distribute(1, 16, 0); distribute(0, 17, SNAPSHOT_END); // --- + if (unconflated && blocking) { + expectMore(10, 0, SNAPSHOT_BEGIN | SNAPSHOT_END | REMOVE_EVENT); + } expectJust(10, 0, SNAPSHOT_BEGIN | SNAPSHOT_END | REMOVE_EVENT); // now close agent and reopen at time 0. Make sure valid snapshot is only up to time 10 closeAgent(); @@ -2590,11 +2873,16 @@ public void testLowerSnapshot() { // distribute snapshot at lower values distribute(1, 12, SNAPSHOT_BEGIN); distribute(0, 13, SNAPSHOT_END); - // -- expect snapshot first, then updates (removes) - expectMore(1, 12, TX_PENDING); - expectMore(0, 13, TX_PENDING | SNAPSHOT_END); - expectMore(3, 0, TX_PENDING | REMOVE_EVENT); - expectJust(2, 0, REMOVE_EVENT); + if (unconflated) { + expectMore(1, 12, SNAPSHOT_BEGIN); + expectJust(0, 13, SNAPSHOT_END); + } else { + // -- expect snapshot first, then updates (removes) + expectMore(1, 12, TX_PENDING); + expectMore(0, 13, TX_PENDING | SNAPSHOT_END); + expectMore(3, 0, TX_PENDING | REMOVE_EVENT); + expectJust(2, 0, REMOVE_EVENT); + } } /* @@ -2705,6 +2993,8 @@ void closeAgent() { agent.close(); agent = null; provider = null; + blockingProvider = null; + blockingListener = null; assertTrue("!available", !available); } @@ -2761,7 +3051,64 @@ private void closeAgentAndExamine() { // HistoryTxBlockingTest overrides RecordProvider getProvider(QDAgent agent) { - return agent; + // Non-blocking mode: do nothing + if (!blocking) + return agent; + + // Blocking mode: + // Buffer size is limited to 1 and blocking is configured, + // while the actual buffer is off-loaded to a separate buffer. + // This way buffer blocking in History is stress-tested. + agent.setBufferOverflowStrategy(QDAgent.BufferOverflowStrategy.BLOCK); + agent.setMaxBufferSize(1); + + // our buffer + final RecordBuffer buf = RecordBuffer.getInstance(agent.getMode()); + // our provider + blockingProvider = new AbstractRecordProvider() { + @Override + public RecordMode getMode() { + return agent.getMode(); + } + + @Override + public boolean retrieve(RecordSink sink) { + return buf.retrieve(sink); + } + + @Override + public void setRecordListener(RecordListener listener) { + blockingListener = listener; + if (listener != null && buf.hasNext()) + listener.recordsAvailable(blockingProvider); + } + }; + // will mimic conflation logic + final RecordSink conflatingSink = new AbstractRecordSink() { + long lastPosition = -1; + @Override + public void append(RecordCursor cursor) { + if (lastPosition >= buf.getPosition()) { + RecordCursor writeCursor = buf.writeCursorAt(lastPosition); + if (writeCursor.getTime() == cursor.getTime() && !unconflated) { + // Emulate conflation + writeCursor.setEventFlags(cursor.getEventFlags()); + writeCursor.copyDataFrom(cursor); + return; + } + } + lastPosition = buf.getLimit(); + buf.append(cursor); + } + }; + // install an agent's listener + agent.setRecordListener(p -> { + boolean wasEmpty = !buf.hasNext(); + agent.retrieve(conflatingSink); + if (wasEmpty && buf.hasNext() && blockingListener != null) + blockingListener.recordsAvailable(blockingProvider); + }); + return blockingProvider; } private void startBatch() { @@ -2783,7 +3130,6 @@ private void processBatch() { distributeBatch = false; } - private void removeData() { RecordBuffer buf = RecordBuffer.getInstance(RecordMode.FLAGGED_DATA); buf.add(RECORD, CIPHER, null); @@ -2801,14 +3147,21 @@ private void expectJust(long time, int value, int flags) { assertTrue("!available", !available && retrieveBuf.isEmpty()); } + private void expectUnconflated(final long time, final int value, final int flags) { + if (unconflated) { + expect(time, value, flags); + } + } + private void expect(final long time, final int value, final int flags) { assertTrue("available", available || !retrieveBuf.isEmpty()); if (retrieveBuf.isEmpty()) retrieveBatch(1); RecordCursor cursor = retrieveBuf.next(); + assertNotNull(cursor); assertEquals("record", RECORD, cursor.getRecord()); assertEquals("cipher", CIPHER, cursor.getCipher()); - assertEquals("symbol", null, cursor.getSymbol()); + assertNull("symbol", cursor.getSymbol()); assertEquals("time", time, cursor.getTime()); assertEquals("value", value, cursor.getInt(VALUE_INDEX)); assertEquals("flags", flags, cursor.getEventFlags()); @@ -2856,7 +3209,7 @@ public void append(RecordCursor cursor) { } }); assertEquals("received", size, retrieveBuf.size()); - assertTrue("!available", !available); + assertFalse("!available", available); available = hasMore; } @@ -2876,8 +3229,13 @@ public int getMaxRecordCount(DataRecord record, int cipher, String symbol) { private class HistoryImpl extends History { boolean forceRetrieveUpdate; - HistoryImpl(Builder builder) { - super(builder); + HistoryImpl(Builder builder) { + super(builder, new RecordOnlyFilter(builder.getScheme()) { + @Override + public boolean acceptRecord(DataRecord record) { + return !unconflated; + } + }); } @Override diff --git a/qd-core/src/test/java/com/devexperts/qd/test/HistoryAddRemoveSnapshotTest.java b/qd-core/src/test/java/com/devexperts/qd/test/HistoryAddRemoveSnapshotTest.java index 278e99afc..26dc5ae96 100644 --- a/qd-core/src/test/java/com/devexperts/qd/test/HistoryAddRemoveSnapshotTest.java +++ b/qd-core/src/test/java/com/devexperts/qd/test/HistoryAddRemoveSnapshotTest.java @@ -45,7 +45,7 @@ public class HistoryAddRemoveSnapshotTest extends TestCase { private static final PentaCodec CODEC = PentaCodec.INSTANCE; private static final DataScheme SCHEME = new DefaultScheme(CODEC, RECORD); - private final QDHistory history = QDFactory.getDefaultFactory().createHistory(SCHEME); + private final QDHistory history = QDFactory.getDefaultFactory().historyBuilder().withScheme(SCHEME).build(); private final QDDistributor distributor = history.distributorBuilder().build(); private QDAgent agent = history.agentBuilder().build(); diff --git a/qd-dataextractor/pom.xml b/qd-dataextractor/pom.xml index 67371b126..8f61ee1b4 100644 --- a/qd-dataextractor/pom.xml +++ b/qd-dataextractor/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/qd-logger/pom.xml b/qd-logger/pom.xml index b76fa3918..42e8129a0 100644 --- a/qd-logger/pom.xml +++ b/qd-logger/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qd-nio/pom.xml b/qd-nio/pom.xml index 8fe4ed17e..287564b78 100644 --- a/qd-nio/pom.xml +++ b/qd-nio/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qd-rmi/pom.xml b/qd-rmi/pom.xml index 0a91bce4f..c94a7478b 100644 --- a/qd-rmi/pom.xml +++ b/qd-rmi/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qd-sample/pom.xml b/qd-sample/pom.xml index c1d958969..c81147f9a 100644 --- a/qd-sample/pom.xml +++ b/qd-sample/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qd-sample/src/test/java/com/devexperts/qd/test/FieldAdaptationTest.java b/qd-sample/src/test/java/com/devexperts/qd/test/FieldAdaptationTest.java index c8b1e0029..8834f5d31 100644 --- a/qd-sample/src/test/java/com/devexperts/qd/test/FieldAdaptationTest.java +++ b/qd-sample/src/test/java/com/devexperts/qd/test/FieldAdaptationTest.java @@ -19,40 +19,224 @@ import com.devexperts.qd.kit.CompactIntField; import com.devexperts.qd.kit.DecimalField; import com.devexperts.qd.kit.DefaultRecord; +import com.devexperts.qd.kit.LongField; +import com.devexperts.qd.kit.PlainIntField; +import com.devexperts.qd.kit.TimeMillisField; +import com.devexperts.qd.kit.TimeSecondsField; +import com.devexperts.qd.kit.VoidIntField; +import com.devexperts.qd.kit.WideDecimalField; import com.devexperts.qd.ng.RecordBuffer; import com.devexperts.qd.ng.RecordCursor; import com.devexperts.qd.qtp.BinaryQTPComposer; import com.devexperts.qd.qtp.BinaryQTPParser; import com.devexperts.qd.qtp.MessageConsumerAdapter; import com.devexperts.qd.qtp.MessageType; -import com.devexperts.qd.util.Decimal; -import junit.framework.TestCase; +import com.devexperts.util.TimeFormat; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; -public class FieldAdaptationTest extends TestCase { +import java.util.Arrays; +import java.util.function.BiFunction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +@RunWith(Parameterized.class) +public class FieldAdaptationTest { + static final String RECORD = "Test"; static final String SYMBOL = "TEST"; - static final int VALUE = 123000; - static final DefaultRecord srcRecord = new DefaultRecord(0, "rec", false, new DataIntField[] {new CompactIntField(0, "rec.i0")}, null); - static final DataScheme srcScheme = new TestDataScheme(1, 0, TestDataScheme.Type.SIMPLE, srcRecord); + // Variable names are fitted into 4 characters for better alignment + static final BiFunction PINT = PlainIntField::new; + static final BiFunction CINT = CompactIntField::new; + static final BiFunction TINY = DecimalField::new; + static final BiFunction WIDE = WideDecimalField::new; + static final BiFunction LONG = LongField::new; + static final BiFunction SECS = TimeSecondsField::new; + static final BiFunction MILS = TimeMillisField::new; + + static final String TIME_SECONDS = TimeFormat.DEFAULT.withTimeZone().format(1_000); + static final String TIME_MILLIS = TimeFormat.DEFAULT.withTimeZone().withMillis().format(1_000); + + private final DataIntField fromField; + private final String fromValue; + private final DataIntField toField; + private final String toValue; + + @Parameterized.Parameters(name="({index}) {4},from={2},to={3}") + public static Iterable parameters() { + return Arrays.asList(new Object[][] { + { PINT, PINT, 1, 1, "int->int" }, + { PINT, CINT, 1, 1, "int->compact int" }, + { PINT, TINY, 1, 1, "int->decimal" }, + { PINT, WIDE, 1, 1, "int->wide decimal" }, + { PINT, LONG, 1, 1, "int->long" }, + + { PINT, PINT, -1, -1, "int->int" }, + { PINT, CINT, -1, -1, "int->compact int" }, + { PINT, TINY, -1, -1, "int->decimal" }, + { PINT, WIDE, -1, -1, "int->wide decimal" }, + { PINT, LONG, -1, -1, "int->long" }, + + { CINT, PINT, 123456, 123456, "compact int->int" }, + { CINT, CINT, 123456, 123456, "compact int->compact int" }, + { CINT, TINY, 123456, 123456, "compact int->decimal" }, + { CINT, WIDE, 123456, 123456, "compact int->wide decimal" }, + { CINT, LONG, 123456, 123456, "compact int->long" }, + + { TINY, PINT, "1", 1, "decimal->int" }, + { TINY, CINT, "1", 1, "decimal->compact int" }, + { TINY, TINY, "1", "1", "decimal->decimal" }, + { TINY, WIDE, "1", "1", "decimal->wide decimal" }, + { TINY, LONG, "1", 1L, "decimal->long" }, + + { TINY, PINT, "-1", -1, "decimal->int" }, + { TINY, CINT, "-1", -1, "decimal->compact int" }, + { TINY, TINY, "-1", "-1", "decimal->decimal" }, + { TINY, WIDE, "-1", "-1", "decimal->wide decimal" }, + { TINY, LONG, "-1", -1L, "decimal->long" }, + + { TINY, PINT, "NaN", 0, "decimal->int" }, + { TINY, CINT, "NaN", 0, "decimal->compact int" }, + { TINY, TINY, "NaN", "NaN", "decimal->decimal" }, + { TINY, WIDE, "NaN", "NaN", "decimal->wide decimal" }, + { TINY, LONG, "NaN", 0L, "decimal->long" }, + + // Tiny Decimal Infinity conversion + { TINY, PINT, "Infinity", Integer.MAX_VALUE, "decimal->int" }, + { TINY, CINT, "Infinity", Integer.MAX_VALUE, "decimal->compact int" }, + { TINY, TINY, "Infinity", "Infinity", "decimal->decimal" }, + { TINY, WIDE, "Infinity", "Infinity", "decimal->wide decimal" }, + { TINY, LONG, "Infinity", Long.MAX_VALUE, "decimal->long" }, + + // Tiny Decimal -Infinity conversion + { TINY, PINT, "-Infinity", Integer.MIN_VALUE, "decimal->int" }, + { TINY, CINT, "-Infinity", Integer.MIN_VALUE, "decimal->compact int" }, + { TINY, TINY, "-Infinity", "-Infinity", "decimal->decimal" }, + { TINY, WIDE, "-Infinity", "-Infinity", "decimal->wide decimal" }, + { TINY, LONG, "-Infinity", Long.MIN_VALUE, "decimal->long" }, + + { WIDE, PINT, "1", "1", "wide decimal->int" }, + { WIDE, CINT, "1", "1", "wide decimal->compact int" }, + { WIDE, TINY, "1", "1", "wide decimal->decimal" }, + { WIDE, WIDE, "1", "1", "wide decimal->wide decimal" }, + { WIDE, LONG, "1", "1", "wide decimal->long" }, + + { WIDE, PINT, "-1", "-1", "wide decimal->int" }, + { WIDE, CINT, "-1", "-1", "wide decimal->compact int" }, + { WIDE, TINY, "-1", "-1", "wide decimal->decimal" }, + { WIDE, WIDE, "-1", "-1", "wide decimal->wide decimal" }, + { WIDE, LONG, "-1", "-1", "wide decimal->long" }, + + { WIDE, PINT, "NaN", 0, "wide decimal->int" }, + { WIDE, CINT, "NaN", 0, "wide decimal->compact int" }, + { WIDE, TINY, "NaN", "NaN", "wide decimal->decimal" }, + { WIDE, WIDE, "NaN", "NaN", "wide decimal->wide decimal" }, + { WIDE, LONG, "NaN", 0L, "wide decimal->long" }, + + // Wide Decimal Infinity conversion + // Note that (int) Long.MAX_VALUE == -1 + { WIDE, PINT, "Infinity", (int)Long.MAX_VALUE, "wide decimal->int" }, + { WIDE, CINT, "Infinity", (int)Long.MAX_VALUE, "wide decimal->compact int" }, + { WIDE, TINY, "Infinity", "Infinity", "wide decimal->decimal" }, + { WIDE, WIDE, "Infinity", "Infinity", "wide decimal->wide decimal" }, + { WIDE, LONG, "Infinity", Long.toString(Long.MAX_VALUE), "wide decimal->long" }, - static final DefaultRecord dstRecord = new DefaultRecord(0, "rec", false, new DataIntField[] {new DecimalField(0, "rec.i0")}, null); - static final DataScheme dstScheme = new TestDataScheme(1, 0, TestDataScheme.Type.SIMPLE, dstRecord); + // Wide Decimal -Infinity conversion + // Note that (int) Long.MIN_VALUE == 0 + { WIDE, PINT, "-Infinity", (int)Long.MIN_VALUE, "wide decimal->int" }, + { WIDE, CINT, "-Infinity", (int)Long.MIN_VALUE, "wide decimal->compact int" }, + { WIDE, TINY, "-Infinity", "-Infinity", "wide decimal->decimal" }, + { WIDE, WIDE, "-Infinity", "-Infinity", "wide decimal->wide decimal" }, + { WIDE, LONG, "-Infinity", Long.MIN_VALUE, "wide decimal->long" }, + + // Truncation & Precision Loss + { WIDE, PINT, "1.23456789", "1", "wide decimal->int" }, + { WIDE, CINT, "1.23456789", "1", "wide decimal->compact int" }, + { WIDE, TINY, "1.23456789", "1.2345679", "wide decimal->decimal" }, + { WIDE, WIDE, "1.23456789", "1.23456789", "wide decimal->wide decimal" }, + { WIDE, LONG, "1.23456789", "1", "wide decimal->long" }, + + { LONG, PINT, 1L, 1, "long->int" }, + { LONG, CINT, 1L, 1, "long->compact int" }, + { LONG, TINY, 1L, "1", "long->decimal" }, + { LONG, WIDE, 1L, "1", "long->wide decimal" }, + { LONG, LONG, 1L, 1L, "long->long" }, + + { LONG, PINT, -1L, -1, "long->int" }, + { LONG, CINT, -1L, -1, "long->compact int" }, + { LONG, TINY, -1L, "-1", "long->decimal" }, + { LONG, WIDE, -1L, "-1", "long->wide decimal" }, + { LONG, LONG, -1L, -1L, "long->long" }, + + // Truncation & Precision Loss + { LONG, PINT, Integer.MAX_VALUE + 1L, Integer.MIN_VALUE, "long->int" }, + { LONG, CINT, Integer.MAX_VALUE + 1L, Integer.MIN_VALUE, "long->compact int" }, + { LONG, TINY, Integer.MAX_VALUE + 1L, "2147483600", "long->decimal" }, + { LONG, WIDE, Integer.MAX_VALUE + 1L, Integer.MAX_VALUE + 1L, "long->wide decimal" }, + { LONG, LONG, Integer.MAX_VALUE + 1L, Integer.MAX_VALUE + 1L, "long->long" }, + + // Time + { SECS, PINT, "0", "0", "time seconds->int" }, + { SECS, LONG, "0", "0", "time seconds->long" }, + { SECS, PINT, "19700101-000001+0000", "1", "time seconds->int" }, + { SECS, LONG, "19700101-000001+0000", "1", "time seconds->long" }, + { LONG, SECS, 0L, "0", "long->time seconds" }, + { LONG, SECS, 1L, TIME_SECONDS, "long->time seconds" }, + + { MILS, PINT, "0", "0", "time millis->long" }, + { MILS, LONG, "0", "0", "time millis->long" }, + { MILS, PINT, "19700101-000001.000+0000", "1000", "time millis->int" }, + { MILS, LONG, "19700101-000001.000+0000", "1000", "time millis->long" }, + { LONG, MILS, 0L, "0", "long->time millis" }, + { LONG, MILS, 1000L, TIME_MILLIS, "long->time millis" }, + + { SECS, MILS, "0", "0", "time seconds->time millis" }, + { SECS, MILS, TIME_SECONDS, TIME_MILLIS, "time seconds->time millis" }, + { MILS, SECS, TIME_MILLIS, TIME_SECONDS, "time millis->time seconds" }, + { MILS, SECS, TIME_MILLIS, TIME_SECONDS, "time millis->time seconds" }, + }); + } + + public FieldAdaptationTest( + BiFunction fromField, BiFunction toField, + Object fromValue, Object toValue, @SuppressWarnings("unused") String description) + { + this.fromField = field(fromField); + this.toField = field(toField); + this.fromValue = fromValue.toString(); + this.toValue = toValue.toString(); + } + + @Test + public void testFieldConversion() { + DefaultRecord fromRecord = new DefaultRecord(0, RECORD, false, new DataIntField[] { + fromField, new VoidIntField(1, RECORD + ".void") }, null); // use extra void field for long values + DataScheme fromScheme = new TestDataScheme(1, 0, TestDataScheme.Type.SIMPLE, fromRecord); + + DefaultRecord toRecord = new DefaultRecord(0, RECORD, false, new DataIntField[] { + toField, new VoidIntField(1, RECORD + ".void")}, null); // use extra void field for long values + DataScheme toScheme = new TestDataScheme(1, 0, TestDataScheme.Type.SIMPLE, toRecord); + + RecordBuffer fromBuffer = new RecordBuffer(); + RecordCursor fromCursor = fromBuffer.add(fromRecord, fromScheme.getCodec().encode(SYMBOL), SYMBOL); + fromField.setString(fromCursor, fromValue); - public void testFieldAdaptation() { - RecordBuffer srcBuffer = new RecordBuffer(); - RecordCursor srcCursor = srcBuffer.add(srcRecord, srcScheme.getCodec().encode(SYMBOL), SYMBOL); - srcCursor.setInt(0, VALUE); ChunkedOutput output = new ChunkedOutput(); - BinaryQTPComposer composer = new BinaryQTPComposer(srcScheme, true); + BinaryQTPComposer composer = new BinaryQTPComposer(fromScheme, true); composer.setOutput(output); - assertFalse(composer.visitData(srcBuffer, MessageType.STREAM_DATA)); + assertFalse(composer.visitData(fromBuffer, MessageType.STREAM_DATA)); // copy composed chunks to chunked input ChunkedInput input = new ChunkedInput(); input.addAllToInput(output.getOutput(this), this); // parser from this input - BinaryQTPParser parser = new BinaryQTPParser(dstScheme); + BinaryQTPParser parser = new BinaryQTPParser(toScheme); parser.setInput(input); parser.parse(new MessageConsumerAdapter() { @@ -73,14 +257,18 @@ public void handleUnknownMessage(int messageTypeId) { @Override public void processStreamData(DataIterator iterator) { - RecordBuffer dstBuffer = (RecordBuffer) iterator; - RecordCursor dstCursor = dstBuffer.next(); - assertNotNull("no data", dstCursor); - assertEquals("wrong record", dstRecord, dstCursor.getRecord()); - assertEquals("wrong symbol", SYMBOL, dstScheme.getCodec().decode(dstCursor.getCipher(), dstCursor.getSymbol())); - assertEquals("wrong value", VALUE, Decimal.toDouble(dstCursor.getInt(0)), 0); - assertNull("extra data", dstBuffer.next()); + RecordBuffer toBuffer = (RecordBuffer) iterator; + RecordCursor toCursor = toBuffer.next(); + assertNotNull("no data", toCursor); + assertEquals("wrong record", toRecord, toCursor.getRecord()); + assertEquals("wrong symbol", SYMBOL, toScheme.getCodec().decode(toCursor.getCipher(), toCursor.getSymbol())); + assertEquals("wrong value", toValue, toField.getString(toCursor)); + assertNull("extra data", toBuffer.next()); } }); } + + private static DataIntField field(BiFunction constructor) { + return constructor.apply(0, RECORD + ".field"); + } } diff --git a/qd-samplecert/pom.xml b/qd-samplecert/pom.xml index b366c5e61..720fd9b92 100644 --- a/qd-samplecert/pom.xml +++ b/qd-samplecert/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qd-servlet/pom.xml b/qd-servlet/pom.xml index 69a6378d3..7a3d46405 100644 --- a/qd-servlet/pom.xml +++ b/qd-servlet/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qd-stripe/pom.xml b/qd-stripe/pom.xml index cd33127f3..f0e128887 100644 --- a/qd-stripe/pom.xml +++ b/qd-stripe/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qd-tools/pom.xml b/qd-tools/pom.xml index 969a06fdd..af0c68b2e 100644 --- a/qd-tools/pom.xml +++ b/qd-tools/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qds-file/pom.xml b/qds-file/pom.xml index a105a9bcc..20377eeeb 100644 --- a/qds-file/pom.xml +++ b/qds-file/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qds-file/src/main/java/com/devexperts/qd/qtp/fieldreplacer/TimeFieldReplacer.java b/qds-file/src/main/java/com/devexperts/qd/qtp/fieldreplacer/TimeFieldReplacer.java index 31b06e6d1..52a9ffe10 100644 --- a/qds-file/src/main/java/com/devexperts/qd/qtp/fieldreplacer/TimeFieldReplacer.java +++ b/qds-file/src/main/java/com/devexperts/qd/qtp/fieldreplacer/TimeFieldReplacer.java @@ -47,7 +47,7 @@ *
    *
  • current time: replaces time with current. See {@link System#currentTimeMillis()}. * Configuration format: {@code "current"};
  • - *
  • increase/decrese time: increases or decreases time on specified delta. + *
  • increase/decrease time: increases or decreases time on specified delta. * Configuration format: {@code "+
  • *
  • specified time: replaces time with specified. * Configuration format: {@code "
  • diff --git a/qds-monitoring/pom.xml b/qds-monitoring/pom.xml index 5038639f9..5f98516b5 100644 --- a/qds-monitoring/pom.xml +++ b/qds-monitoring/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qds-tools/pom.xml b/qds-tools/pom.xml index 393d270e1..7c0f7555b 100644 --- a/qds-tools/pom.xml +++ b/qds-tools/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/qds/pom.xml b/qds/pom.xml index 29866f960..584c6643f 100644 --- a/qds/pom.xml +++ b/qds/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0 diff --git a/rt-api-builder/pom.xml b/rt-api-builder/pom.xml index d3d832b45..3b2890c87 100644 --- a/rt-api-builder/pom.xml +++ b/rt-api-builder/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 4.0.0 diff --git a/teamcity-version/pom.xml b/teamcity-version/pom.xml index 33206e06f..fb8ef1f72 100644 --- a/teamcity-version/pom.xml +++ b/teamcity-version/pom.xml @@ -14,7 +14,7 @@ QD com.devexperts.qd - 3.291 + 3.292 ../pom.xml 4.0.0