Class FieldType<T>
- java.lang.Object
-
- io.permazen.core.FieldType<T>
-
- Type Parameters:
T
- The associated Java type
- All Implemented Interfaces:
Serializable
,Comparator<T>
- Direct Known Subclasses:
NonNullFieldType
,NullSafeType
public abstract class FieldType<T> extends Object implements Comparator<T>, Serializable
Defines the encoding, ordering, and range of possible values for aSimpleField
.A
FieldType
maps between instances of its supported Java type and the self-delimitedbyte[]
encoding of those instances used in the database. Thebyte[]
encoding also implicitly defines the database sort order (via unsigned lexicographical ordering). This ordering is also reflected viacompare()
.A
FieldType
also defines a mapping between Java instances andString
values. There are two separateString
forms, a regular form and a self-delimiting form.FieldType
s have these requirements and properties:- They have a unique name; typically the same as their supported type.
- All possible values can be represented in Java as an instance of the associated Java type (possibly including null).
- Instances totally order their Java values. If the associated Java type implements
Comparable
, then the two orderings do not necessarily have to agree, but they should if possible. - All possible values can be encoded/decoded into a self-delimiting binary string (i.e.,
byte[]
array) without losing information, and these binary strings, when sorted lexicographically using unsigned comparison, sort consistently with the total ordering of the corresponding Java values. - All possible values can be encoded/decoded to/from
String
s without losing information, with both a regular string form for non-null values and a self-delimiting string form for any value including null (these two forms may be the same). null
may or may not be a supported value; if so, it must be handled bycompare()
(typically null values sort last) and have binary and string encodings just like any other value.- There is a default value. For types that support null, the default value must be null.
- An optional encoding signature protects against incompatible encodings
when a
FieldType
's binary or string encoding changes without changing the name.
Two
FieldType
instances should be equal according toequals()
if only if they behave identically with respect to all of the above.A
FieldTypeRegistry
contains a registry ofFieldType
s indexed by name.Instances are
Serializable
if their default values are (typically the default value is null, making this the case).- See Also:
FieldTypeRegistry
, Serialized Form
-
-
Method Summary
All Methods Instance Methods Abstract Methods Concrete Methods Modifier and Type Method Description abstract int
compare(T value1, T value2)
Order two field values.<S> T
convert(FieldType<S> type, S value)
boolean
equals(Object obj)
abstract T
fromParseableString(ParseContext context)
Parse a value previously encoded bytoParseableString()
as a self-delimitedString
and positioned at the start of the given parsing context.T
fromString(String string)
Parse a non-null value previously encoded bytoString(T)
.FieldType<T>
genericizeForIndex()
Remove any information that may differ between instances associated with the same indexed field in the same schema.byte[]
getDefaultValue()
Get the default value for this field type encoded as abyte[]
array.T
getDefaultValueObject()
Get the default value for this field type.long
getEncodingSignature()
Get the binary encoding signature of this type.KeyRange
getKeyRange(Bounds<? extends T> bounds)
Calculate theKeyRange
that includes exactly those encoded values that lie within the given bounds.String
getName()
Get the name of this type.TypeToken<T>
getTypeToken()
Get the Java type corresponding to this type's values.int
hashCode()
boolean
hasPrefix0x00()
Determine whether any of this field type's encoded values start with a0x00
byte.boolean
hasPrefix0xff()
Determine whether any of this field type's encoded values start with a0xff
byte.abstract T
read(ByteReader reader)
Read a value from the given input.abstract void
skip(ByteReader reader)
Read and discard a value from the given input.abstract String
toParseableString(T value)
Encode a possibly null value as aString
for later decoding byfromParseableString()
.String
toString()
String
toString(T value)
Encode a non-null value as aString
for later decoding byfromString()
.T
validate(Object obj)
Verify the given object is a valid instance of thisFieldType
's Java type and cast it to that type.void
validateAndWrite(ByteWriter writer, Object obj)
Convenience method that both validates and encodes a value.abstract void
write(ByteWriter writer, T value)
Write a value to the given output.-
Methods inherited from class java.lang.Object
clone, finalize, getClass, notify, notifyAll, wait, wait, wait
-
Methods inherited from interface java.util.Comparator
reversed, thenComparing, thenComparing, thenComparing, thenComparingDouble, thenComparingInt, thenComparingLong
-
-
-
-
Field Detail
-
NAME_PATTERN
public static final String NAME_PATTERN
The regular expression thatFieldType
names must match. This pattern is the same as is required for Java identifiers, except that the following additional characters are allowed after the first character: dot (`.') and dash (`-'), and lastly up to 255 pairs of square brackets (`[]') to indicate an array type.- See Also:
- Constant Field Values
-
REFERENCE_TYPE_NAME
public static final String REFERENCE_TYPE_NAME
Type name for reference types.- See Also:
- Constant Field Values
-
name
protected final String name
-
signature
protected final long signature
-
-
Constructor Detail
-
FieldType
protected FieldType(String name, TypeToken<T> typeToken, long signature, T defaultValue)
Constructor.- Parameters:
name
- the name of this typetypeToken
- Java type for the field's valuessignature
- binary encoding signaturedefaultValue
- default value for this type- Throws:
IllegalArgumentException
- if any parameter is nullIllegalArgumentException
- ifname
is invalid
-
FieldType
protected FieldType(Class<T> type, long signature, T defaultValue)
- Parameters:
type
- Java type for the field's valuessignature
- binary encoding signaturedefaultValue
- default value for this type- Throws:
NullPointerException
- iftype
is null
-
-
Method Detail
-
getName
public String getName()
Get the name of this type.The combination of
FieldType
name and signature must be unique in aFieldTypeRegistry
.- Returns:
- this type's name
- See Also:
JField.type()
,JField.typeSignature()
-
getTypeToken
public TypeToken<T> getTypeToken()
Get the Java type corresponding to this type's values.- Returns:
- this type's Java type
-
getEncodingSignature
public long getEncodingSignature()
Get the binary encoding signature of this type.The binary encoding signature is analogous to the
serialVersionUID
used by Java serialization. It represents a specific binary and/orString
encoding for Java values. In the case that aFieldType
implementation changes its binary encoding, but not it's name, it must use a new, different binary encoding signature to eliminate the possibility of mixing incompatible encodings in software vs. persistent storage. Typically a value of zero is used until if/when such a change occurs.Note that another option when encodings change is simply to change the name of the type any case, the combination of
FieldType
name and signature must be unique in aFieldTypeRegistry
.- Returns:
- binary encoding signature
- See Also:
JField.type()
,JField.typeSignature()
-
read
public abstract T read(ByteReader reader)
Read a value from the given input.- Parameters:
reader
- byte input- Returns:
- field value (possibly null)
- Throws:
IllegalArgumentException
- if invalid input is encounteredIndexOutOfBoundsException
- if input is truncatedIllegalArgumentException
- ifreader
is null
-
write
public abstract void write(ByteWriter writer, T value)
Write a value to the given output.- Parameters:
writer
- byte outputvalue
- value to write (possibly null)- Throws:
IllegalArgumentException
- ifvalue
is null and this type does not support nullIllegalArgumentException
- ifwriter
is null
-
getDefaultValue
public final byte[] getDefaultValue()
Get the default value for this field type encoded as abyte[]
array.- Returns:
- encoded default value
-
getDefaultValueObject
public final T getDefaultValueObject()
Get the default value for this field type.- Returns:
- default value
-
skip
public abstract void skip(ByteReader reader)
Read and discard a value from the given input.- Parameters:
reader
- byte input- Throws:
IllegalArgumentException
- if invalid input is encounteredIndexOutOfBoundsException
- if input is truncatedIllegalArgumentException
- ifreader
is null
-
toString
public String toString(T value)
Encode a non-null value as aString
for later decoding byfromString()
.Each of the characters in the returned
String
must be one of the valid XML characters (tab, newline, carriage return,\u0020 - \ud7ff
, and\ue000 - \fffdf
).The implementation in
FieldType
checks thatvalue
is not null, then delegates totoParseableString(T)
. Subclasses that override this method should also overridefromString()
.- Parameters:
value
- actual value, never null- Returns:
- string encoding of
value
acceptable tofromString()
- Throws:
IllegalArgumentException
- ifvalue
is null- See Also:
- The XML 1.0 Specification
-
fromString
public T fromString(String string)
Parse a non-null value previously encoded bytoString(T)
.The implementation in
FieldType
creates a newParseContext
based onstring
, delegates totoParseableString(T)
to parse it, and verifies that all ofstring
was consumed during the parse. Subclasses that override this method should also overridetoString(T)
.- Parameters:
string
- non-null value previously encoded as aString
bytoString(T)
- Returns:
- actual value
- Throws:
IllegalArgumentException
- if the input is invalid
-
toParseableString
public abstract String toParseableString(T value)
Encode a possibly null value as aString
for later decoding byfromParseableString()
. The string value must be self-delimiting, i.e., decodable even when followed by arbitrary additional characters, and must not start with whitespace or closing square bracket ("]"
).In addition, each of the characters in the returned
String
must be one of the valid XML characters (tab, newline, carriage return,\u0020 - \ud7ff
, and\ue000 - \fffdf
).- Parameters:
value
- actual value (possibly null)- Returns:
- string encoding of
value
acceptable tofromParseableString()
- Throws:
IllegalArgumentException
- ifvalue
is null and this type does not support null- See Also:
- The XML 1.0 Specification
-
fromParseableString
public abstract T fromParseableString(ParseContext context)
Parse a value previously encoded bytoParseableString()
as a self-delimitedString
and positioned at the start of the given parsing context.- Parameters:
context
- parse context starting with a string previously encoded viatoParseableString()
- Returns:
- actual value (possibly null)
- Throws:
IllegalArgumentException
- if the input is invalid
-
convert
public <S> T convert(FieldType<S> type, S value)
Attempt to convert a value from the givenFieldType
into a value of thisFieldType
.For a non-null
value
, the implementation inFieldType
first checks whether thevalue
is already a valid value for this type; if so, the value is returned. Otherwise, it invokestype.
toString(value)
to convertvalue
into aString
, and then attempts to parse that string viathis.
fromString()
; if the parse fails, anIllegalArgumentException
is thrown.If
value
is null, the implementation inFieldType
returns null, unless this type does not support null values, in which case anIllegalArgumentException
is thrown.Special handling also exists for certain conversions between built-in types:
- Primitive types other than Boolean convert as if by the corresponding Java cast
- Non-Boolean primitive types convert to Boolean as if by
value != 0
- Boolean converts to non-Boolean primitive types by first converting to zero (if false) or one (if true)
- A
char
and aString
of length one are convertible (otherString
s are not) - A
char[]
array and aString
are convertible - Arrays are converted by converting each array element individually (if possible)
- Type Parameters:
S
- source field type- Parameters:
type
- theFieldType
ofvalue
value
- the value to convert- Returns:
value
converted to this instance's type- Throws:
IllegalArgumentException
- if the conversion fails
-
validate
public T validate(Object obj)
Verify the given object is a valid instance of thisFieldType
's Java type and cast it to that type.Note that this method must throw
IllegalArgumentException
, notClassCastException
orNullPointerException
, ifobj
does not have the correct type, or is an illegal null value.This method is allowed to perform widening conversions of the object that lose no information, e.g., from
Integer
toLong
.The implementation in
FieldType
simply casts the value using this instance's raw Java type. Subclasses should override this method to implement any other restrictions, e.g., disallowing null values.- Parameters:
obj
- object to validate- Returns:
obj
cast to this field's type- Throws:
IllegalArgumentException
- ifobj
in not of type TIllegalArgumentException
- ifobj
is null and this type does not support null valuesIllegalArgumentException
- ifobj
is in any other way not supported by thisFieldType
-
compare
public abstract int compare(T value1, T value2)
Order two field values.This method must provide a total ordering of all supported Java values that is consistent with the database ordering, i.e., the unsigned lexicographical ordering of the corresponding
byte[]
encoded field values.If null is a supported Java value, then the returned
Comparator
must accept null parameters without throwing an exception (note, this is a stronger requirement than theComparator
interface normally requires).Note: by convention, null values usually sort last.
- Specified by:
compare
in interfaceComparator<T>
- Throws:
IllegalArgumentException
- ifvalue1
orvalue2
is null and this type does not support null
-
hasPrefix0x00
public boolean hasPrefix0x00()
Determine whether any of this field type's encoded values start with a0x00
byte. Certain optimizations are possible when this is not the case. It is safe for this method to always return true.Note: changing the return value of this method usually means changing the binary encoding, resulting in an incompatible type.
The implementation in
FieldType
returnstrue
.- Returns:
- true if an encoded value starting with
0x00
exists
-
hasPrefix0xff
public boolean hasPrefix0xff()
Determine whether any of this field type's encoded values start with a0xff
byte. Certain optimizations are possible when this is not the case. It is safe for this method to always return true.Note: changing the return value of this method usually means changing the binary encoding, resulting in an incompatible type.
The implementation in
FieldType
returnstrue
.- Returns:
- true if an encoded value starting with
0xff
exists
-
validateAndWrite
public void validateAndWrite(ByteWriter writer, Object obj)
Convenience method that both validates and encodes a value.Equivalent to:
this.write(writer, this.validate(obj))
- Parameters:
writer
- byte outputobj
- object to validate- Throws:
IllegalArgumentException
- ifobj
in not of type TIllegalArgumentException
- ifobj
is null and this type does not support null valuesIllegalArgumentException
- ifobj
is in any other way not supported by thisFieldType
IllegalArgumentException
- ifwriter
is null
-
genericizeForIndex
public FieldType<T> genericizeForIndex()
Remove any information that may differ between instances associated with the same indexed field in the same schema.This operation should be applied before using this instance with index queries.
- Returns:
- this instance with all non-index-relevant information elided
-
getKeyRange
public KeyRange getKeyRange(Bounds<? extends T> bounds)
Calculate theKeyRange
that includes exactly those encoded values that lie within the given bounds.- Parameters:
bounds
- bounds to impose- Returns:
KeyRange
corresponding tobounds
- Throws:
IllegalArgumentException
- ifbounds
is null
-
-