/*
 * $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.mwe2.codebuilder.fragments;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeParameterDeclarator;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.access.IJvmTypeProvider;
import org.eclipse.xtext.util.EmfFormatter;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotation;
import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotationsFactory;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.Functions.Function4;
import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess.BindingFactory;
import org.eclipse.xtext.xtext.generator.model.TypeReference;

import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;

import io.sarl.lang.mwe2.codebuilder.extractor.CodeElementExtractor;

/** Generator of the builder for top element types.
 *
 * @author $Author: sgalland$
 * @version $FullVersion$
 * @mavengroupid $GroupId$
 * @mavenartifactid $ArtifactId$
 */
public class TopElementBuilderFragment extends AbstractSubCodeBuilderFragment {

	@Inject
	private BuilderFactoryContributions builderFactoryContributions;

	/** Replies the accessor to the generated type.
	 *
	 * @param generatedType the name of the type.
	 * @return the accessor name.
	 */
	@SuppressWarnings("static-method")
	@Pure
	protected String getGeneratedTypeAccessor(TypeReference generatedType) {
		return "get" //$NON-NLS-1$
				+ Strings.toFirstUpper(generatedType.getSimpleName())
				+ "()"; //$NON-NLS-1$
	}

	@Override
	protected Collection<AbstractSubCodeBuilderFragment> initializeSubGenerators(Injector injector) {
		final var fg1 = injector.getInstance(ConstructorBuilderFragment.class);
		final var fg2 = injector.getInstance(NamedMemberBuilderFragment.class);
		return Arrays.asList(fg1, fg2);
	}

	@Override
	public void generate() {
		generateIElementBuilder();
		generateElementBuilderImplementation();
		if (getCodeBuilderConfig().isISourceAppendableEnable()) {
			generateElementSourceAppender();
		}
		generateBuilderFactoryContributions();
		super.generate();
	}

	@Override
	public void generateRuntimeBindings(BindingFactory factory) {
		super.generateRuntimeBindings(factory);
		for (final var description : generateTopElements(false, false)) {
			bindElementDescription(factory, description.getElementDescription());
		}
	}

	/** Generate the contributions for the BuildFactory.
	 */
	protected void generateBuilderFactoryContributions() {
		final var topElements = generateTopElements(true, false);
		final var enableAppenders = getCodeBuilderConfig().isISourceAppendableEnable();
		for (final var element : topElements) {
			final var createFunctionName = "create" //$NON-NLS-1$
					+ Strings.toFirstUpper(element.getElementDescription().name());
			this.builderFactoryContributions.addContribution(new StringConcatenationClient() {
				@Override
				protected void appendTo(TargetStringConcatenation it) {
					it.append("\t/** Create the factory for a " + getLanguageName() + " " //$NON-NLS-1$ //$NON-NLS-2$
							+ element.getElementDescription().elementType().getSimpleName() + "."); //$NON-NLS-1$
					it.newLine();
					it.append("\t * @param name the name of the " + element.getElementDescription().name()); //$NON-NLS-1$
					it.newLine();
					it.append("\t * @param resourceSet the set of the resources that must be used for"); //$NON-NLS-1$
					it.newLine();
					it.append("\t *    containing the generated resource, and resolving types from names."); //$NON-NLS-1$
					it.newLine();
					it.append("\t * @return the factory."); //$NON-NLS-1$
					it.newLine();
					appendFileLineComment(it);
					it.append("\t */"); //$NON-NLS-1$
					it.newLine();
					it.append("\t@"); //$NON-NLS-1$
					it.append(Pure.class);
					it.newLine();
					it.append("\tpublic "); //$NON-NLS-1$
					it.append(element.getElementDescription().builderInterfaceType());
					it.append(" "); //$NON-NLS-1$
					it.append(createFunctionName);
					it.append("(String name, "); //$NON-NLS-1$
					it.append(ResourceSet.class);
					it.append(" resourceSet) {"); //$NON-NLS-1$
					it.newLine();
					it.append("\t\t return "); //$NON-NLS-1$
					it.append(createFunctionName);
					it.append("(name, createResource(resourceSet));"); //$NON-NLS-1$
					it.newLine();
					it.append("\t}"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
					it.append("\t/** Create the factory for a " + getLanguageName() + " " //$NON-NLS-1$ //$NON-NLS-2$
							+ element.getElementDescription().elementType().getSimpleName() + "."); //$NON-NLS-1$
					it.newLine();
					it.append("\t * @param name the name of the " + element.getElementDescription().name()); //$NON-NLS-1$
					it.newLine();
					it.append("\t * @param resource the resource that must be used for"); //$NON-NLS-1$
					it.newLine();
					it.append("\t *    containing the generated element, and resolving types from names."); //$NON-NLS-1$
					it.newLine();
					it.append("\t * @return the factory."); //$NON-NLS-1$
					it.newLine();
					appendFileLineComment(it);
					it.append("\t */"); //$NON-NLS-1$
					it.newLine();
					it.append("\t@"); //$NON-NLS-1$
					it.append(Pure.class);
					it.newLine();
					it.append("\tpublic "); //$NON-NLS-1$
					it.append(element.getElementDescription().builderInterfaceType());
					it.append(" "); //$NON-NLS-1$
					it.append(createFunctionName);
					it.append("(String name, "); //$NON-NLS-1$
					it.append(Resource.class);
					it.append(" resource) {"); //$NON-NLS-1$
					it.newLine();
					it.append("\t\t"); //$NON-NLS-1$
					it.append(getScriptBuilderInterface());
					it.append(" scriptBuilder = createScript(getFooPackageName(), resource);"); //$NON-NLS-1$
					it.newLine();
					it.append("\t\treturn scriptBuilder.add"); //$NON-NLS-1$
					it.append(Strings.toFirstUpper(element.getElementDescription().name()));
					it.append("(name);"); //$NON-NLS-1$
					it.newLine();
					it.append("\t}"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
				}
			});
			if (enableAppenders) {
				final var buildFunctionName = "build" + Strings.toFirstUpper(//$NON-NLS-1$
						element.getElementDescription().name());
				final var appender = getCodeElementExtractor().getElementAppenderImpl(
						element.getElementDescription().name());
				this.builderFactoryContributions.addContribution(new StringConcatenationClient() {
					@Override
					protected void appendTo(TargetStringConcatenation it) {
						it.append("\t/** Create the appender for a " + getLanguageName() + " " //$NON-NLS-1$ //$NON-NLS-2$
								+ element.getElementDescription().elementType().getSimpleName() + "."); //$NON-NLS-1$
						it.newLine();
						it.append("\t * @param name the name of the " + element.getElementDescription().name()); //$NON-NLS-1$
						it.newLine();
						it.append("\t * @param resourceSet the set of the resources that must be used for"); //$NON-NLS-1$
						it.newLine();
						it.append("\t *    containing the generated resource, and resolving types from names."); //$NON-NLS-1$
						it.newLine();
						it.append("\t * @return the appender."); //$NON-NLS-1$
						it.newLine();
						appendFileLineComment(it);
						it.append("\t */"); //$NON-NLS-1$
						it.newLine();
						it.append("\t@"); //$NON-NLS-1$
						it.append(Pure.class);
						it.newLine();
						it.append("\tpublic "); //$NON-NLS-1$
						it.append(appender);
						it.append(" "); //$NON-NLS-1$
						it.append(buildFunctionName);
						it.append("(String name, "); //$NON-NLS-1$
						it.append(ResourceSet.class);
						it.append(" resourceSet) {"); //$NON-NLS-1$
						it.newLine();
						it.append("\t\t"); //$NON-NLS-1$
						it.append(appender);
						it.append(" a = new "); //$NON-NLS-1$
						it.append(appender);
						it.append("("); //$NON-NLS-1$
						it.append(createFunctionName);
						it.append("(name, resourceSet));"); //$NON-NLS-1$
						it.newLine();
						it.append("\t\tgetInjector().injectMembers(a);"); //$NON-NLS-1$
						it.newLine();
						it.append("\t\treturn a;"); //$NON-NLS-1$
						it.newLine();
						it.append("\t}"); //$NON-NLS-1$
						it.newLineIfNotEmpty();
						it.newLine();
						it.append("\t/** Create the appender for a " + getLanguageName() + " " //$NON-NLS-1$ //$NON-NLS-2$
								+ element.getElementDescription().elementType().getSimpleName() + "."); //$NON-NLS-1$
						it.newLine();
						it.append("\t * @param name the name of the " + element.getElementDescription().name()); //$NON-NLS-1$
						it.newLine();
						it.append("\t * @param resource the resource that must be used for"); //$NON-NLS-1$
						it.newLine();
						it.append("\t *    containing the generated resource, and resolving types from names."); //$NON-NLS-1$
						it.newLine();
						it.append("\t * @return the appender."); //$NON-NLS-1$
						it.newLine();
						appendFileLineComment(it);
						it.append("\t */"); //$NON-NLS-1$
						it.newLine();
						it.append("\t@"); //$NON-NLS-1$
						it.append(Pure.class);
						it.newLine();
						it.append("\tpublic "); //$NON-NLS-1$
						it.append(appender);
						it.append(" "); //$NON-NLS-1$
						it.append(buildFunctionName);
						it.append("(String name, "); //$NON-NLS-1$
						it.append(Resource.class);
						it.append(" resource) {"); //$NON-NLS-1$
						it.newLine();
						it.append("\t\t"); //$NON-NLS-1$
						it.append(appender);
						it.append(" a = new "); //$NON-NLS-1$
						it.append(appender);
						it.append("("); //$NON-NLS-1$
						it.append(createFunctionName);
						it.append("(name, resource));"); //$NON-NLS-1$
						it.newLine();
						it.append("\t\tgetInjector().injectMembers(a);"); //$NON-NLS-1$
						it.newLine();
						it.append("\t\treturn a;"); //$NON-NLS-1$
						it.newLine();
						it.append("\t}"); //$NON-NLS-1$
						it.newLineIfNotEmpty();
						it.newLine();
					}
				});
			}
		}
	}

	/** Generate the element builder interface.
	 */
	protected void generateIElementBuilder() {
		final var topElements = generateTopElements(true, false);
		for (final var element : topElements) {
			final var content = new StringConcatenationClient() {
				@Override
				protected void appendTo(TargetStringConcatenation it) {
					it.append("/** Builder of a " + getLanguageName() //$NON-NLS-1$
					+ " " + element.getElementDescription().elementType().getSimpleName() //$NON-NLS-1$
					+ "."); //$NON-NLS-1$
					it.newLine();
					appendFileLineComment(it);
					it.append(" */"); //$NON-NLS-1$
					it.newLine();
					it.append("@SuppressWarnings(\"all\")"); //$NON-NLS-1$
					it.newLine();
					it.append("public interface "); //$NON-NLS-1$
					it.append(element.getElementDescription().builderInterfaceType().getSimpleName());
					it.append(" {"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
					it.append(element.getContent());
					for (final var cons : generateMembers(element.getConstructors(),
							element, true, false, true, false)) {
						it.append(cons);
					}
					for (final var mbr : generateMembers(element.getNamedMembers(),
							element, true, false, true, false)) {
						it.append(mbr);
					}
					for (final var mbr : generateMembers(element.getUnnamedMembers(),
							element, true, false, false, false)) {
						it.append(mbr);
					}
					for (final var mbr : generateMembers(element.getImplicitlyNamedMembers(),
							element, true, false, false, true)) {
						it.append(mbr);
					}
					it.append("}"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
				}
			};
			final var javaFile = getFileAccessFactory().createJavaFile(
					element.getElementDescription().builderInterfaceType(), content);
			javaFile.writeTo(getSrcGen());
		}
	}

	/** Generate the element builder interface.
	 */
	protected void generateElementSourceAppender() {
		final var topElements = generateTopElements(false, true);
		for (final var element : topElements) {
			final var appender = element.getElementDescription().appenderType();
			final var content = new StringConcatenationClient() {
				@Override
				protected void appendTo(TargetStringConcatenation it) {
					it.append("/** Source adapter of a " + getLanguageName() //$NON-NLS-1$
					+ " " + element.getElementDescription().elementType().getSimpleName() //$NON-NLS-1$
					+ "."); //$NON-NLS-1$
					it.newLine();
					appendFileLineComment(it);
					it.append(" */"); //$NON-NLS-1$
					it.newLine();
					it.append("@SuppressWarnings(\"all\")"); //$NON-NLS-1$
					it.newLine();
					it.append("public class "); //$NON-NLS-1$
					it.append(appender.getSimpleName());
					it.append(" extends "); //$NON-NLS-1$
					it.append(getCodeElementExtractor().getAbstractAppenderImpl());
					it.append(" implements "); //$NON-NLS-1$
					it.append(element.getElementDescription().builderInterfaceType());
					it.append(" {"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
					it.append(generateAppenderMembers(appender.getSimpleName(),
							element.getElementDescription().builderInterfaceType(),
							getGeneratedTypeAccessor(element.getElementDescription().elementType())));
					it.append(element.getContent());
					for (final var cons : generateMembers(element.getConstructors(), element,
							false, true, true, false)) {
						it.append(cons);
					}
					for (final var mbr : generateMembers(element.getNamedMembers(), element,
							false, true, true, false)) {
						it.append(mbr);
					}
					for (final var mbr : generateMembers(element.getUnnamedMembers(), element,
							false, true, false, false)) {
						it.append(mbr);
					}
					for (final var mbr : generateMembers(element.getImplicitlyNamedMembers(), element,
							false, true, false, true)) {
						it.append(mbr);
					}
					it.append("}"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
				}
			};
			final var javaFile = getFileAccessFactory().createJavaFile(appender, content);
			javaFile.writeTo(getSrcGen());
		}
	}

	/** Generate the element builder implementation.
	 */
	protected void generateElementBuilderImplementation() {
		final var topElements = generateTopElements(false, false);
		for (final var element : topElements) {
			final var content = new StringConcatenationClient() {
				@Override
				protected void appendTo(TargetStringConcatenation it) {
					it.append("/** Builder of a " + getLanguageName() //$NON-NLS-1$
					+ " " + element.getElementDescription().elementType().getSimpleName() //$NON-NLS-1$
					+ "."); //$NON-NLS-1$
					it.newLine();
					appendFileLineComment(it);
					it.append(" */"); //$NON-NLS-1$
					it.newLine();
					it.append("@SuppressWarnings(\"all\")"); //$NON-NLS-1$
					it.newLine();
					it.append("public class "); //$NON-NLS-1$
					it.append(element.getElementDescription().builderImplementationType().getSimpleName());
					it.append(" extends "); //$NON-NLS-1$
					it.append(getAbstractBuilderImpl());
					it.append(" implements "); //$NON-NLS-1$
					it.append(element.getElementDescription().builderInterfaceType());
					it.append(" {"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
					it.append(element.getContent());
					for (final var cons : generateMembers(element.getConstructors(), element,
							false, false, true, false)) {
						it.append(cons);
					}
					for (final var mbr : generateMembers(element.getNamedMembers(), element,
							false, false, true, false)) {
						it.append(mbr);
					}
					for (final var mbr : generateMembers(element.getUnnamedMembers(), element,
							false, false, false, false)) {
						it.append(mbr);
					}
					for (final var mbr : generateMembers(element.getImplicitlyNamedMembers(), element,
							false, false, false, true)) {
						it.append(mbr);
					}
					it.append("}"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
				}
			};
			final var javaFile = getFileAccessFactory().createJavaFile(
					element.getElementDescription().builderImplementationType(), content);
			javaFile.writeTo(getSrcGen());
		}
	}

	/** Generate the creators of members.
	 * If {@code namedMembers} and {@code implicitlyNamedMembers} are both {@code false}, then
	 * the members have a name that is a JVM type reference.
	 *
	 * @param grammarContainers the containers from which the members' definitions could be retreived.
	 * @param description the description of the top element.
	 * @param forInterface {@code true} if the generation is for an interface.
	 * @param forAppender {@code true} if the generation is for the ISourceAppender.
	 * @param namedMembers {@code true} if the members have explicit name attribute.
	 * @param implicitlyNamedMembers {@code true} if the members have implicit name attribute.
	 * @return the code.
	 */
	protected List<StringConcatenationClient> generateMembers(
			Collection<CodeElementExtractor.ElementDescription> grammarContainers,
			TopElementDescription description, boolean forInterface, boolean forAppender, boolean namedMembers, boolean implicitlyNamedMembers) {
		final var clients = new ArrayList<StringConcatenationClient>();
		for (final var elementDescription : grammarContainers) {
			clients.addAll(generateMember(elementDescription, description, forInterface, forAppender, namedMembers, implicitlyNamedMembers));
		}
		return clients;
	}

	/** Generate a member from the grammar.
	 * If {@code namedMember} and {@code implicitlyNamedMember} are both {@code false}, then
	 * the member has a name that is a JVM type reference.
	 *
	 * @param memberDescription the description of the member.
	 * @param topElementDescription the description of the top element.
	 * @param forInterface indicates if the generated code is for interfaces.
	 * @param forAppender {@code true} if the generation is for the ISourceAppender.
	 * @param namedMember {@code true} if the members have explicit name attribute.
	 * @param implicitlyNamedMember {@code true} if the members have implicit name attribute.
	 * @return the member functions.
	 */
	protected List<StringConcatenationClient> generateMember(CodeElementExtractor.ElementDescription memberDescription,
			TopElementDescription topElementDescription, boolean forInterface, boolean forAppender, boolean namedMember,
			boolean implicitlyNamedMember) {
		if (namedMember) {
			return generateNamedMember(memberDescription, topElementDescription, forInterface, forAppender);
		}
		if (implicitlyNamedMember) {
			return generateImplicitlyNamedMember(memberDescription, topElementDescription, forInterface, forAppender);
		}
		return generateUnnamedMember(memberDescription, topElementDescription, forInterface, forAppender);
	}

	private List<StringConcatenationClient> generateNamedMember(CodeElementExtractor.ElementDescription memberDescription,
			TopElementDescription topElementDescription, boolean forInterface, boolean forAppender) {
		final var memberName = Strings.toFirstUpper(memberDescription.name());
		final var classifier = memberDescription.elementType();
		final var clients = new ArrayList<StringConcatenationClient>();
		final var hasName = new AtomicBoolean(false);
		final var hasTypeName = new AtomicBoolean(false);
		for (final var assignment : GrammarUtil.containedAssignments(memberDescription.grammarComponent())) {
			if (Objects.equals(getCodeBuilderConfig().getMemberNameExtensionGrammarName(), assignment.getFeature())) {
				hasName.set(true);
				if (nameMatches(assignment.getTerminal(), getCodeBuilderConfig().getTypeReferenceGrammarPattern())) {
					hasTypeName.set(true);
				}
			}
		}
		var tmpModifiers = getCodeBuilderConfig().getModifiers().get(classifier.getSimpleName());
		if (tmpModifiers == null || tmpModifiers.isEmpty()) {
			tmpModifiers = Collections.singletonList(""); //$NON-NLS-1$
		}
		final var modifiers = tmpModifiers;
		final var builderType = memberDescription.builderInterfaceType();
		if (!forInterface && !forAppender) {
			clients.add(new StringConcatenationClient() {
				@Override
				protected void appendTo(TargetStringConcatenation it) {
					appendEmptyComment(it);
					it.append("\t@"); //$NON-NLS-1$
					it.append(getInjectType());
					it.newLine();
					it.append("\tprivate "); //$NON-NLS-1$
					it.append(Provider.class);
					it.append("<"); //$NON-NLS-1$
					it.append(builderType);
					it.append("> "); //$NON-NLS-1$
					it.append(Strings.toFirstLower(builderType.getSimpleName()));
					it.append("Provider;"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
				}
			});
		}
		for (final var modifier : modifiers) {
			final String functionName;
			if (modifiers.size() > 1) {
				functionName = "add" + Strings.toFirstUpper(modifier) + memberName; //$NON-NLS-1$
			} else {
				functionName = "add" + memberName; //$NON-NLS-1$
			}
			clients.add(new StringConcatenationClient() {
				@Override
				protected void appendTo(TargetStringConcatenation it) {
					it.append("\t/** Create " + getAorAnArticle(memberName) //$NON-NLS-1$
					+ " " + memberName + "."); //$NON-NLS-1$//$NON-NLS-2$
					it.newLine();
					if (hasName.get()) {
						it.append("\t * @param name the "); //$NON-NLS-1$
						if (hasTypeName.get()) {
							it.append("type"); //$NON-NLS-1$
						} else {
							it.append("name"); //$NON-NLS-1$
						}
						it.append(" of the " + memberName + "."); //$NON-NLS-1$ //$NON-NLS-2$
						it.newLine();
					}
					it.append("\t * @return the builder."); //$NON-NLS-1$
					it.newLine();
					appendFileLineComment(it);
					it.append("\t */"); //$NON-NLS-1$
					it.newLine();
					it.append("\t"); //$NON-NLS-1$
					if (!forInterface) {
						it.append("public "); //$NON-NLS-1$
					}
					it.append(builderType);
					it.append(" "); //$NON-NLS-1$
					it.append(functionName);
					it.append("("); //$NON-NLS-1$
					if (hasName.get()) {
						it.append("String name"); //$NON-NLS-1$
					}
					it.append(")"); //$NON-NLS-1$
					if (forInterface) {
						it.append(";"); //$NON-NLS-1$
					} else {
						it.append(" {"); //$NON-NLS-1$
						it.newLine();
						it.append("\t\t"); //$NON-NLS-1$
						if (forAppender) {
							it.append("return this.builder."); //$NON-NLS-1$
							it.append(functionName);
							it.append("("); //$NON-NLS-1$
							if (hasName.get()) {
								it.append("name"); //$NON-NLS-1$
							}
							it.append(");"); //$NON-NLS-1$
						} else {
							it.append(builderType);
							it.append(" builder = this."); //$NON-NLS-1$
							it.append(Strings.toFirstLower(builderType.getSimpleName()));
							it.append("Provider.get();"); //$NON-NLS-1$
							it.newLine();
							it.append("\t\tbuilder.eInit("); //$NON-NLS-1$
							it.append(getGeneratedTypeAccessor(topElementDescription.getElementDescription().elementType()));
							if (hasName.get()) {
								it.append(", name"); //$NON-NLS-1$
							}
							if (!Strings.isEmpty(modifier) && modifiers.size() > 1) {
								it.append(", \""); //$NON-NLS-1$
								it.append(Strings.convertToJavaString(modifier));
								it.append("\""); //$NON-NLS-1$
							}
							it.append(", getTypeResolutionContext());"); //$NON-NLS-1$
							it.newLine();
							it.append("\t\treturn builder;"); //$NON-NLS-1$
						}
						it.newLine();
						it.append("\t}"); //$NON-NLS-1$
					}
					it.newLineIfNotEmpty();
					it.newLine();
					if (hasName.get() && hasTypeName.get()) {
						it.append("\t/** Create " + getAorAnArticle(memberName) //$NON-NLS-1$
								+ " " + memberName + "."); //$NON-NLS-1$//$NON-NLS-2$
						it.newLine();
						it.append("\t * @param name the type of the " + memberName + "."); //$NON-NLS-1$ //$NON-NLS-2$
						it.newLine();
						it.append("\t * @return the builder."); //$NON-NLS-1$
						it.newLine();
						appendFileLineComment(it);
						it.append("\t */"); //$NON-NLS-1$
						it.newLine();
						it.append("\t"); //$NON-NLS-1$
						if (!forInterface) {
							it.append("public "); //$NON-NLS-1$
						}
						it.append(builderType);
						it.append(" "); //$NON-NLS-1$
						it.append(functionName);
						it.append("("); //$NON-NLS-1$
						it.append(JvmParameterizedTypeReference.class);
						it.append(" name"); //$NON-NLS-1$
						it.append(")"); //$NON-NLS-1$
						if (forInterface) {
							it.append(";"); //$NON-NLS-1$
						} else {
							it.append(" {"); //$NON-NLS-1$
							it.newLine();
							it.append("\t\t"); //$NON-NLS-1$
							if (forAppender) {
								it.append("return this.builder."); //$NON-NLS-1$
								it.append(functionName);
								it.append("("); //$NON-NLS-1$
								it.append("name"); //$NON-NLS-1$
								it.append(");"); //$NON-NLS-1$
							} else {
								it.append(builderType);
								it.append(" builder = this."); //$NON-NLS-1$
								it.append(Strings.toFirstLower(builderType.getSimpleName()));
								it.append("Provider.get();"); //$NON-NLS-1$
								it.newLine();
								it.append("\t\tbuilder.eInit("); //$NON-NLS-1$
								it.append(getGeneratedTypeAccessor(topElementDescription.getElementDescription().elementType()));
								it.append(", name"); //$NON-NLS-1$
								if (!Strings.isEmpty(modifier) && modifiers.size() > 1) {
									it.append(", \""); //$NON-NLS-1$
									it.append(Strings.convertToJavaString(modifier));
									it.append("\""); //$NON-NLS-1$
								}
								it.append(", getTypeResolutionContext());"); //$NON-NLS-1$
								it.newLine();
								it.append("\t\treturn builder;"); //$NON-NLS-1$
							}
							it.newLine();
							it.append("\t}"); //$NON-NLS-1$
						}
						it.newLineIfNotEmpty();
						it.newLine();
					}
				}
			});
		}
		if (modifiers.size() > 1) {
			final var firstModifier = Strings.toFirstUpper(modifiers.get(0));
			final var functionName = "add" + memberName; //$NON-NLS-1$
			final var callFunctionName = "add" + firstModifier + memberName; //$NON-NLS-1$
			clients.add(new StringConcatenationClient() {
				@Override
				protected void appendTo(TargetStringConcatenation it) {
					it.append("\t/** Create " + getAorAnArticle(memberName) //$NON-NLS-1$
					+ " " + memberName + "."); //$NON-NLS-1$//$NON-NLS-2$
					it.append("\t *"); //$NON-NLS-1$
					it.newLine();
					it.append("\t * <p>This function is equivalent to {@link #"); //$NON-NLS-1$
					it.append(callFunctionName);
					it.append("}."); //$NON-NLS-1$
					it.newLine();
					if (hasName.get()) {
						it.append("\t * @param name the "); //$NON-NLS-1$
						if (hasTypeName.get()) {
							it.append("type"); //$NON-NLS-1$
						} else {
							it.append("name"); //$NON-NLS-1$
						}
						it.append(" of the " + memberName + "."); //$NON-NLS-1$ //$NON-NLS-2$
						it.newLine();
					}
					it.append("\t * @return the builder."); //$NON-NLS-1$
					it.newLine();
					appendFileLineComment(it);
					it.append("\t */"); //$NON-NLS-1$
					it.newLine();
					it.append("\t"); //$NON-NLS-1$
					if (!forInterface) {
						it.append("public "); //$NON-NLS-1$
					}
					it.append(builderType);
					it.append(" "); //$NON-NLS-1$
					it.append(functionName);
					it.append("("); //$NON-NLS-1$
					if (hasName.get()) {
						it.append("String name"); //$NON-NLS-1$
					}
					it.append(")"); //$NON-NLS-1$
					if (forInterface) {
						it.append(";"); //$NON-NLS-1$
					} else {
						it.append(" {"); //$NON-NLS-1$
						it.newLine();
						it.append("\t\treturn "); //$NON-NLS-1$
						if (forAppender) {
							it.append("this.builder."); //$NON-NLS-1$
							it.append(functionName);
							it.append("("); //$NON-NLS-1$
							if (hasName.get()) {
								it.append("name"); //$NON-NLS-1$
							}
							it.append(");"); //$NON-NLS-1$
						} else {
							it.append("this."); //$NON-NLS-1$
							it.append(callFunctionName);
							it.append("("); //$NON-NLS-1$
							if (hasName.get()) {
								it.append("name"); //$NON-NLS-1$
							}
							it.append(");"); //$NON-NLS-1$
						}
						it.newLine();
						it.append("\t}"); //$NON-NLS-1$
					}
					it.newLineIfNotEmpty();
					it.newLine();
				}
			});
		}
		return clients;
	}

	private List<StringConcatenationClient> generateImplicitlyNamedMember(CodeElementExtractor.ElementDescription memberDescription,
			TopElementDescription topElementDescription, boolean forInterface, boolean forAppender) {
		final var memberName = Strings.toFirstUpper(memberDescription.name());
		final var classifier = memberDescription.elementType();
		final var clients = new ArrayList<StringConcatenationClient>();
		final var hasName = new AtomicBoolean(false);
		final var hasTypeName = new AtomicBoolean(false);
		for (final var assignment : GrammarUtil.containedAssignments(memberDescription.grammarComponent())) {
			if (getCodeBuilderConfig().getIndirectlyNamedMemberExtensionGrammarNames().contains(assignment.getFeature())) {
				hasName.set(true);
				if (nameMatches(assignment.getTerminal(), getCodeBuilderConfig().getTypeReferenceGrammarPattern())) {
					hasTypeName.set(true);
				}
			}
		}
		var tmpModifiers = getCodeBuilderConfig().getModifiers().get(classifier.getSimpleName());
		if (tmpModifiers == null || tmpModifiers.isEmpty()) {
			tmpModifiers = Collections.singletonList(""); //$NON-NLS-1$
		}
		final var modifiers = tmpModifiers;
		final var builderType = memberDescription.builderInterfaceType();
		if (!forInterface && !forAppender) {
			clients.add(new StringConcatenationClient() {
				@Override
				protected void appendTo(TargetStringConcatenation it) {
					appendEmptyComment(it);
					it.append("\t@"); //$NON-NLS-1$
					it.append(getInjectType());
					it.newLine();
					it.append("\tprivate "); //$NON-NLS-1$
					it.append(Provider.class);
					it.append("<"); //$NON-NLS-1$
					it.append(builderType);
					it.append("> "); //$NON-NLS-1$
					it.append(Strings.toFirstLower(builderType.getSimpleName()));
					it.append("Provider;"); //$NON-NLS-1$
					it.newLineIfNotEmpty();
					it.newLine();
				}
			});
		}
		for (final var modifier : modifiers) {
			final String functionName;
			if (modifiers.size() > 1) {
				functionName = "add" + Strings.toFirstUpper(modifier) + memberName; //$NON-NLS-1$
			} else {
				functionName = "add" + memberName; //$NON-NLS-1$
			}
			clients.add(new StringConcatenationClient() {
				@Override
				protected void appendTo(TargetStringConcatenation it) {
					it.append("\t/** Create " + getAorAnArticle(memberName) //$NON-NLS-1$
					+ " " + memberName + "."); //$NON-NLS-1$//$NON-NLS-2$
					it.newLine();
					if (hasName.get()) {
						it.append("\t * @param name the "); //$NON-NLS-1$
						if (hasTypeName.get()) {
							it.append("type"); //$NON-NLS-1$
						} else {
							it.append("name"); //$NON-NLS-1$
						}
						it.append(" of the " + memberName + "."); //$NON-NLS-1$ //$NON-NLS-2$
						it.newLine();
					}
					it.append("\t * @return the builder."); //$NON-NLS-1$
					it.newLine();
					appendFileLineComment(it);
					it.append("\t */"); //$NON-NLS-1$
					it.newLine();
					it.append("\t"); //$NON-NLS-1$
					if (!forInterface) {
						it.append("public "); //$NON-NLS-1$
					}
					it.append(builderType);
					it.append(" "); //$NON-NLS-1$
					it.append(functionName);
					it.append("("); //$NON-NLS-1$
					if (hasName.get()) {
						it.append("String name"); //$NON-NLS-1$
					}
					it.append(")"); //$NON-NLS-1$
