Chronicle-Values
About
Generation of constantly-sized flyweight accessors to Chronicle Bytes and simple bean-style on-heap implementations from interfaces. Interfaces, that could be processed by Chronicle-Values generation, are called value interfaces.
Project status: Alpha, the feature matrix (see below) is very vast and not fully implemented yet, please write the value interface according to the specification below, with the unit tests for your interface. If the generation from the value interface (according to spec) doesn’t work, please report the case via issues on Github.
Value interface specification
An important pre-requirement for value interfaces: they should belong to some package with a non-empty name, not the default package.
Simple example:
package test;
interface Balance {
long getDollars();
void setDollars(long dollars);
long addDollars(long addition);
int getCents();
void setCents(int cents);
}
is processed to either a flyweight of 12 bytes, or a bean class with long dollars;
and
int cents;
fields.
Supported field types
All Java primitives
int
, long
, float
, double
, byte
, boolean
, char
, short
String
or CharSequence
Must be annotated with @MaxUtf8Length()
, like:
interface Client {
CharSequence getName();
void setName(@MaxUtf8Length(20) CharSequence name);
CharSequence getStateCode();
void setStateCode(@NotNull @MaxUtf8Length(2) CharSequence stateCode);
}
Another value interface
This allows to build nested structures:
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.
enum
type
Any Java interface Order {
enum State {NEW, CANCELLED, FILLED}
State getState();
void setState(@NotNull State state);
}
java.util.Date
Array fields
Of any of the above types, with special syntax: -At
suffix and first parameter of all methods
should be int index
.
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]
, is<FieldName>[At]
- simple get/set. For boolean
fields, isFoo()
Java bean syntax variation is supported. Also, get-
and set-
prefixes could be
omitted, e. g.
interface Point {
double x();
void x(double x);
double y();
void y(double y);
}
Volatile get/set
getVolatile<FieldName>[At]
, setVolatile<FieldName>[At]
"Ordered" set
setOrdered<FieldName>[At]
- ordered write operation, the same as behind AtomicInteger.lazySet()
Simple add
type add<FieldName>[At]([int index, ]type addition)
- equivalent of
int foo = getFoo();
foo += addition;
setFoo(foo);
return foo;
works only with numeric primitive field types: byte
, char
, short
, int
, long
, double
,
float
Atomic add
type addAtomic<FieldName>[At]([int index, ]type addition)
- same as add
, operates via atomic
operations, works only with numeric primitive field types.
Compare-and-swap
boolean compareAndSwap<FieldName>[At]([int index, ]type expectedValue, type newValue)
- atomic
field value exchange, returns true
if successfully swapped the value. Works only with primitive,
enum
and Date
field types.
getUsing
getUsing<FieldName>[At]([int index, ]Type using)
- for String
, CharSequence
or another value
interface field types. Reads the value into the given on-heap object. Primarily useful for
retrieving data from flyweight implementations without creating garbage.
If the field type is String
or CharSequence
, using
parameter type must be StringBuilder
.
Return type of the getUsing
method in this case might be CharSequence
, StringBuilder
, String
or void
, if this char sequence field is marked as @NotNull
. Semantically this method is
equivalent to
CharSequence getUsingName(StringBuilder using) {
using.setLength(0);
CharSequence name = getName();
if (name != null) {
using.append(name);
return using;
} else {
return null;
}
}
Note that the StringBuilder
is cleared via setLength(0)
before reusing.
If the field type is another value interface field, using
parameter type is the value interface,
the return type of the method could be the interface or void
. See getUsingCenter(Point using)
in
the example above.
Table of supported methods (type of field × type of method)
Integer type: byte ..long |
float , double |
boolean |
Char sequence | Value interface | enum type |
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
Field order is unspecified. To ensure some order, put @Group
annotations on any of field’s
methods, for example:
interface Complex {
@Group(1)
double real();
void real(double real);
@Group(2)
double image();
void image(double image);
}
Groups are ordered in the ascending order of their argument numbers. In the above case, the
generated flyweight implementation will place real
field at 0-7 bytes and image
field at 8-15
bytes from it’s offset.
Field nullability
By default, enum
and String
/CharSequence
fields are nullable. Annotate them with
@net.openhft.chronicle.values.NotNull
to forbid null
values:
interface Instrument {
CharSequence getSymbol();
void setSymbol(@NotNull @MaxUtf8Length(5) CharSequence symbol);
}
Numeric field ranges
Annotate numeric fields with @Range(min=, max=)
to save space in flyweight implementation, e. g.
interface Transaction {
int getSecondFromDayStart();
void setSecondFromDayStart(@Range(min = 0, max = 24 * 60 * 60) int secondFromDayStart);
}
The field SecondFromDayStart
could take only 17 bits in bytes, instead of 32.
Field alignment
For flyweight implementation, you might need to align certain fields, to ensure some properties of reads and writes. For example, you might want to ensure, that a certain field doesn’t cross cache line boundary:
interface Message {
...many fields
@Align(dontCross=64)
long getImportantField();
void setImportantField(long importantValue);
}
See @Align
and @Array
annotations Javadocs
for more information.
Use
// 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);
The generated on-heap and flyweight classes do implement:
- Copyable<Point>
, to allow easy data exchange: onHeapPoint.copyFrom(offHeapPoint)
- BytesMarshallable
from Chronicle Bytes
- Proper equals()
, hashCode()
and toString()
- Byteable
, but on-heap implementation is dummy, throws UnsupportedOperationException
For convenience, you could make the value interface to extend the above utility interfaces, to avoid casting:
interface Point extends Byteable, BytesMarshallable, Copyable { ... }
Point offHeapPoint = Values.newNativeReference(Point.class);
// no cast
offHeapPoint.bytesStore(bytesStore, offset, offHeapPoint.maxSize());