https://github.com/openhft/chronicle-values
https://github.com/openhft/chronicle-values
Last synced: 5 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/openhft/chronicle-values
- Owner: OpenHFT
- License: other
- Created: 2015-02-24T13:44:32.000Z (over 11 years ago)
- Default Branch: ea
- Last Pushed: 2024-05-29T07:45:52.000Z (about 2 years ago)
- Last Synced: 2024-05-29T07:50:54.292Z (about 2 years ago)
- Language: Java
- Homepage: http://chronicle.software
- Size: 668 KB
- Stars: 103
- Watchers: 32
- Forks: 38
- Open Issues: 4
-
Metadata Files:
- Readme: README.adoc
- License: LICENSE.adoc
Awesome Lists containing this project
README
= Chronicle-Values
Chronicle Software
:css-signature: demo
:toc: macro
:toclevels: 3
image:https://maven-badges.herokuapp.com/maven-central/net.openhft/chronicle-values/badge.svg[link=https://maven-badges.herokuapp.com/maven-central/net.openhft/chronicle-values]
image:https://javadoc.io/badge2/net.openhft/chronicle-values/javadoc.svg[link=https://www.javadoc.io/doc/net.openhft/chronicle-values/latest/index.html]
image:https://img.shields.io/badge/release%20notes-subscribe-brightgreen[link=https://chronicle.software/release-notes/]
image:https://sonarcloud.io/api/project_badges/measure?project=OpenHFT_Chronicle-Values&metric=alert_status[link=https://sonarcloud.io/dashboard?id=OpenHFT_Chronicle-Values]
toc::[]
== About
Generation of constantly-sized flyweight accessors to https://github.com/OpenHFT/Chronicle-Bytes[Chronicle Bytes] and simple bean-style on-heap implementations from interfaces.
Interfaces that can be processed by Chronicle-Values generation are called *value interfaces*.
*Project status: Stable* - the feature matrix (see below) is very vast and not expected to be implemented further.
Please write the value interface according to the specification below, with *unit tests for your interface*.
If the generation from the value interface (according to spec) doesn't work, please report the case via GitHub issues.
All classes in the package `net.openhft.chronicle.values.internal` and its subpackages are internal implementation details.
They may change without notice and must not be referenced directly.
=== Value interface specification
Value interfaces must belong to a non-empty package (they cannot sit in the default package).
.Simple example
[source,java]
----
package test;
interface Balance {
long getDollars();
void setDollars(long dollars);
long addDollars(long addition);
int getCents();
void setCents(int cents);
}
----
The above is compiled into either a 12-byte flyweight or a regular bean with `long dollars` and `int cents`.
==== Supported field types
===== All Java primitives
`byte`, `boolean`, `char`, `short`, `int`, `long`, `float`, `double`
===== String or CharSequence
Must be annotated with `@MaxUtf8Length`:
[source,java]
----
interface Client {
CharSequence getName();
void setName(@MaxUtf8Length(20) CharSequence name);
CharSequence getStateCode();
void setStateCode(@NotNull @MaxUtf8Length(2) CharSequence stateCode);
}
----
The value supplied to `@MaxUtf8Length` is the allowed size once the text is encoded as UTF-8. The native form checks this limit when writing to the backing `BytesStore`.
Heap implementations assume the caller supplies a compliant value and any breach is detected only when copied to a native instance.
===== Another value interface
Allows nested structures:
[source,java]
----
interface Point {
double getX();
void setX(double x);
double getY();
void setY(double y);
}
interface Circle {
Point getCenter();
void getUsingCenter(Point using);
void setCenter(Point center);
double getRadius();
void setRadius(double radius);
}
----
(Self-references are forbidden.)
===== Any Java enum type
[source,java]
----
interface Order {
enum State { NEW, CANCELLED, FILLED }
State getState();
void setState(@NotNull State state);
}
----
The generated classes keep a reference to the array of constants for each enum field.
The `Enums` helper obtains this array via reflection from `EnumSet.getUniverse(Class)`.
Caching the array lets the marshalling code translate ordinals without calling `values()` on every access.
===== java.util.Date
===== Array fields
Use the `-At` suffix and make the first parameter `int index`:
[source,java]
----
interface SomeStats {
@Array(length = 100)
long getPercentFreqAt(int index);
void setPercentFreqAt(int index, long percentFreq);
long addPercentFreqAt(int index, long addition);
}
----
==== Supported methods
===== Simple get/set
`[get]FieldName[At]`, `[set]FieldName[At]`, or `isFieldName` for boolean fields.
You may omit the `get`/`set` prefixes:
[source,java]
----
interface Point {
double x();
void x(double x);
double y();
void y(double y);
}
----
===== Volatile get/set
`getVolatileFieldName[At]`, `setVolatileFieldName[At]`
===== Ordered set
`setOrderedFieldName[At]` (similar to `AtomicInteger.lazySet`)
===== Simple add
`type addFieldName[At]([int index, ]type addition)`
Works for numeric primitives.
===== Atomic add
`type addAtomicFieldName[At]([int index, ]type addition)`
Atomic variant for numeric primitives.
===== Compare-and-swap
`boolean compareAndSwapFieldName[At]([int index, ]type expected, type newValue)`
Works for primitives, enums, and `Date`.
===== getUsing
`getUsingFieldName[At]([int index, ]Type using)`
For `String`/`CharSequence`, `using` must be a `StringBuilder`.
For nested value interfaces, `using` is that interface.
==== Table of supported methods
[cols="1,^1,^1,^1,^1,^1,^1,^1",options="header"]
|===
| | Integer (`byte`-`long`) | `float`, `double` | `boolean` | Char sequence | Value interface | `enum` | `Date`
| get / set
| ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓
| Volatile get/set, ordered set
| ✓ | ✓ | ✓ | | | ✓ | ✓
| Compare-and-swap
| ✓ | ✓ | ✓ | | | ✓ | ✓
| Simple add, atomic add
| ✓ | ✓ | | | | |
| getUsing
| | | | ✓ | ✓ | |
|===
=== Field configuration via annotations
==== Field ordering in flyweight layout
Fields are laid out group by group.
Use `@Group` to cluster fields and control their ordering.
Fields without `@Group` form the default group, which always comes first:
[source,java]
----
interface Complex {
@Group(1)
double real();
void real(double real);
@Group(2)
double imaginary();
void imaginary(double imaginary);
}
----
==== Field nullability
`enum` and string fields are nullable by default; mark with `@NotNull` to forbid `null`:
[source,java]
----
interface Instrument {
CharSequence getSymbol();
void setSymbol(@NotNull @MaxUtf8Length(5) CharSequence symbol);
}
----
When a field is nullable the generated native form stores a marker instead of the text.
Heap implementations use an extra boolean for `CharSequence` fields to track a null value.
With `@NotNull` the setters emit a `NullPointerException` if given `null`.
==== Numeric field ranges
Annotate with `@Range(min=, max=)` to reduce flyweight size:
[source,java]
----
interface Transaction {
int getSecondFromDayStart();
void setSecondFromDayStart(@Range(min = 0, max = 24 * 60 * 60) int secondFromDayStart);
}
----
==== Field alignment
Ensure a field does not cross a cache-line boundary, for example:
[source,java]
----
interface Message {
// ... other fields ...
@Align(dontCross = 64)
long getImportantField();
void setImportantField(long importantValue);
}
----
See the Javadocs of `@Align` and `@Array` for details.
== Using Chronicle Values
[source,java]
----
// Flyweight
Point offHeapPoint = Values.newNativeReference(Point.class);
((Byteable) offHeapPoint).bytesStore(bytesStore, offset, 16);
offHeapPoint.setX(0);
offHeapPoint.setY(0);
// On-heap
Point onHeapPoint = Values.newHeapInstance(Point.class);
onHeapPoint.setX(1);
onHeapPoint.setY(2);
----
Generated classes implement:
* `Copyable` - e.g. `onHeapPoint.copyFrom(offHeapPoint)`
* `BytesMarshallable` (Chronicle-Bytes)
* Correct `equals`, `hashCode`, `toString`
* `Byteable` (no-op on the heap implementation)
The `Copyable` feature allows an on-heap value to take a snapshot of a native instance or vice versa.
This provides a simple way to shuttle data between the two representations.
You may extend these in the interface to avoid casting:
[source,java]
----
interface Point extends Byteable, BytesMarshallable, Copyable { ... }
Point offHeapPoint = Values.newNativeReference(Point.class);
// no cast required
offHeapPoint.bytesStore(bytesStore, offset, offHeapPoint.maxSize());
----
== Why Use Chronicle-Values and How It Fits In
Chronicle-Values empowers you to work with structured data with high performance and type safety, especially when interfacing with low-latency and off-heap memory systems.
It achieves this by generating efficient data accessors from Java interfaces.
=== Core Benefits
Chronicle-Values is designed to provide:
* **High Performance & Low Latency:** Access and manipulate data with minimal overhead, crucial for systems where every microsecond counts. This is particularly true when using its "native" (off-heap) implementations.
* **Minimized GC Impact:** Through the use of off-heap 'flyweight' objects, which act as lightweight views onto raw byte data. This significantly reduces garbage collection pressure by avoiding the creation of numerous heap objects.
* **Type Safety:** Define your data structures using standard Java interfaces. This provides compile-time checking, clear API contracts, and reduces runtime errors.
* **Developer Convenience:** The library automatically generates the necessary accessor boilerplate (getters, setters, etc.) based on your interface definition and intuitive annotations. This saves development time and reduces manual coding errors.
* **"Zero-Deserialization" Access:** Read data directly from memory without the traditional cost of hydrating full heap objects, allowing for faster processing.
* **Seamless Integration:** Designed to work smoothly with other Chronicle libraries like Chronicle Map (for off-heap key-value stores) and Chronicle Queue (for low-latency messaging), enabling efficient data storage and exchange.
=== Primary Use Cases
Chronicle-Values is ideal when you need to work with data that has a **fixed, well-defined structure at compile time**.
Common scenarios include:
* Representing financial messages (e.g., FIX, SBE, or custom binary protocols).
* Defining event payloads for event-sourcing architectures.
* Creating complex, fixed-layout keys or values for use with Chronicle Map or Chronicle Queue.
* Any situation where you need performant, typed access to structured binary data, whether on-heap or off-heap.
=== How Chronicle-Values Complements Chronicle-Bytes
Chronicle-Values operates on top of Chronicle-Bytes, which provides the foundational layer for byte manipulation.
While Values offers a higher-level, typed abstraction for _structured_ data, Chronicle-Bytes remains essential for:
* Low-level, direct manipulation of byte sequences.
* Implementing custom serialization or deserialization logic for arbitrary formats.
* Parsing and constructing binary network protocols from scratch.
* Handling dynamic, unstructured, or schema-less binary data.
**Typical Workflow:**
A common pattern is to use Chronicle-Bytes for initial I/O operations (e.g., reading from a network socket or a file) or to parse the outer envelope of a raw data stream.
Once you've identified a segment of that stream that corresponds to a known, fixed structure defined by a value interface, you can then map a Chronicle-Values native reference (flyweight) over that region for typed, convenient, and performant access.
=== Heap vs. Native (Flyweight) Implementations
For every value interface, Chronicle-Values can generate two types of implementations:
* **Native (Flyweight) References (`Values.newNativeReference(..)`):**
** These are lightweight views onto a `BytesStore` (which can be on-heap or off-heap memory).
** They provide maximum performance and are central to off-heap data strategies.
** They do not store data themselves; they merely provide typed accessors to the underlying bytes.
** Careful management of the backing `BytesStore`'s lifecycle is crucial (see section on Lifecycle Management).
* **Heap Instances (`Values.newHeapInstance(..)`):**
** These are standard Java objects (POJOs) that store their data on the Java heap.
** Useful for interacting with other parts of your system that expect POJOs, or when off-heap performance is not the primary concern for a particular data structure.
** Data can be easily copied between native and heap instances using the generated `copyFrom()` method (from the `Copyable` interface).