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

org.optaplanner.constraint.streams.drools.common.QuadLeftHandSide Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.optaplanner.constraint.streams.drools.common;

import static java.util.Collections.singletonList;
import static org.drools.model.DSL.exists;
import static org.drools.model.DSL.not;
import static org.drools.model.PatternDSL.betaIndexedBy;
import static org.drools.model.PatternDSL.pattern;

import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.drools.model.BetaIndex4;
import org.drools.model.DSL;
import org.drools.model.PatternDSL;
import org.drools.model.Variable;
import org.drools.model.functions.Function4;
import org.drools.model.functions.Predicate5;
import org.drools.model.functions.accumulate.AccumulateFunction;
import org.drools.model.view.ViewItem;
import org.optaplanner.constraint.streams.common.penta.DefaultPentaJoiner;
import org.optaplanner.constraint.streams.common.penta.FilteringPentaJoiner;
import org.optaplanner.constraint.streams.drools.DroolsVariableFactory;
import org.optaplanner.core.api.function.PentaPredicate;
import org.optaplanner.core.api.function.QuadFunction;
import org.optaplanner.core.api.function.QuadPredicate;
import org.optaplanner.core.api.function.ToIntQuadFunction;
import org.optaplanner.core.api.function.ToLongQuadFunction;
import org.optaplanner.core.api.score.stream.penta.PentaJoiner;
import org.optaplanner.core.api.score.stream.quad.QuadConstraintCollector;
import org.optaplanner.core.impl.score.stream.JoinerType;

/**
 * Represents the left hand side of a Drools rule, the result of which are four variables.
 * For more, see {@link UniLeftHandSide} and {@link BiLeftHandSide}.
 *
 * @param  generic type of the first resulting variable
 * @param  generic type of the second resulting variable
 * @param  generic type of the third resulting variable
 * @param  generic type of the fourth resulting variable
 */
public final class QuadLeftHandSide extends AbstractLeftHandSide {

    private final PatternVariable patternVariableA;
    private final PatternVariable patternVariableB;
    private final PatternVariable patternVariableC;
    private final PatternVariable patternVariableD;
    private final QuadRuleContext ruleContext;

    QuadLeftHandSide(Variable variableA, Variable variableB, Variable variableC,
            PatternVariable patternVariableD, DroolsVariableFactory variableFactory) {
        this(new DetachedPatternVariable<>(variableA), new DetachedPatternVariable<>(variableB),
                new DetachedPatternVariable<>(variableC), patternVariableD, variableFactory);
    }

    QuadLeftHandSide(PatternVariable patternVariableA, PatternVariable patternVariableB,
            PatternVariable patternVariableC, PatternVariable patternVariableD,
            DroolsVariableFactory variableFactory) {
        super(variableFactory);
        this.patternVariableA = Objects.requireNonNull(patternVariableA);
        this.patternVariableB = Objects.requireNonNull(patternVariableB);
        this.patternVariableC = Objects.requireNonNull(patternVariableC);
        this.patternVariableD = Objects.requireNonNull(patternVariableD);
        this.ruleContext = buildRuleContext();
    }

    private QuadRuleContext buildRuleContext() {
        ViewItem[] viewItems = Stream.of(patternVariableA, patternVariableB, patternVariableC, patternVariableD)
                .flatMap(variable -> variable.build().stream())
                .toArray((IntFunction[]>) ViewItem[]::new);
        return new QuadRuleContext<>(patternVariableA.getPrimaryVariable(), patternVariableB.getPrimaryVariable(),
                patternVariableC.getPrimaryVariable(), patternVariableD.getPrimaryVariable(), viewItems);
    }

    public QuadLeftHandSide andFilter(QuadPredicate predicate) {
        return new QuadLeftHandSide<>(patternVariableA, patternVariableB, patternVariableC,
                patternVariableD.filter(predicate, patternVariableA.getPrimaryVariable(),
                        patternVariableB.getPrimaryVariable(), patternVariableC.getPrimaryVariable()),
                variableFactory);
    }

    private  QuadLeftHandSide applyJoiners(Class otherFactType, Predicate nullityFilter,
            DefaultPentaJoiner joiner, PentaPredicate predicate, boolean shouldExist) {
        Variable toExist = variableFactory.createVariable(otherFactType, "toExist");
        PatternDSL.PatternDef existencePattern = pattern(toExist);
        if (nullityFilter != null) {
            existencePattern = existencePattern.expr("Exclude nulls using " + nullityFilter,
                    nullityFilter::test);
        }
        if (joiner == null) {
            return applyFilters(existencePattern, predicate, shouldExist);
        }
        int joinerCount = joiner.getJoinerCount();
        for (int mappingIndex = 0; mappingIndex < joinerCount; mappingIndex++) {
            JoinerType joinerType = joiner.getJoinerType(mappingIndex);
            QuadFunction leftMapping = joiner.getLeftMapping(mappingIndex);
            Function rightMapping = joiner.getRightMapping(mappingIndex);
            Predicate5 joinPredicate =
                    (e, a, b, c, d) -> joinerType.matches(leftMapping.apply(a, b, c, d), rightMapping.apply(e));
            existencePattern = existencePattern.expr("Join using joiner #" + mappingIndex + " in " + joiner,
                    patternVariableA.getPrimaryVariable(), patternVariableB.getPrimaryVariable(),
                    patternVariableC.getPrimaryVariable(), patternVariableD.getPrimaryVariable(), joinPredicate,
                    createBetaIndex(joiner, mappingIndex));
        }
        return applyFilters(existencePattern, predicate, shouldExist);
    }

    private  BetaIndex4 createBetaIndex(DefaultPentaJoiner joiner, int mappingIndex) {
        JoinerType joinerType = joiner.getJoinerType(mappingIndex);
        QuadFunction leftMapping = joiner.getLeftMapping(mappingIndex);
        Function rightMapping = joiner.getRightMapping(mappingIndex);
        if (joinerType == JoinerType.EQUAL) {
            return betaIndexedBy(Object.class, getConstraintType(joinerType), mappingIndex, rightMapping::apply,
                    leftMapping::apply, Object.class);
        } else { // Drools beta index on LT/LTE/GT/GTE requires Comparable.
            JoinerType reversedJoinerType = joinerType.flip();
            return betaIndexedBy(Comparable.class, getConstraintType(reversedJoinerType), mappingIndex,
                    e -> (Comparable) rightMapping.apply(e), leftMapping::apply, Comparable.class);
        }
    }

    private  QuadLeftHandSide applyFilters(PatternDSL.PatternDef existencePattern,
            PentaPredicate predicate, boolean shouldExist) {
        PatternDSL.PatternDef possiblyFilteredExistencePattern = predicate == null ? existencePattern
                : existencePattern.expr("Filter using " + predicate, patternVariableA.getPrimaryVariable(),
                        patternVariableB.getPrimaryVariable(), patternVariableC.getPrimaryVariable(),
                        patternVariableD.getPrimaryVariable(), (e, a, b, c, d) -> predicate.test(a, b, c, d, e));
        ViewItem existenceExpression = exists(possiblyFilteredExistencePattern);
        if (!shouldExist) {
            existenceExpression = not(possiblyFilteredExistencePattern);
        }
        return new QuadLeftHandSide<>(patternVariableA, patternVariableB, patternVariableC,
                patternVariableD.addDependentExpression(existenceExpression), variableFactory);
    }

    private  QuadLeftHandSide existsOrNot(Class dClass, PentaJoiner[] joiners,
            Predicate nullityFilter, boolean shouldExist) {
        int indexOfFirstFilter = -1;
        // Prepare the joiner and filter that will be used in the pattern
        DefaultPentaJoiner finalJoiner = null;
        PentaPredicate finalFilter = null;
        for (int i = 0; i < joiners.length; i++) {
            PentaJoiner joiner = joiners[i];
            boolean hasAFilter = indexOfFirstFilter >= 0;
            if (joiner instanceof FilteringPentaJoiner) {
                if (!hasAFilter) { // From now on, we only allow filtering joiners.
                    indexOfFirstFilter = i;
                }
                // Merge all filters into one to avoid paying the penalty for lack of indexing more than once.
                FilteringPentaJoiner castJoiner = (FilteringPentaJoiner) joiner;
                finalFilter = finalFilter == null ? castJoiner.getFilter() : finalFilter.and(castJoiner.getFilter());
            } else {
                if (hasAFilter) {
                    throw new IllegalStateException("Indexing joiner (" + joiner + ") must not follow a filtering joiner ("
                            + joiners[indexOfFirstFilter] + ").");
                } else { // Merge this Joiner with the existing Joiners.
                    DefaultPentaJoiner castJoiner = (DefaultPentaJoiner) joiner;
                    finalJoiner = finalJoiner == null ? castJoiner : finalJoiner.and(castJoiner);
                }
            }
        }
        return applyJoiners(dClass, nullityFilter, finalJoiner, finalFilter, shouldExist);
    }

    public  QuadLeftHandSide andExists(Class dClass, PentaJoiner[] joiners,
            Predicate nullityFilter) {
        return existsOrNot(dClass, joiners, nullityFilter, true);
    }

    public  QuadLeftHandSide andNotExists(Class dClass, PentaJoiner[] joiners,
            Predicate nullityFilter) {
        return existsOrNot(dClass, joiners, nullityFilter, false);
    }

    public  UniLeftHandSide andGroupBy(QuadConstraintCollector collector) {
        Variable accumulateOutput = variableFactory.createVariable("collected");
        ViewItem outerAccumulatePattern = buildAccumulate(createAccumulateFunction(collector, accumulateOutput));
        return new UniLeftHandSide<>(accumulateOutput, singletonList(outerAccumulatePattern), variableFactory);
    }

    public  BiLeftHandSide andGroupBy(QuadConstraintCollector collectorA,
            QuadConstraintCollector collectorB) {
        Variable accumulateOutputA = variableFactory.createVariable("collectedA");
        Variable accumulateOutputB = variableFactory.createVariable("collectedB");
        ViewItem outerAccumulatePattern = buildAccumulate(createAccumulateFunction(collectorA, accumulateOutputA),
                createAccumulateFunction(collectorB, accumulateOutputB));
        return new BiLeftHandSide<>(accumulateOutputA,
                new DirectPatternVariable<>(accumulateOutputB, outerAccumulatePattern), variableFactory);
    }

    public  TriLeftHandSide andGroupBy(
            QuadConstraintCollector collectorA,
            QuadConstraintCollector collectorB,
            QuadConstraintCollector collectorC) {
        Variable accumulateOutputA = variableFactory.createVariable("collectedA");
        Variable accumulateOutputB = variableFactory.createVariable("collectedB");
        Variable accumulateOutputC = variableFactory.createVariable("collectedC");
        ViewItem outerAccumulatePattern = buildAccumulate(createAccumulateFunction(collectorA, accumulateOutputA),
                createAccumulateFunction(collectorB, accumulateOutputB),
                createAccumulateFunction(collectorC, accumulateOutputC));
        return new TriLeftHandSide<>(accumulateOutputA, accumulateOutputB,
                new DirectPatternVariable<>(accumulateOutputC, outerAccumulatePattern),
                variableFactory);
    }

    public  QuadLeftHandSide andGroupBy(
            QuadConstraintCollector collectorA,
            QuadConstraintCollector collectorB,
            QuadConstraintCollector collectorC,
            QuadConstraintCollector collectorD) {
        Variable accumulateOutputA = variableFactory.createVariable("collectedA");
        Variable accumulateOutputB = variableFactory.createVariable("collectedB");
        Variable accumulateOutputC = variableFactory.createVariable("collectedC");
        Variable accumulateOutputD = variableFactory.createVariable("collectedD");
        ViewItem outerAccumulatePattern = buildAccumulate(createAccumulateFunction(collectorA, accumulateOutputA),
                createAccumulateFunction(collectorB, accumulateOutputB),
                createAccumulateFunction(collectorC, accumulateOutputC),
                createAccumulateFunction(collectorD, accumulateOutputD));
        return new QuadLeftHandSide<>(accumulateOutputA, accumulateOutputB, accumulateOutputC,
                new DirectPatternVariable<>(accumulateOutputD, outerAccumulatePattern), variableFactory);
    }

    /**
     * Creates a Drools accumulate function based on a given collector. The accumulate function will take one
     * {@link Variable} as input and return its result into another {@link Variable}.
     *
     * @param  type of the accumulate result
     * @param collector collector to use in the accumulate function
     * @param out variable in which to store accumulate result
     * @return Drools accumulate function
     */
    private  AccumulateFunction createAccumulateFunction(QuadConstraintCollector collector,
            Variable out) {
        Variable variableA = patternVariableA.getPrimaryVariable();
        Variable variableB = patternVariableB.getPrimaryVariable();
        Variable variableC = patternVariableC.getPrimaryVariable();
        Variable variableD = patternVariableD.getPrimaryVariable();
        return new AccumulateFunction(null,
                () -> new QuadAccumulator<>(variableA, variableB, variableC, variableD, collector))
                .with(variableA, variableB, variableC, variableD)
                .as(out);
    }

    public  UniLeftHandSide andGroupBy(QuadFunction keyMapping) {
        Variable groupKey = variableFactory.createVariable("groupKey");
        ViewItem groupByPattern = buildGroupBy(groupKey, keyMapping::apply);
        return new UniLeftHandSide<>(groupKey, singletonList(groupByPattern), variableFactory);
    }

    public  BiLeftHandSide andGroupBy(QuadFunction keyMappingA,
            QuadConstraintCollector collectorB) {
        Variable groupKey = variableFactory.createVariable("groupKey");
        Variable accumulateOutput = variableFactory.createVariable("output");
        ViewItem groupByPattern = buildGroupBy(groupKey, keyMappingA::apply,
                createAccumulateFunction(collectorB, accumulateOutput));
        return new BiLeftHandSide<>(groupKey, new DirectPatternVariable<>(accumulateOutput, groupByPattern),
                variableFactory);
    }

    public  TriLeftHandSide andGroupBy(QuadFunction keyMappingA,
            QuadConstraintCollector collectorB,
            QuadConstraintCollector collectorC) {
        Variable groupKey = variableFactory.createVariable("groupKey");
        Variable accumulateOutputB = variableFactory.createVariable("outputB");
        Variable accumulateOutputC = variableFactory.createVariable("outputC");
        ViewItem groupByPattern = buildGroupBy(groupKey, keyMappingA::apply,
                createAccumulateFunction(collectorB, accumulateOutputB),
                createAccumulateFunction(collectorC, accumulateOutputC));
        return new TriLeftHandSide<>(groupKey, accumulateOutputB,
                new DirectPatternVariable<>(accumulateOutputC, groupByPattern), variableFactory);
    }

    public  QuadLeftHandSide andGroupBy(
            QuadFunction keyMappingA, QuadConstraintCollector collectorB,
            QuadConstraintCollector collectorC,
            QuadConstraintCollector collectorD) {
        Variable groupKey = variableFactory.createVariable("groupKey");
        Variable accumulateOutputB = variableFactory.createVariable("outputB");
        Variable accumulateOutputC = variableFactory.createVariable("outputC");
        Variable accumulateOutputD = variableFactory.createVariable("outputD");
        ViewItem groupByPattern = buildGroupBy(groupKey, keyMappingA::apply,
                createAccumulateFunction(collectorB, accumulateOutputB),
                createAccumulateFunction(collectorC, accumulateOutputC),
                createAccumulateFunction(collectorD, accumulateOutputD));
        return new QuadLeftHandSide<>(groupKey, accumulateOutputB, accumulateOutputC,
                new DirectPatternVariable<>(accumulateOutputD, groupByPattern), variableFactory);
    }

    public  BiLeftHandSide andGroupBy(QuadFunction keyMappingA,
            QuadFunction keyMappingB) {
        Variable> groupKey = variableFactory.createVariable(BiTuple.class, "groupKey");
        ViewItem groupByPattern = buildGroupBy(groupKey, createCompositeBiGroupKey(keyMappingA, keyMappingB));
        Variable newA = variableFactory.createVariable("newA");
        Variable newB = variableFactory.createVariable("newB");
        IndirectPatternVariable> bPatternVar =
                decompose(groupKey, groupByPattern, newA, newB);
        return new BiLeftHandSide<>(newA, bPatternVar, variableFactory);
    }

    /**
     * Takes group key mappings and merges them in such a way that the result is a single composite key.
     * This is necessary because Drools groupBy can only take a single key - therefore multiple variables need to be
     * converted into a singular composite variable.
     *
     * @param keyMappingA mapping for the first variable
     * @param keyMappingB mapping for the second variable
     * @param  generic type of the first variable
     * @param  generic type of the second variable
     * @return never null, Drools function to convert the keys to a singular composite key
     */
    private  Function4> createCompositeBiGroupKey(
            QuadFunction keyMappingA, QuadFunction keyMappingB) {
        return (a, b, c, d) -> new BiTuple<>(keyMappingA.apply(a, b, c, d), keyMappingB.apply(a, b, c, d));
    }

    public  TriLeftHandSide andGroupBy(QuadFunction keyMappingA,
            QuadFunction keyMappingB, QuadConstraintCollector collectorC) {
        Variable> groupKey = variableFactory.createVariable(BiTuple.class, "groupKey");
        Variable accumulateOutput = variableFactory.createVariable("output");
        ViewItem groupByPattern = buildGroupBy(groupKey,
                createCompositeBiGroupKey(keyMappingA, keyMappingB),
                createAccumulateFunction(collectorC, accumulateOutput));
        Variable newA = variableFactory.createVariable("newA");
        Variable newB = variableFactory.createVariable("newB");
        DirectPatternVariable cPatternVar =
                decomposeWithAccumulate(groupKey, groupByPattern, newA, newB, accumulateOutput);
        return new TriLeftHandSide<>(newA, newB, cPatternVar, variableFactory);
    }

    public  QuadLeftHandSide andGroupBy(
            QuadFunction keyMappingA, QuadFunction keyMappingB,
            QuadConstraintCollector collectorC,
            QuadConstraintCollector collectorD) {
        Variable> groupKey = variableFactory.createVariable(BiTuple.class, "groupKey");
        Variable accumulateOutputC = variableFactory.createVariable("outputC");
        Variable accumulateOutputD = variableFactory.createVariable("outputD");
        ViewItem groupByPattern = buildGroupBy(groupKey,
                createCompositeBiGroupKey(keyMappingA, keyMappingB),
                createAccumulateFunction(collectorC, accumulateOutputC),
                createAccumulateFunction(collectorD, accumulateOutputD));
        Variable newA = variableFactory.createVariable("newA");
        Variable newB = variableFactory.createVariable("newB");
        DirectPatternVariable dPatternVar =
                decomposeWithAccumulate(groupKey, groupByPattern, newA, newB, accumulateOutputD);
        return new QuadLeftHandSide<>(newA, newB, accumulateOutputC, dPatternVar, variableFactory);
    }

    /**
     * Takes group key mappings and merges them in such a way that the result is a single composite key.
     * This is necessary because Drools groupBy can only take a single key - therefore multiple variables need to be
     * converted into a singular composite variable.
     *
     * @param keyMappingA mapping for the first variable
     * @param keyMappingB mapping for the second variable
     * @param keyMappingC mapping for the third variable
     * @param  generic type of the first variable
     * @param  generic type of the second variable
     * @param  generic type of the third variable
     * @return never null, Drools function to convert the keys to a singular composite key
     */
    private  Function4> createCompositeTriGroupKey(
            QuadFunction keyMappingA, QuadFunction keyMappingB,
            QuadFunction keyMappingC) {
        return (a, b, c, d) -> new TriTuple<>(keyMappingA.apply(a, b, c, d), keyMappingB.apply(a, b, c, d),
                keyMappingC.apply(a, b, c, d));
    }

    public  TriLeftHandSide andGroupBy(QuadFunction keyMappingA,
            QuadFunction keyMappingB, QuadFunction keyMappingC) {
        Variable> groupKey = variableFactory.createVariable(TriTuple.class, "groupKey");
        ViewItem groupByPattern = buildGroupBy(groupKey,
                createCompositeTriGroupKey(keyMappingA, keyMappingB, keyMappingC));
        Variable newA = variableFactory.createVariable("newA");
        Variable newB = variableFactory.createVariable("newB");
        Variable newC = variableFactory.createVariable("newC");
        IndirectPatternVariable> cPatternVar =
                decompose(groupKey, groupByPattern, newA, newB, newC);
        return new TriLeftHandSide<>(newA, newB, cPatternVar, variableFactory);
    }

    public  QuadLeftHandSide andGroupBy(
            QuadFunction keyMappingA, QuadFunction keyMappingB,
            QuadFunction keyMappingC, QuadConstraintCollector collectorD) {
        Variable> groupKey = variableFactory.createVariable(TriTuple.class, "groupKey");
        Variable accumulateOutputD = variableFactory.createVariable("outputD");
        ViewItem groupByPattern = buildGroupBy(groupKey,
                createCompositeTriGroupKey(keyMappingA, keyMappingB, keyMappingC),
                createAccumulateFunction(collectorD, accumulateOutputD));
        Variable newA = variableFactory.createVariable("newA");
        Variable newB = variableFactory.createVariable("newB");
        Variable newC = variableFactory.createVariable("newC");
        DirectPatternVariable dPatternVar =
                decomposeWithAccumulate(groupKey, groupByPattern, newA, newB, newC, accumulateOutputD);
        return new QuadLeftHandSide<>(newA, newB, newC, dPatternVar, variableFactory);
    }

    /**
     * Takes group key mappings and merges them in such a way that the result is a single composite key.
     * This is necessary because Drools groupBy can only take a single key - therefore multiple variables need to be
     * converted into a singular composite variable.
     *
     * @param keyMappingA mapping for the first variable
     * @param keyMappingB mapping for the second variable
     * @param keyMappingC mapping for the third variable
     * @param  generic type of the first variable
     * @param  generic type of the second variable
     * @param  generic type of the third variable
     * @return never null, Drools function to convert the keys to a singular composite key
     */
    private  Function4>
            createCompositeQuadGroupKey(QuadFunction keyMappingA, QuadFunction keyMappingB,
                    QuadFunction keyMappingC, QuadFunction keyMappingD) {
        return (a, b, c, d) -> new QuadTuple<>(keyMappingA.apply(a, b, c, d), keyMappingB.apply(a, b, c, d),
                keyMappingC.apply(a, b, c, d), keyMappingD.apply(a, b, c, d));
    }

    public  QuadLeftHandSide andGroupBy(
            QuadFunction keyMappingA, QuadFunction keyMappingB,
            QuadFunction keyMappingC, QuadFunction keyMappingD) {
        Variable> groupKey = variableFactory.createVariable(QuadTuple.class, "groupKey");
        ViewItem groupByPattern = buildGroupBy(groupKey,
                createCompositeQuadGroupKey(keyMappingA, keyMappingB, keyMappingC, keyMappingD));
        Variable newA = variableFactory.createVariable("newA");
        Variable newB = variableFactory.createVariable("newB");
        Variable newC = variableFactory.createVariable("newC");
        Variable newD = variableFactory.createVariable("newD");
        IndirectPatternVariable> dPatternVar =
                decompose(groupKey, groupByPattern, newA, newB, newC, newD);
        return new QuadLeftHandSide<>(newA, newB, newC, dPatternVar, variableFactory);
    }

    public  UniLeftHandSide andMap(QuadFunction mapping) {
        Variable newA = variableFactory.createVariable("mapped", patternVariableA.getPrimaryVariable(),
                patternVariableB.getPrimaryVariable(), patternVariableC.getPrimaryVariable(),
                patternVariableD.getPrimaryVariable(), mapping);
        List> allPrerequisites = mergeViewItems(patternVariableA, patternVariableB, patternVariableC,
                patternVariableD);
        DirectPatternVariable newPatternVariableA = new DirectPatternVariable<>(newA, allPrerequisites);
        return new UniLeftHandSide<>(newPatternVariableA, variableFactory);
    }

    public  QuadLeftHandSide andFlattenLast(Function> mapping) {
        Variable source = patternVariableD.getPrimaryVariable();
        Variable newD = variableFactory.createFlattenedVariable("flattened", source, mapping);
        List> allPrerequisites = mergeViewItems(patternVariableA, patternVariableB, patternVariableC,
                patternVariableD);
        PatternVariable newPatternVariableD = new DirectPatternVariable<>(newD, allPrerequisites);
        return new QuadLeftHandSide<>(patternVariableA.getPrimaryVariable(), patternVariableB.getPrimaryVariable(),
                patternVariableC.getPrimaryVariable(), newPatternVariableD, variableFactory);
    }

    public  RuleBuilder andTerminate() {
        return ruleContext.newRuleBuilder();
    }

    public  RuleBuilder andTerminate(ToIntQuadFunction matchWeigher) {
        return ruleContext.newRuleBuilder(matchWeigher);
    }

    public  RuleBuilder andTerminate(ToLongQuadFunction matchWeigher) {
        return ruleContext.newRuleBuilder(matchWeigher);
    }

    public  RuleBuilder andTerminate(QuadFunction matchWeigher) {
        return ruleContext.newRuleBuilder(matchWeigher);
    }

    private ViewItem buildAccumulate(AccumulateFunction... accFunctions) {
        ViewItem innerAccumulatePattern =
                joinViewItemsWithLogicalAnd(patternVariableA, patternVariableB, patternVariableC, patternVariableD);
        return buildAccumulate(innerAccumulatePattern, accFunctions);
    }

    private  ViewItem buildGroupBy(Variable groupKey,
            Function4 groupKeyExtractor, AccumulateFunction... accFunctions) {
        Variable inputA = patternVariableA.getPrimaryVariable();
        Variable inputB = patternVariableB.getPrimaryVariable();
        Variable inputC = patternVariableC.getPrimaryVariable();
        Variable inputD = patternVariableD.getPrimaryVariable();
        ViewItem innerGroupByPattern =
                joinViewItemsWithLogicalAnd(patternVariableA, patternVariableB, patternVariableC, patternVariableD);
        return DSL.groupBy(innerGroupByPattern, inputA, inputB, inputC, inputD, groupKey, groupKeyExtractor,
                accFunctions);
    }

}