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

com.sap.cds.util.OnConditionAnalyzer Maven / Gradle / Ivy

There is a newer version: 3.8.0
Show newest version
/*******************************************************************
 * © 2019 SAP SE or an SAP affiliate company. All rights reserved. *
 *******************************************************************/
package com.sap.cds.util;

import static com.sap.cds.reflect.impl.DraftAdapter.IS_ACTIVE_ENTITY;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

import com.sap.cds.SessionContext;
import com.sap.cds.impl.AssociationAnalyzer;
import com.sap.cds.impl.builder.model.ElementRefImpl;
import com.sap.cds.ql.ElementRef;
import com.sap.cds.ql.Value;
import com.sap.cds.ql.cqn.CqnComparisonPredicate;
import com.sap.cds.ql.cqn.CqnConnectivePredicate;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnLiteral;
import com.sap.cds.ql.cqn.CqnModifier;
import com.sap.cds.ql.cqn.CqnNegation;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnToken;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.ql.impl.ExpressionVisitor;
import com.sap.cds.reflect.CdsElement;

public class OnConditionAnalyzer {

	private final String associationName;
	private final boolean reverse;
	private final CqnPredicate on;

	public OnConditionAnalyzer(String associationName, CqnPredicate on, boolean reverse) {
		this.associationName = associationName;
		this.reverse = reverse;
		this.on = on;
	}

	public OnConditionAnalyzer(CdsElement association, boolean reverse, SessionContext session) {
		this(association.getName(), onCondition(association, session), reverse);
	}

	private static CqnPredicate onCondition(CdsElement association, SessionContext session) {
		AssociationAnalyzer associationAnalyzer = new AssociationAnalyzer();
		return associationAnalyzer.getOnCondition(association);
	}

	private void getFkMapping(BiConsumer consumer) {
		CqnVisitor v = new CqnVisitor() {
			@Override
			public void visit(CqnComparisonPredicate cmp) {
				if (!cmp.operator().equals(CqnComparisonPredicate.Operator.EQ)) {
					throw new UnsupportedOperationException("expecting comparison predicate \"=\"");
				}
				CqnValue lhs = cmp.left();
				CqnValue rhs = cmp.right();
				if (isRefToTarget(rhs) ^ reverse) {
					consumer.accept(lhs, rhs);
				} else {
					consumer.accept(rhs, lhs);
				}
			}

			@Override
			public void visit(CqnConnectivePredicate connective) {
				if (connective.operator() != CqnConnectivePredicate.Operator.AND) {
					throw new UnsupportedOperationException("expecting 'and' operator");
				}
			}

			@Override
			public void visit(CqnNegation neg) {
				throw new UnsupportedOperationException("expecting 'and' operator");
			}

		};
		on.accept(v);
	}

	public Map getFkValues(Map sourceObject) {
		Map fkValues = new HashMap<>();
		getFkMapping((fk, pk) -> addMapping(sourceObject, fkValues, fk, pk));
		return fkValues;
	}

	public Map getParentPkValues() {
		Map pkValues = new HashMap<>();
		getFkMapping((fk, pk) -> addMapping(Collections.emptyMap(), pkValues, fk, pk));
		return pkValues;
	}

	/**
	 * Calculate the target predicate based on the on-condition and predicates'
	 * concrete values. Example: on-condition has (PK) id = (FK)
	 * parent_id, filter contains where id = 42. The
	 * resulting predicate will be where parent_id = 42
	 * 
	 * @param predicate containing concrete values
	 * @return calculated target predicate
	 */
	public CqnPredicate getTargetPredicate(CqnPredicate predicate) {
		final Map pkToFkMap = new HashMap<>();
		// TODO: workaround for Draft. Until usage of $generatedFieldName is done
		pkToFkMap.put(IS_ACTIVE_ENTITY, IS_ACTIVE_ENTITY);
		getFkMapping((fk, pk) -> addMapping(pkToFkMap, (CqnElementRef) pk, (CqnElementRef) fk));

		return ExpressionVisitor.copy(predicate, new CqnModifier() {
			@Override
			public Value ref(ElementRef ref) {
				return ElementRefImpl.element(pkToFkMap.get(ref.lastSegment()));
			}
		});
	}

	private void addMapping(Map sourceObject, Map fkValues, CqnToken fkSide,
			CqnToken pkSide) {
		String fk = ref(fkSide).lastSegment();
		Object value = val(sourceObject, pkSide);
		fkValues.put(fk, value);
	}

	private void addMapping(Map values, CqnElementRef fkSide, CqnElementRef pkSide) {
		values.put(fkSide.lastSegment(), pkSide.lastSegment());
	}

	private Object val(Map sourceObject, CqnToken token) {
		if (token instanceof CqnElementRef) {
			String pk = ref(token).lastSegment();
			return sourceObject.get(pk);
		}

		if (token instanceof CqnLiteral) {
			return literal(token).value();
		}

		throw new UnsupportedOperationException();
	}

	private boolean isRefToTarget(CqnValue token) {
		if (!(token instanceof CqnElementRef)) {
			return false;
		}
		CqnElementRef ref = ref(token);

		return associationName.equals(ref.firstSegment());
	}

	@SuppressWarnings("unchecked")
	private static CqnLiteral literal(CqnToken token) {
		return (CqnLiteral) token;
	}

	private static CqnElementRef ref(CqnToken token) {
		return (CqnElementRef) token;
	}

}