/*
 * $Id$
 *
 * SARL is an general-purpose agent programming language.
 * More details on http://www.sarl.io
 *
 * Copyright (C) 2014-2026 SARL.io, the original authors and main authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sarl.lang.jvmmodel.fragments;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtend.core.xtend.XtendClass;
import org.eclipse.xtend.core.xtend.XtendField;
import org.eclipse.xtend.core.xtend.XtendInterface;
import org.eclipse.xtend.core.xtend.XtendMember;
import org.eclipse.xtend.core.xtend.XtendTypeDeclaration;
import org.eclipse.xtext.common.types.JvmAnnotationTarget;
import org.eclipse.xtext.common.types.JvmConstructor;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmExecutable;
import org.eclipse.xtext.common.types.JvmField;
import org.eclipse.xtext.common.types.JvmGenericType;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeParameter;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.JvmUnknownTypeReference;
import org.eclipse.xtext.common.types.JvmVisibility;
import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.Pair;
import org.eclipse.xtext.xbase.lib.Procedures;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure2;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.XbaseGenerated;
import org.eclipse.xtext.xbase.validation.ReadAndWriteTracking;

import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.inject.Inject;

import io.sarl.lang.core.Agent;
import io.sarl.lang.core.Behavior;
import io.sarl.lang.core.Event;
import io.sarl.lang.core.SARLVersion;
import io.sarl.lang.core.Skill;
import io.sarl.lang.core.annotation.DefaultValueUse;
import io.sarl.lang.core.annotation.Injectable;
import io.sarl.lang.core.annotation.NoEqualityTestFunctionsGeneration;
import io.sarl.lang.core.annotation.PerceptGuardEvaluator;
import io.sarl.lang.core.annotation.SarlElementType;
import io.sarl.lang.core.annotation.SarlSpecification;
import io.sarl.lang.core.util.SarlUtils;
import io.sarl.lang.jvmmodel.GenerationContext;
import io.sarl.lang.jvmmodel.GenerationContext.BehaviorUnitGuardEvaluators;
import io.sarl.lang.jvmmodel.IBaseJvmModelInferrer;
import io.sarl.lang.sarl.SarlCapacityUses;
import io.sarl.lang.sarl.SarlRequiredCapacity;
import io.sarl.lang.sarl.actionprototype.ActionParameterTypes;
import io.sarl.lang.sarl.actionprototype.ActionPrototype;
import io.sarl.lang.sarl.actionprototype.InferredValuedParameter;
import io.sarl.lang.typesystem.InheritanceHelper;
import io.sarl.lang.util.Utils;
import io.sarl.lang.util.Utils.TypeParameterStatus;

/** Abstract implementation of a fragment that may be used for inferring type to JVM model.
 *
 * @author $Author: sgalland$
 * @version $FullVersion$
 * @mavengroupid $GroupId$
 * @mavenartifactid $ArtifactId$
 * @since 0.15
 */
public abstract class AbstractJvmModelInferrerTypeFragment extends AbstractJvmModelInferrerFragment {

	private static final String EQUALS_FUNCTION_NAME = "equals"; //$NON-NLS-1$

	private static final String HASHCODE_FUNCTION_NAME = "hashCode"; //$NON-NLS-1$

	private static final String CLONE_FUNCTION_NAME = "clone"; //$NON-NLS-1$

	private static final String SERIAL_FIELD_NAME = "serialVersionUID"; //$NON-NLS-1$

	private static final Set<Class<?>> EQUALITY_TEST_TYPES = new TreeSet<>((ele1, ele2) -> ele1.getName().compareTo(ele2.getName()));

	static {
		EQUALITY_TEST_TYPES.add(Boolean.TYPE);
		EQUALITY_TEST_TYPES.add(Boolean.class);
		EQUALITY_TEST_TYPES.add(Integer.TYPE);
		EQUALITY_TEST_TYPES.add(Integer.class);
		EQUALITY_TEST_TYPES.add(Character.TYPE);
		EQUALITY_TEST_TYPES.add(Character.class);
		EQUALITY_TEST_TYPES.add(Byte.TYPE);
		EQUALITY_TEST_TYPES.add(Byte.class);
		EQUALITY_TEST_TYPES.add(Short.TYPE);
		EQUALITY_TEST_TYPES.add(Short.class);
		EQUALITY_TEST_TYPES.add(Long.TYPE);
		EQUALITY_TEST_TYPES.add(Long.class);
		EQUALITY_TEST_TYPES.add(Float.TYPE);
		EQUALITY_TEST_TYPES.add(Float.class);
		EQUALITY_TEST_TYPES.add(Double.TYPE);
		EQUALITY_TEST_TYPES.add(Double.class);
		EQUALITY_TEST_TYPES.add(String.class);
		EQUALITY_TEST_TYPES.add(UUID.class);
	}

	@Inject
	private InheritanceHelper inheritanceHelper;

	/** The tracker of the read/write accesses to the fields.
	 *
	 * @since 0.15
	 */
	@Inject
	protected ReadAndWriteTracking readAndWriteTracking;

	/** Generate the missed operations that are the results from the generation of actions with default value parameters.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param source the SARL container.
	 * @param target the JVM feature container.
	 * @param ignoreOverridableOperations indicates if the operations must not be added if they are marked has
	 *     overridable.
	 * @param context description of the generation context in which the members must be considered.
	 */
	protected void appendSyntheticDefaultValuedParameterMethods(
			IBaseJvmModelInferrer baseInferrer,
			XtendTypeDeclaration source,
			JvmDeclaredType target,
			boolean ignoreOverridableOperations,
			GenerationContext context) {

		// Generate the different operations.
		final var differedGeneration = context.getPreFinalizationElements().iterator();
		while (differedGeneration.hasNext()) {
			final var runnable = differedGeneration.next();
			differedGeneration.remove();
			runnable.run();
		}

		// Generated the missed functions that are the result of the generation of operations
		// with default values.
		var actIndex = context.getActionIndex();

		final var inheritedOperations = context.getInheritedOperationsToImplement();

		for (final var missedOperation : inheritedOperations.entrySet()) {

			final var originalSignature = this.annotationUtils.findStringValue(
					missedOperation.getValue(), DefaultValueUse.class);
			if (!Strings.isNullOrEmpty(originalSignature)) {

				// Find the definition of the operation from the inheritance context.
				final var redefinedOperation = inheritedOperations.get(
						this.sarlSignatureProvider.createActionPrototype(
								missedOperation.getKey().getActionName(),
								this.sarlSignatureProvider.createParameterTypesFromString(originalSignature)));
				if (redefinedOperation != null) {
					final var parameterTypes = this.sarlSignatureProvider.createParameterTypesFromJvmModel(
							redefinedOperation.isVarArgs(), redefinedOperation.getParameters());
					final var qualifiedActionName = this.sarlSignatureProvider.createQualifiedActionName(
							missedOperation.getValue().getDeclaringType(),
							redefinedOperation.getSimpleName());

					// Retrieve the inferred prototype (including the prototypes with optional arguments)
					var redefinedPrototype = this.sarlSignatureProvider.getPrototypes(
							context.getActionPrototypeContext(this.sarlSignatureProvider),
							qualifiedActionName, parameterTypes);
					if (redefinedPrototype == null) {
						// The original operation was not parsed by the SARL compiler in the current run-time context.
						redefinedPrototype = this.sarlSignatureProvider.createPrototypeFromJvmModel(
								context.getActionPrototypeContext(this.sarlSignatureProvider),
								qualifiedActionName, redefinedOperation.isVarArgs(),
								redefinedOperation.getParameters());
					}

					// Retrieve the specification of the formal parameters that will be used for
					// determining the calling arguments.
					final var argumentSpec = redefinedPrototype.getInferredParameterTypes().get(
							missedOperation.getKey().getParametersTypes());

					// Create the missed java operation.
					final var op = this.jvmTypeBuilder.toMethod(
							source,
							missedOperation.getValue().getSimpleName(),
							missedOperation.getValue().getReturnType(),
							null);
					op.setVarArgs(missedOperation.getValue().isVarArgs());
					op.setFinal(true);

					final var arguments = new ArrayList<String>();

					// Create the formal parameters.
					for (final var parameter : argumentSpec) {
						if (parameter instanceof InferredValuedParameter inferredParameter) {
							arguments.add(this.sarlSignatureProvider.toJavaArgument(
									target.getIdentifier(), inferredParameter.getCallingArgument()));
						} else {
							arguments.add(parameter.getName());
							final var jvmParam = this.jvmTypesFactory.createJvmFormalParameter();
							jvmParam.setName(parameter.getName());
							jvmParam.setParameterType(this.jvmTypeBuilder.cloneWithProxies(parameter.getType().toTypeReference()));
							this.associator.associate(parameter.getParameter(), jvmParam);
							op.getParameters().add(jvmParam);
						}
					}

					// Create the body
					setBody(op,
							it -> {
								it.append(redefinedOperation.getSimpleName());
								it.append("("); //$NON-NLS-1$
								it.append(IterableExtensions.join(arguments, ", ")); //$NON-NLS-1$
								it.append(");"); //$NON-NLS-1$
							});

					// Add the annotations.
					addAnnotationSafe(baseInferrer, op, DefaultValueUse.class, originalSignature);
					appendGeneratedAnnotation(baseInferrer, op, context);

					// Add the operation in the container.
					target.getMembers().add(op);
					++actIndex;
				}
			}
		}
		context.setActionIndex(actIndex);
	}

	/** Append the @FunctionalInterface to the given type if it is a functional interface according
	 * to the Java 8 specification definition.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param type the type to update.
	 */
	protected void appendFunctionalInterfaceAnnotation(IBaseJvmModelInferrer baseInferrer, JvmGenericType type) {
		if (type != null && Utils.isFunctionalInterface(type, this.sarlSignatureProvider)
				&& this.jvmAnnotationFinder.findAnnotation(type, FunctionalInterface.class) == null) {
			addAnnotationSafe(baseInferrer, type, FunctionalInterface.class);
		}
	}

	/** Append the SARL specification version as an annotation to the given container.
	 *
	 * <p>The added annotation may be used by any underground platform for determining what is
	 * the version of the SARL specification that was used for generating the container.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param context the current generation context.
	 * @param source the source object.
	 * @param target the inferred JVM object.
	 */
	protected void appendSARLSpecificationVersion(IBaseJvmModelInferrer baseInferrer, GenerationContext context, XtendTypeDeclaration source,
			JvmDeclaredType target) {
		addAnnotationSafe(baseInferrer, target, SarlSpecification.class, SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING);
	}

	/** Append the SARL element type as an annotation to the given container.
	 *
	 * <p>The added annotation may be used by any underground platform for determining what is
	 * the type of the SARL element without invoking the costly "instanceof" operations.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param source the source object.
	 * @param target the inferred JVM object.
	 */
	protected void appendSARLElementType(IBaseJvmModelInferrer baseInferrer, XtendTypeDeclaration source, JvmDeclaredType target) {
		addAnnotationSafe(target, SarlElementType.class, source.eClass().getClassifierID());
	}


	/** Append the injectable annotation to the given container.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param target the inferred JVM object.
	 * @param context the generation context.
	 * @since 0.12
	 */
	protected void appendInjectableAnnotationIfInjectable(IBaseJvmModelInferrer baseInferrer, JvmDeclaredType target, GenerationContext context) {
		if (context.isInjectable()) {
			addAnnotationSafe(baseInferrer, target, Injectable.class);
		}
	}

	/** Add the @XbaseGenerated annotation to the given target.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param target the target of the annotation.
	 */
	protected void appendXbaseGeneratedAnnotation(IBaseJvmModelInferrer baseInferrer, JvmAnnotationTarget target) {
		if (target instanceof JvmExecutable || target instanceof JvmDeclaredType) {
			addAnnotationSafe(baseInferrer, target, XbaseGenerated.class);
		}
	}

	/** Create the functions that permits to compare the object.
	 * The comparaison functions are {@link #equals(Object)} and {@link #hashCode()}.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param context the current generation context.
	 * @param source the source object.
	 * @param target the inferred JVM object.
	 */
	protected void appendComparisonFunctions(IBaseJvmModelInferrer baseInferrer, GenerationContext context, XtendTypeDeclaration source,
			JvmGenericType target) {
		if (isAppendComparisonFunctionsEnable(context, target)) {
			var isEqualsUserDefined = false;
			var isHashCodeUserDefined = false;
			for (final var operation : target.getDeclaredOperations()) {
				if (Objects.equal(EQUALS_FUNCTION_NAME, operation.getSimpleName())
						&& operation.getParameters().size() == 1
						&& Objects.equal(Object.class.getName(),
								operation.getParameters().get(0).getParameterType().getIdentifier())) {
					isEqualsUserDefined = true;
				} else if (Objects.equal(HASHCODE_FUNCTION_NAME, operation.getSimpleName())
						&& operation.getParameters().isEmpty()) {
					isHashCodeUserDefined = true;
				}
			}

			if (!isEqualsUserDefined || !isHashCodeUserDefined) {
				// Create a list of the declared non-static fields.
				final var declaredInstanceFields = new ArrayList<JvmField>();
				for (final var field : target.getDeclaredFields()) {
					if (isEqualityTestValidField(field)) {
						declaredInstanceFields.add(field);
					}
				}

				if (!declaredInstanceFields.isEmpty()) {
					final var finalOperations = new TreeMap<ActionPrototype, JvmOperation>();
					Utils.populateInheritanceContext(
							target, finalOperations, null, null, null, null, this.sarlSignatureProvider);

					var couldCreateEqualsFunction = true;
					if (!isEqualsUserDefined) {
						final var prototype = new ActionPrototype(EQUALS_FUNCTION_NAME,
								this.sarlSignatureProvider.createParameterTypesFromString(Object.class.getName()), false);
						couldCreateEqualsFunction = !finalOperations.containsKey(prototype);
					}

					var couldCreateHashCodeFunction = true;
					if (!isHashCodeUserDefined) {
						final var prototype = new ActionPrototype(HASHCODE_FUNCTION_NAME,
								this.sarlSignatureProvider.createParameterTypesForVoid(), false);
						couldCreateHashCodeFunction = !finalOperations.containsKey(prototype);
					}

					if (couldCreateEqualsFunction && couldCreateHashCodeFunction) {
						if (!isEqualsUserDefined) {
							final var op = toEqualsMethod(baseInferrer, source, target, declaredInstanceFields,
									context.getGeneratorConfig2().isGeneratePureAnnotation());
							if (op != null) {
								appendGeneratedAnnotation(baseInferrer, op, context);
								target.getMembers().add(op);
							}
						}

						if (!isHashCodeUserDefined) {
							final var op = toHashCodeMethod(baseInferrer, source, declaredInstanceFields,
									context.getGeneratorConfig2().isGeneratePureAnnotation());
							if (op != null) {
								appendGeneratedAnnotation(baseInferrer, op, context);
								target.getMembers().add(op);
							}
						}
					}
				}
			}
		}
	}

	private boolean isAppendComparisonFunctionsEnable(GenerationContext context, JvmGenericType target) {
		if (context.getGeneratorConfig2().isGenerateEqualityTestFunctions()) {
			var current = target;
			final var encounteredTypes = new TreeSet<String>();
			do {
				encounteredTypes.add(current.getIdentifier());
				if (this.jvmAnnotationFinder.findAnnotation(current, NoEqualityTestFunctionsGeneration.class) != null) {
					return false;
				}
				final var superType = current.getExtendedClass();
				current = null;
				if (superType != null) {
					final var type = superType.getType();
					if (type instanceof JvmGenericType cvalue) {
						current = cvalue;
						if (encounteredTypes.contains(current.getIdentifier())) {
							current = null;
						}
					}
				}
			} while (current != null);
			return true;
		}
		return false;
	}

	private boolean isEqualityTestValidField(JvmField field) {
		return !field.isStatic() && !SarlUtils.isHiddenMember(field.getSimpleName())
				&& this.jvmAnnotationFinder.findAnnotation(field, NoEqualityTestFunctionsGeneration.class) == null;
	}

	private boolean isEqualityTestValidField(JvmTypeReference reference) {
		for (final var type : EQUALITY_TEST_TYPES) {
			if (this.jvmTypeReferences.is(reference, type)) {
				return true;
			}
		}
		return false;
	}

	/** Generate the "equals()" operation.
	 * This function was deprecated in Xbase, and should be provided by DSL
	 * providers now.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param sarlElement the SARL element for which the "equals function must be generated.
	 * @param declaredType the declating type.
	 * @param jvmFields the fields declared in the container.
	 * @param generatePureAnnotation indicates if the {@code @Pure} annotation should be generated.
	 * @return the "equals" function.
	 */
	private JvmOperation toEqualsMethod(
			IBaseJvmModelInferrer baseInferrer,
			XtendTypeDeclaration sarlElement,
			final JvmDeclaredType declaredType,
			final Iterable<JvmField> jvmFields,
			boolean generatePureAnnotation) {
		if (sarlElement == null || declaredType == null) {
			return null;
		}

		final var result = this.jvmTypeBuilder.toMethod(sarlElement, EQUALS_FUNCTION_NAME,
				baseInferrer.getJvmTypeReferenceBuilder().typeRef(boolean.class), null);
		if (result == null) {
			return null;
		}
		addAnnotationSafe(baseInferrer, result, Override.class);
		if (generatePureAnnotation) {
			addAnnotationSafe(baseInferrer, result, Pure.class);
		}

		final var param = this.jvmTypesFactory.createJvmFormalParameter();
		param.setName("obj"); //$NON-NLS-1$
		param.setParameterType(baseInferrer.getJvmTypeReferenceBuilder().typeRef(Object.class));
		this.associator.associate(sarlElement, param);
		result.getParameters().add(param);
		setBody(result, new Procedures.Procedure1<ITreeAppendable>() {
			@Override
			public void apply(ITreeAppendable it) {
				var firstAttr = true;
				for (final var field : jvmFields) {
					if (isEqualityTestValidField(field.getType())) {
						if (firstAttr) {
							firstAttr = false;
							it.append("if (this == obj)").increaseIndentation(); //$NON-NLS-1$
							it.newLine().append("return true;").decreaseIndentation(); //$NON-NLS-1$
							it.newLine().append("if (obj == null)").increaseIndentation(); //$NON-NLS-1$
							it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
							it.newLine().append("if (getClass() != obj.getClass())").increaseIndentation(); //$NON-NLS-1$
							it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
							final var currentTypeName = new StringBuilder();
							currentTypeName.append(declaredType.getSimpleName());
							final var typeParameters = getTypeParametersFor(sarlElement);
							if (!typeParameters.isEmpty()) {
								currentTypeName.append("<"); //$NON-NLS-1$
								var first = true;
								for (final var typeParameter : typeParameters) {
									if (first) {
										first = false;
									} else {
										currentTypeName.append(", "); //$NON-NLS-1$
									}
									currentTypeName.append(typeParameter.getName());
								}
								currentTypeName.append(">"); //$NON-NLS-1$
							}
							it.newLine().append(currentTypeName).append(" other = ("); //$NON-NLS-1$
							it.append(currentTypeName).append(") obj;").newLine(); //$NON-NLS-1$
						}
						generateToEqualForField(it, field);
					}
				}
				it.append("return super.").append(EQUALS_FUNCTION_NAME); //$NON-NLS-1$
				it.append("(obj);"); //$NON-NLS-1$
			}

			private void generateToEqualForObjectNullity(ITreeAppendable it, JvmField field) {
				it.append("if (other.").append(field.getSimpleName()); //$NON-NLS-1$
				it.append(" == null) {").increaseIndentation(); //$NON-NLS-1$
				it.newLine().append("if (this.").append(field.getSimpleName()).append(" != null)").increaseIndentation(); //$NON-NLS-1$ //$NON-NLS-2$
				it.newLine().append("return false;").decreaseIndentation().decreaseIndentation(); //$NON-NLS-1$
				it.newLine().append("} else if (this."); //$NON-NLS-1$
				it.append(field.getSimpleName()).append(" == null)").increaseIndentation(); //$NON-NLS-1$
				it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
			}

			private void generateToEqualForObjectTest(ITreeAppendable it, JvmField field, String convertName) {
				if (convertName != null) {
					it.newLine().append("if (other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(" != null && other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(".").append(convertName).append("Value() != this.").append(field.getSimpleName()); //$NON-NLS-1$ //$NON-NLS-2$
					it.append(".").append(convertName).append("Value())").increaseIndentation(); //$NON-NLS-1$ //$NON-NLS-2$
					it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
				} else {
					it.newLine().append("if (!").append(java.util.Objects.class); //$NON-NLS-1$
					it.append(".equals(this.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(", other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append("))").increaseIndentation(); //$NON-NLS-1$
					it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
				}
			}

			private void generateToEqualForField(ITreeAppendable it, JvmField field) {
				final var refs = AbstractJvmModelInferrerTypeFragment.this.jvmTypeReferences;
				final var type = field.getType();
				if (refs.is(type, Boolean.TYPE)
						|| refs.is(type, Integer.TYPE)
						|| refs.is(type, Long.TYPE)
						|| refs.is(type, Character.TYPE)
						|| refs.is(type, Byte.TYPE)
						|| refs.is(type, Short.TYPE)) {
					it.append("if (other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(" != this.").append(field.getSimpleName()).append(")").increaseIndentation(); //$NON-NLS-1$ //$NON-NLS-2$
					it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
				} else if (refs.is(type, Double.TYPE)) {
					it.append("if (Double.doubleToLongBits(other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(") != Double.doubleToLongBits(this.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append("))").increaseIndentation(); //$NON-NLS-1$
					it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
				} else if (refs.is(type, Double.class)) {
					generateToEqualForObjectNullity(it, field);
					it.newLine().append("if (other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(" != null && Double.doubleToLongBits(other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(".doubleValue()) != Double.doubleToLongBits(this.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(".doubleValue()))").increaseIndentation(); //$NON-NLS-1$
					it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
				} else if (refs.is(type, Float.TYPE)) {
					it.append("if (Float.floatToIntBits(other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(") != Float.floatToIntBits(this.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append("))").increaseIndentation(); //$NON-NLS-1$
					it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
				} else if (refs.is(type, Float.class)) {
					generateToEqualForObjectNullity(it, field);
					it.newLine().append("if (other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(" != null && Float.floatToIntBits(other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(".floatValue()) != Float.floatToIntBits(this.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(".floatValue()))").increaseIndentation(); //$NON-NLS-1$
					it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
				} else if (refs.is(type, Byte.class)
						|| refs.is(type, Short.class)
						|| refs.is(type, Integer.class) || refs.is(type, AtomicInteger.class)
						|| refs.is(type, Long.class) || refs.is(type, AtomicLong.class)
						|| refs.is(type, Boolean.class)
						|| refs.is(type, Character.class)) {
					generateToEqualForObjectNullity(it, field);
					final String conv;
					if (refs.is(type, Byte.class)) {
						conv = "byte"; //$NON-NLS-1$
					} else if (refs.is(type, Short.class)) {
						conv = "short"; //$NON-NLS-1$
					} else if (refs.is(type, Integer.class)) {
						conv = "int"; //$NON-NLS-1$
					} else if (refs.is(type, Long.class)) {
						conv = "long"; //$NON-NLS-1$
					} else if (refs.is(type, Character.class)) {
						conv = "char"; //$NON-NLS-1$
					} else if (refs.is(type, Boolean.class)) {
						conv = "boolean"; //$NON-NLS-1$
					} else {
						conv = null;
					}
					generateToEqualForObjectTest(it, field, conv);
				} else {
					it.append("if (!").append(java.util.Objects.class); //$NON-NLS-1$
					it.append(".equals(this.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append(", other.").append(field.getSimpleName()); //$NON-NLS-1$
					it.append("))").increaseIndentation(); //$NON-NLS-1$
					it.newLine().append("return false;").decreaseIndentation(); //$NON-NLS-1$
				}
				it.newLine();
			}
		});

		return result;
	}

	/** Replies the type parameters for the given type.
	 *
	 * @param type the type.
	 * @return the type parameters for the given type.
	 */
	@SuppressWarnings("static-method")
	private List<JvmTypeParameter> getTypeParametersFor(XtendTypeDeclaration type) {
		if (type instanceof XtendClass cvalue) {
			return cvalue.getTypeParameters();
		}
		if (type instanceof XtendInterface cvalue) {
			return cvalue.getTypeParameters();
		}
		return Collections.emptyList();
	}

	/** Generate the "hashCode()" operation.
	 * This function was deprecated in Xbase, and should be provided by DSL
	 * providers now.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param sarlElement the SARL element for which the "hashCode" msut be generated.
	 * @param jvmFields the fields declared in the container.
	 * @return the "hashCode" function.
	 */
	private JvmOperation toHashCodeMethod(
			IBaseJvmModelInferrer baseInferrer,
			XtendTypeDeclaration sarlElement,
			final Iterable<JvmField> jvmFields,
			boolean generatePureAnnotation) {
		if (sarlElement == null) {
			return null;
		}
		final var result = this.jvmTypeBuilder.toMethod(sarlElement, HASHCODE_FUNCTION_NAME,
				baseInferrer.getJvmTypeReferenceBuilder().typeRef(int.class), null);
		if (result == null) {
			return null;
		}
		addAnnotationSafe(baseInferrer, result, Override.class);
		if (generatePureAnnotation) {
			addAnnotationSafe(baseInferrer, result, Pure.class);
		}
		setBody(result, it -> {
			final var refs = this.jvmTypeReferences;
			it.append("int result = super.").append(HASHCODE_FUNCTION_NAME); //$NON-NLS-1$
			it.append("();"); //$NON-NLS-1$
			var firstAttr = true;
			for (final var field : jvmFields) {
				if (isEqualityTestValidField(field.getType())) {
					if (firstAttr) {
						firstAttr = false;
						it.newLine().append("final int prime = 31;"); //$NON-NLS-1$
					}
					final var type = field.getType();
					if (refs.is(type, Boolean.TYPE)) {
						it.newLine().append("result = prime * result + Boolean.hashCode(this."); //$NON-NLS-1$
						it.append(field.getSimpleName()).append(");"); //$NON-NLS-1$
					} else if (refs.is(type, Character.TYPE)) {
						it.newLine().append("result = prime * result + Character.hashCode(this."); //$NON-NLS-1$
						it.append(field.getSimpleName()).append(");"); //$NON-NLS-1$
					} else if (refs.is(type, Byte.TYPE)) {
						it.newLine().append("result = prime * result + Byte.hashCode(this."); //$NON-NLS-1$
						it.append(field.getSimpleName()).append(");"); //$NON-NLS-1$
					} else if (refs.is(type, Short.TYPE)) {
						it.newLine().append("result = prime * result + Short.hashCode(this."); //$NON-NLS-1$
						it.append(field.getSimpleName()).append(");"); //$NON-NLS-1$
					} else if (refs.is(type, Integer.TYPE)) {
						it.newLine().append("result = prime * result + Integer.hashCode(this."); //$NON-NLS-1$
						it.append(field.getSimpleName()).append(");"); //$NON-NLS-1$
					} else if (refs.is(type, Long.TYPE)) {
						it.newLine().append("result = prime * result + Long.hashCode(this."); //$NON-NLS-1$
						it.append(field.getSimpleName()).append(");"); //$NON-NLS-1$
					} else if (refs.is(type, Float.TYPE)) {
						it.newLine().append("result = prime * result + Float.hashCode(this."); //$NON-NLS-1$
						it.append(field.getSimpleName()).append(");"); //$NON-NLS-1$
					} else if (refs.is(type, Double.TYPE)) {
						it.newLine().append("result = prime * result + Double.hashCode(this."); //$NON-NLS-1$
						it.append(field.getSimpleName()).append(");"); //$NON-NLS-1$
					} else {
						it.newLine().append("result = prime * result + "); //$NON-NLS-1$
						it.append(java.util.Objects.class).append(".hashCode(this."); //$NON-NLS-1$
						it.append(field.getSimpleName()).append(");"); //$NON-NLS-1$
					}
				}
			}
			it.newLine().append("return result;"); //$NON-NLS-1$
		});

		return result;
	}

	/** Append the clone function only if the type is a subtype of {@link Cloneable}.
	 *
	 * <p>The clone function replies a value of the current type, not {@code Object}.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param context the current generation context.
	 * @param source the source object.
	 * @param target the inferred JVM object.
	 * @since 0.6
	 */
	protected void appendCloneFunctionIfCloneable(
			IBaseJvmModelInferrer baseInferrer,
			GenerationContext context, XtendTypeDeclaration source,
			JvmGenericType target) {
		if (!target.isInterface() && this.inheritanceHelper.isSubTypeOf(target, Cloneable.class, null)) {
			appendCloneFunction(baseInferrer, context, source, target);
		}
	}

	/** Append the clone function.
	 *
	 * <p>The clone function replies a value of the current type, not {@code Object}.
	 *
	 * @param baseInferrer the inferrer that is the considered as the base (starting point) of inferring process.
	 * @param context the current generation context.
	 * @param source the source object.
	 * @param target the inferred JVM object.
	 * @since 0.6
	 */
	private void appendCloneFunction(
			IBaseJvmModelInferrer baseInferrer, GenerationContext context,
			XtendTypeDeclaration source, JvmGenericType target) {
		if (!isAppendCloneFunctionsEnable(context)) {
			return;
		}
		for (final var operation : target.getDeclaredOperations()) {
			if (CLONE_FUNCTION_NAME.equals(operation.getSimpleName())) {
				return;
			}
		}

		final var standardPrototype = new ActionPrototype(CLONE_FUNCTION_NAME,
				this.sarlSignatureProvider.createParameterTypesForVoid(), false);

