one.microstream.persistence.binary.internal.CustomBinaryHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of microstream-persistence-binary Show documentation
Show all versions of microstream-persistence-binary Show documentation
MicroStream Persistence Binary Data Extension
package one.microstream.persistence.binary.internal;
/*-
* #%L
* microstream-persistence-binary
* %%
* Copyright (C) 2019 - 2021 MicroStream Software
* %%
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
* #L%
*/
import static one.microstream.X.mayNull;
import static one.microstream.X.notNull;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import one.microstream.chars.XChars;
import one.microstream.collections.BulkList;
import one.microstream.collections.EqConstHashTable;
import one.microstream.collections.EqHashTable;
import one.microstream.collections.HashTable;
import one.microstream.collections.types.XAddingCollection;
import one.microstream.collections.types.XGettingSequence;
import one.microstream.collections.types.XGettingTable;
import one.microstream.collections.types.XImmutableEnum;
import one.microstream.collections.types.XTable;
import one.microstream.persistence.binary.types.Binary;
import one.microstream.persistence.binary.types.BinaryField;
import one.microstream.persistence.exceptions.PersistenceException;
import one.microstream.persistence.types.PersistenceFunction;
import one.microstream.persistence.types.PersistenceLoadHandler;
import one.microstream.persistence.types.PersistenceReferenceLoader;
import one.microstream.persistence.types.PersistenceStoreHandler;
import one.microstream.persistence.types.PersistenceTypeDefinitionMember;
import one.microstream.persistence.types.PersistenceTypeInstantiator;
import one.microstream.reflect.Getter;
import one.microstream.reflect.Getter_boolean;
import one.microstream.reflect.Getter_byte;
import one.microstream.reflect.Getter_char;
import one.microstream.reflect.Getter_double;
import one.microstream.reflect.Getter_float;
import one.microstream.reflect.Getter_int;
import one.microstream.reflect.Getter_long;
import one.microstream.reflect.Getter_short;
import one.microstream.reflect.Setter;
import one.microstream.reflect.Setter_boolean;
import one.microstream.reflect.Setter_byte;
import one.microstream.reflect.Setter_char;
import one.microstream.reflect.Setter_double;
import one.microstream.reflect.Setter_float;
import one.microstream.reflect.Setter_int;
import one.microstream.reflect.Setter_long;
import one.microstream.reflect.Setter_short;
import one.microstream.reflect.XReflect;
import one.microstream.typing.KeyValue;
public class CustomBinaryHandler extends AbstractBinaryHandlerCustom
{
///////////////////////////////////////////////////////////////////////////
// static methods //
///////////////////
protected static final BinaryField Field_byte(final Getter_byte getter)
{
return Field_byte(getter, null);
}
protected static final BinaryField Field_byte(
final Getter_byte getter,
final Setter_byte setter
)
{
return Binary.Field_byte(BinaryField.Defaults.defaultUninitializedName(), getter, setter);
}
protected static final BinaryField Field_boolean(final Getter_boolean getter)
{
return Field_boolean(getter, null);
}
protected static final BinaryField Field_boolean(
final Getter_boolean getter,
final Setter_boolean setter
)
{
return Binary.Field_boolean(BinaryField.Defaults.defaultUninitializedName(), getter, setter);
}
protected static final BinaryField Field_short(final Getter_short getter)
{
return Field_short(getter, null);
}
protected static final BinaryField Field_short(
final Getter_short getter,
final Setter_short setter
)
{
return Binary.Field_short(BinaryField.Defaults.defaultUninitializedName(), getter, setter);
}
protected static final BinaryField Field_char(final Getter_char getter)
{
return Field_char(getter, null);
}
protected static final BinaryField Field_char(
final Getter_char getter,
final Setter_char setter
)
{
return Binary.Field_char(BinaryField.Defaults.defaultUninitializedName(), getter, setter);
}
protected static final BinaryField Field_int(final Getter_int getter)
{
return Field_int(getter, null);
}
protected static final BinaryField Field_int(
final Getter_int getter,
final Setter_int setter
)
{
return Binary.Field_int(BinaryField.Defaults.defaultUninitializedName(), getter, setter);
}
protected static final BinaryField Field_float(final Getter_float getter)
{
return Field_float(getter, null);
}
protected static final BinaryField Field_float(
final Getter_float getter,
final Setter_float setter
)
{
return Binary.Field_float(BinaryField.Defaults.defaultUninitializedName(), getter, setter);
}
protected static final BinaryField Field_long(final Getter_long getter)
{
return Field_long(getter, null);
}
protected static final BinaryField Field_long(
final Getter_long getter,
final Setter_long setter
)
{
return Binary.Field_long(BinaryField.Defaults.defaultUninitializedName(), getter, setter);
}
protected static final BinaryField Field_double(final Getter_double getter)
{
return Field_double(getter, null);
}
protected static final BinaryField Field_double(
final Getter_double getter,
final Setter_double setter
)
{
return Binary.Field_double(BinaryField.Defaults.defaultUninitializedName(), getter, setter);
}
protected static final BinaryField Field(final Class referenceType, final Getter getter)
{
return Field(referenceType, getter, null);
}
protected static final BinaryField Field(
final Class referenceType,
final Getter getter ,
final Setter setter
)
{
return Binary.Field(referenceType, BinaryField.Defaults.defaultUninitializedName(), getter, setter);
}
// (22.01.2020 TM)TODO: priv#88: support variable length fields
// public static BinaryField Complex(
// final PersistenceTypeDefinitionMemberFieldGeneric... nestedFields
// )
// {
// return Complex(Defaults.defaultUninitializedName(), nestedFields);
// }
//
// public static BinaryField Complex(
// final String name ,
// final PersistenceTypeDefinitionMemberFieldGeneric... nestedFields
// )
// {
// return new BinaryField.Abstract(
// AbstractBinaryHandlerCustom.Complex(notNull(name), nestedFields),
// Defaults.defaultUninitializedOffset()
// );
// }
//
// public static BinaryField Bytes()
// {
// return Chars(Defaults.defaultUninitializedName());
// }
//
// public static BinaryField Bytes(final String name)
// {
// return new BinaryField.Abstract(
// AbstractBinaryHandlerCustom.bytes(name),
// Defaults.defaultUninitializedOffset()
// );
// }
//
// public static BinaryField Chars()
// {
// return Chars(Defaults.defaultUninitializedName());
// }
//
// public static BinaryField Chars(final String name)
// {
// return new BinaryField.Abstract(
// AbstractBinaryHandlerCustom.chars(name),
// Defaults.defaultUninitializedOffset()
// );
// }
protected EqConstHashTable> initializeDefinedFields(
final XGettingSequence> binaryFields
)
{
if(binaryFields == null)
{
// may be null for deferred on-demand initialization via reflection
return null;
}
final EqHashTable> mappedBinaryFields = EqHashTable.New();
for(final BinaryField binaryField : binaryFields)
{
if(!(binaryField instanceof BinaryField.Initializable))
{
throw new PersistenceException(
BinaryField.class.getSimpleName() + "\"" + binaryField.name() + "\" is not "
+ BinaryField.Initializable.class.getSimpleName() + "."
);
}
final String identifier = binaryField.identifier();
if(identifier == null)
{
throw new PersistenceException(
"Unnamed " + BinaryField.class.getSimpleName() + " of type " + binaryField.type()
);
}
if(!mappedBinaryFields.add(identifier, (BinaryField.Initializable)binaryField))
{
throw new PersistenceException(
"Duplicate " + BinaryField.class.getSimpleName() + " \"" + binaryField.type() + "\""
);
}
}
this.initializeBinaryFields(mappedBinaryFields);
return mappedBinaryFields.immure();
}
public static CustomBinaryHandler New(
final Class type ,
final PersistenceTypeInstantiator instantiator,
final XGettingSequence> binaryFields
)
{
return new CustomBinaryHandler<>(
notNull(type),
notNull(instantiator),
notNull(binaryFields)
);
}
///////////////////////////////////////////////////////////////////////////
// instance fields //
////////////////////
private final PersistenceTypeInstantiator instantiator;
// must be deferred-initialized since all fields have to be collected in a generic way
private EqConstHashTable> binaryFields;
// having no setting members effectively means the type is an immutable value type
private boolean hasSettingMembers;
private boolean hasNonSettingMembers;
private boolean hasPersistedReferences;
private BinaryField[] storingFields ;
private BinaryField[] nonSettingFields ;
private BinaryField[] allReferenceFields ;
private BinaryField[] settingRefrncFields;
private BinaryField[] settingNonRefFields;
// may be null if no such field is present
private BinaryField trailingVariableLengthField;
// all but trailing variable length field, if present.
private int fixedLengthBinaryContent;
///////////////////////////////////////////////////////////////////////////
// constructors //
/////////////////
protected CustomBinaryHandler(final Class type)
{
this(type, (XGettingSequence>)null);
}
protected CustomBinaryHandler(
final Class type ,
final XGettingSequence> binaryFields
)
{
this(type, deriveTypeName(type), null, binaryFields);
}
protected CustomBinaryHandler(
final Class type ,
final String typeName ,
final XGettingSequence> binaryFields
)
{
this(type, typeName, null, binaryFields);
}
protected CustomBinaryHandler(
final Class type ,
final PersistenceTypeInstantiator instantiator
)
{
this(type, instantiator, null);
}
protected CustomBinaryHandler(
final Class type ,
final PersistenceTypeInstantiator instantiator,
final XGettingSequence> binaryFields
)
{
this(type, deriveTypeName(type), instantiator, binaryFields);
}
protected CustomBinaryHandler(
final Class type ,
final String typeName ,
final PersistenceTypeInstantiator instantiator,
final XGettingSequence> binaryFields
)
{
super(type, typeName, binaryFields);
this.instantiator = mayNull(instantiator);
this.binaryFields = this.initializeDefinedFields(binaryFields);
}
///////////////////////////////////////////////////////////////////////////
// methods //
////////////
@Override
public final boolean hasPersistedReferences()
{
this.ensureInitializeInstanceMembers();
return this.hasPersistedReferences;
}
@Override
public final boolean hasVaryingPersistedLengthInstances()
{
this.ensureInitializeInstanceMembers();
return this.trailingVariableLengthField != null;
}
@Override
protected XImmutableEnum initializeInstanceMembers()
{
// super class's on-demand logic guarantees that this method is only called once for every instance.
final XGettingSequence> binaryFields = this.reflectiveInitializeBinaryFields();
return validateAndImmure(binaryFields);
}
private long calculcateContentLength(final T instance)
{
if(this.trailingVariableLengthField != null)
{
return this.fixedLengthBinaryContent + this.trailingVariableLengthField.calculateBinaryLength(instance);
}
return this.fixedLengthBinaryContent;
}
@Override
public void store(
final Binary data ,
final T instance,
final long objectId,
final PersistenceStoreHandler handler
)
{
final long contentLength = this.calculcateContentLength(instance);
data.storeEntityHeader(contentLength, this.typeId(), objectId);
for(final BinaryField storingField : this.storingFields)
{
storingField.storeFromInstance(instance, data, handler);
}
}
protected T instantiate(final Binary data)
{
// if this method is not overwritten by the subclass, then instantiator must be non-null.
return this.instantiator.instantiate(data);
}
@Override
public T create(final Binary data, final PersistenceLoadHandler handler)
{
final T instance = this.instantiate(data);
// reference values will get set later on in initializeState, when instances are guaranteed to be available
this.setNonReferenceValues(instance, data, handler);
return instance;
}
@Override
public void initializeState(final Binary data, final T instance, final PersistenceLoadHandler handler)
{
// non-reference values were already set in #create
this.setReferenceValues(instance, data, handler);
}
@Override
public void updateState(final Binary data, final T instance, final PersistenceLoadHandler handler)
{
if(this.hasNonSettingMembers)
{
// read-only fields must be validated instead of updated. Of course BEFORE updating anything
this.validateReadOnlyFields(instance, data, handler);
}
if(this.hasSettingMembers)
{
// update has to set both types of values
this.setNonReferenceValues(instance, data, handler);
this.setReferenceValues(instance, data, handler);
}
}
protected void validateReadOnlyFields(final T instance, final Binary data, final PersistenceLoadHandler handler)
{
for(final BinaryField nonSettingFieldField : this.nonSettingFields)
{
nonSettingFieldField.validateState(instance, data, handler);
}
}
private void setNonReferenceValues(final T instance, final Binary data, final PersistenceLoadHandler handler)
{
for(final BinaryField settingNonRefField : this.settingNonRefFields)
{
settingNonRefField.setToInstance(instance, data, handler);
}
}
private void setReferenceValues(final T instance, final Binary data, final PersistenceLoadHandler handler)
{
for(final BinaryField settingRefrncField : this.settingRefrncFields)
{
settingRefrncField.setToInstance(instance, data, handler);
}
}
@Override
public void complete(final Binary data, final T instance, final PersistenceLoadHandler handler)
{
// no-op for normal implementation (see non-reference-hashing collections for other examples)
}
@Override
public >> C iterateMemberTypes(final C logic)
{
for(final BinaryField storingField : this.storingFields)
{
logic.accept(storingField.type());
}
return logic;
}
protected final synchronized XGettingSequence> reflectiveInitializeBinaryFields()
{
final HashTable, XGettingSequence>> binaryFieldsPerClass = HashTable.New();
this.collectBinaryFields(binaryFieldsPerClass);
final EqHashTable> binaryFieldsInOrder = EqHashTable.New();
this.defineBinaryFieldOrder(binaryFieldsPerClass, (identifier, field) ->
{
if(!binaryFieldsInOrder.add(identifier, field))
{
throw new PersistenceException(
BinaryField.class.getSimpleName()
+ " with the unique identifier \"" + identifier + "\" is already registered."
);
}
});
this.initializeBinaryFields(binaryFieldsInOrder);
this.binaryFields = binaryFieldsInOrder.immure();
return this.binaryFields.values();
}
private void collectBinaryFields(
final HashTable, XGettingSequence>> binaryFieldsPerClass
)
{
for(Class c = this.getClass(); c != CustomBinaryHandler.class; c = c.getSuperclass())
{
// This construction is necessary to maintain the order even if a class overrides the collecting logic.
final BulkList> binaryFieldsOfClass = BulkList.New();
this.collectDeclaredBinaryFields(c, binaryFieldsOfClass);
// Already existing entries (added by an extending class in an override of this method) are allowed.
binaryFieldsPerClass.add(c, binaryFieldsOfClass);
}
// collection the fields "upwards" requires to reverse the collected class hierarchy in the end.
binaryFieldsPerClass.reverse();
}
protected void validateBinaryFieldGenericType(final Field binaryFieldField)
{
// the cast is safe for BinaryField since it is parameterized.
final Type genericType = binaryFieldField.getGenericType();
if(!(genericType instanceof ParameterizedType))
{
// omitted type parameter causes #getGenericType to return the primary type instead (which is idiotic).
return;
}
final ParameterizedType parameterizedType = (ParameterizedType)genericType;
// hardcoded array index is safe for BinaryField since it has exactely one type parameter.
final Type typeParameter = parameterizedType.getActualTypeArguments()[0];
if(!(typeParameter instanceof Class))
{
// complex type parameters (WildCard etc.) are not analyzed (for now).
return;
}
final Class typeParameterClass = (Class)typeParameter;
if(XReflect.isActualClass(typeParameterClass) && typeParameterClass.isAssignableFrom(this.type()))
{
// same or field-layout-wise compatible class
return;
}
throw new PersistenceException(
BinaryField.class.getSimpleName()
+ " type parameter \"" + typeParameterClass + "\""
+ " of field \"" + binaryFieldField + "\""
+ " is not compatible with this type handler's handled type \"" + this.type() + "\""
);
}
protected void collectDeclaredBinaryFields(
final Class clazz ,
final XAddingCollection> target
)
{
for(final Field field : clazz.getDeclaredFields())
{
if(!BinaryField.class.isAssignableFrom(field.getType()))
{
continue;
}
try
{
field.setAccessible(true);
@SuppressWarnings("unchecked")
final BinaryField binaryField = (BinaryField)field.get(this);
if(!(binaryField instanceof BinaryField.Initializable))
{
continue;
}
this.validateBinaryFieldGenericType(field);
@SuppressWarnings("unchecked")
final BinaryField.Initializable initializable = (BinaryField.Initializable)binaryField;
// the whole identifier must be initialized to ensure uniqueness.
initializable.initializeIdentifierOptional(clazz.getName(), field.getName());
target.add(initializable);
}
catch(final Exception e)
{
throw new PersistenceException(e);
}
}
}
protected void defineBinaryFieldOrder(
final XGettingTable, XGettingSequence>> binaryFieldsPerClass,
final BiConsumer> collector
)
{
/*
* With the class hiararchy collection order guaranteed above, this loop does:
* - order binaryFields from most to least specific class ("upwards")
* - order binaryFields per class in declaration order
*/
for(final XGettingSequence> binaryFieldsOfClass : binaryFieldsPerClass.values())
{
for(final BinaryField.Initializable binaryField : binaryFieldsOfClass)
{
collector.accept(binaryField.identifier(), binaryField);
}
}
}
private void initializeBinaryFields(
final EqHashTable> binaryFields
)
{
final BinaryField varLengthField = checkVariableLengthLayout(binaryFields);
final BulkList> storingFields = BulkList.New();
final BulkList> allReferenceFields = BulkList.New();
final BulkList> settingNonRefFields = BulkList.New();
final BulkList> settingRefrncFields = BulkList.New();
final BulkList> nonSettingFields = BulkList.New();
int offset = 0;
for(final BinaryField.Initializable binaryField : binaryFields.values())
{
binaryField.initializeOffset(offset);
storingFields.add(binaryField);
if(binaryField.hasReferences())
{
allReferenceFields.add(binaryField);
}
// variable length field must be excluded as offset is co-used as the fixed content length
if(binaryField != varLengthField)
{
offset += binaryField.persistentMinimumLength();
}
if(!binaryField.canSet())
{
nonSettingFields.add(binaryField);
continue;
}
/*
* must use "hasReferences" instead of "isReference" as a variable list field
* is not a reference, but can contain references.
*/
if(binaryField.hasReferences())
{
settingRefrncFields.add(binaryField);
}
else
{
settingNonRefFields.add(binaryField);
}
}
this.storingFields = storingFields .toArray(this.binaryFieldClass());
this.nonSettingFields = nonSettingFields .toArray(this.binaryFieldClass());
this.allReferenceFields = allReferenceFields .toArray(this.binaryFieldClass());
this.settingRefrncFields = settingRefrncFields.toArray(this.binaryFieldClass());
this.settingNonRefFields = settingNonRefFields.toArray(this.binaryFieldClass());
this.trailingVariableLengthField = varLengthField;
this.hasPersistedReferences = !allReferenceFields.isEmpty();
this.hasSettingMembers = !settingRefrncFields.isEmpty() || !settingNonRefFields.isEmpty();
this.hasNonSettingMembers = !nonSettingFields.isEmpty();
this.fixedLengthBinaryContent = offset;
}
@SuppressWarnings({"unchecked", "rawtypes"})
private Class> binaryFieldClass()
{
// no idea how to get ".class" to work otherwise
return (Class)BinaryField.class;
}
/**
* Only the last field may have variable length, otherweise simple offsets can't be used.
*
* @param binaryFields
*/
private static > F checkVariableLengthLayout(
final XTable binaryFields
)
{
if(binaryFields.isEmpty())
{
// empty fields is implicitely valid and, of course, yields null.
return null;
}
KeyValue varLengthEntry = null;
for(final KeyValue e : binaryFields)
{
if(e.value().isVariableLength())
{
if(varLengthEntry == null)
{
varLengthEntry = e;
continue;
}
throw new PersistenceException(
"Multiple variable length fields detected: "
+ XChars.systemString(varLengthEntry.value()) + ": " + varLengthEntry.value().identifier()
+ ", " + XChars.systemString(e.value()) + ": " + e.value().identifier()
);
}
}
if(varLengthEntry == null)
{
// no variable length field
return null;
}
binaryFields.put(varLengthEntry);
return varLengthEntry.value();
}
@Override
public final void iterateInstanceReferences(final T instance, final PersistenceFunction iterator)
{
for(final BinaryField referenceField : this.allReferenceFields)
{
referenceField.iterateReferences(instance, iterator);
}
}
@Override
public void iterateLoadableReferences(final Binary data, final PersistenceReferenceLoader loader)
{
for(final BinaryField referenceField : this.allReferenceFields)
{
referenceField.iterateLoadableReferences(data, loader);
}
}
}