All Downloads are FREE. Search and download functionalities are using the official Maven repository.

one.microstream.persistence.binary.internal.CustomBinaryHandler Maven / Gradle / Ivy

There is a newer version: 08.01.02-MS-GA
Show newest version
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);
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy