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

com.xlrit.gears.engine.export.AstVisitor Maven / Gradle / Ivy

package com.xlrit.gears.engine.export;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.xlrit.gears.base.snel.SnelBaseVisitor;
import com.xlrit.gears.base.snel.SnelParser;
import com.xlrit.gears.engine.meta.BaseField;
import com.xlrit.gears.engine.meta.EntityInfo;
import com.xlrit.gears.engine.meta.MetaManager;
import com.xlrit.gears.engine.snel.Expression;
import com.xlrit.gears.engine.snel.ExpressionBuilderVisitor;
import lombok.RequiredArgsConstructor;
import org.antlr.v4.runtime.ParserRuleContext;

@RequiredArgsConstructor
class AstVisitor extends SnelBaseVisitor {
	private static final Ast.AsteriskPath ASTERISK = new Ast.AsteriskPath();

	private final ExpressionBuilderVisitor expressionBuilder = new ExpressionBuilderVisitor();
	private final MetaManager metaManager;
	private final Deque> currentType = new ArrayDeque<>();

	@Override
	public Ast visitStartExportSpec(SnelParser.StartExportSpecContext ctx) {
		List items = ctx.exportItem().stream().map(this::visitExportItem).toList();
		return new Ast.ExportDef(items);
	}

	@Override
	public Ast.ExportItem visitExportItem(SnelParser.ExportItemContext ctx) {
		String name = ctx.identifier().getText();
		Ast.RootPath root = visitRootPath(ctx.rootPath());
		return new Ast.ExportItem(name, root);
	}

	@Override
	public Ast.RootPath visitRootPath(SnelParser.RootPathContext ctx) {
		String name = ctx.identifier().getText();
		EntityInfo entityInfo = metaManager.getCollectionInfo(name);
		if (entityInfo == null) {
			throw new RuntimeException("Collection '" + name + "' not found");
		}
		List selections = getSubPaths(ctx.subPaths(), entityInfo);
		Optional filter = getFilter(ctx);
		return new Ast.RootPath(entityInfo, selections, filter);
	}

	@Override
	public Ast.SubPath visitSubPath(SnelParser.SubPathContext ctx) {
		return (Ast.SubPath) super.visitChildren(ctx);
	}

	@Override
	public Ast.AttributePath visitAttributePath(SnelParser.AttributePathContext ctx) {
		String name = ctx.identifier().getText();
		BaseField field = currentType.peek().getField(name);
		return new Ast.AttributePath(field);
	}

	@Override
	public Ast.RelationPath visitRelationPath(SnelParser.RelationPathContext ctx) {
		String name = ctx.identifier().getText();
		BaseField field = currentType.peek().getField(name);
		List selections = getSubPaths(ctx.subPaths(), field.getAssociatedEntityInfo());
		Optional filter = getFilter(ctx);
		return new Ast.RelationPath(field, selections, filter);
	}

	@Override
	public Ast.AsteriskPath visitAsteriskPath(SnelParser.AsteriskPathContext ctx) {
		return ASTERISK;
	}

	private Optional getFilter(ParserRuleContext ctx) {
		if (ctx instanceof SnelParser.RootPathContext rootCtx) {
			return Optional.ofNullable(rootCtx.filter()).map(f -> f.accept(expressionBuilder));
		}
		else if (ctx instanceof SnelParser.RelationPathContext relCtx) {
			return Optional.ofNullable(relCtx.filter()).map(f -> f.accept(expressionBuilder));
		}
		throw new IllegalArgumentException();
	}

	private List getSubPaths(SnelParser.SubPathsContext ctx, EntityInfo entityInfo) {
		Objects.requireNonNull(ctx, "ctx is required");
		Objects.requireNonNull(entityInfo, "entityInfo is required");
		currentType.push(entityInfo);
		List selections = normalizeSelections(ctx.subPath().stream().map(this::visitSubPath).toList());
		currentType.pop();
		return selections;
	}

	private List normalizeSelections(List selections) {
		if (!(selections.contains(ASTERISK))) {
			return selections;
		}
		Set explicitSelections = selections.stream().map(Ast.SubPath::name).collect(Collectors.toSet());
		List additionalPaths = currentType.peek().getFields().stream()
			.filter(field -> !explicitSelections.contains(field.getName()) && !field.isCalculated())
			.map(this::createDefaultPath)
			.toList();
		return selections.stream()
			.flatMap(selection -> selection == ASTERISK ? additionalPaths.stream() : Stream.of(selection))
			.toList();
	}

	private Ast.SubPath createDefaultPath(BaseField baseField) {
		return baseField.isEntityReference()
			? new Ast.RelationPath(baseField, List.of(), Optional.empty()) // no explicit selection means that only the id and type are serialized
			: new Ast.AttributePath(baseField);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy