AllocationsIRef.java

/*
 * Copyright (c) 2021 Mārtiņš Avots (Martins Avots) and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the MIT License,
 * which is available at https://spdx.org/licenses/MIT.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR MIT
 */
package net.splitcells.gel.data.allocations;

import net.splitcells.dem.data.set.Set;
import net.splitcells.dem.environment.config.StaticFlags;
import net.splitcells.dem.lang.Xml;
import net.splitcells.gel.data.allocation.Allocations;
import net.splitcells.gel.data.allocation.AllocationsI;
import net.splitcells.gel.data.database.Database;
import net.splitcells.gel.data.table.Line;
import net.splitcells.gel.data.table.attribute.Attribute;
import net.splitcells.gel.data.table.column.ColumnView;

import static net.splitcells.dem.data.set.Sets.setOfUniques;
import static net.splitcells.dem.data.set.list.Lists.list;
import static net.splitcells.dem.environment.config.StaticFlags.ENFORCING_UNIT_CONSISTENCY;
import static net.splitcells.dem.environment.config.StaticFlags.TRACING;
import static net.splitcells.dem.lang.Xml.elementWithChildren;
import static net.splitcells.dem.lang.Xml.event;
import static net.splitcells.dem.resource.communication.log.Domsole.domsole;
import static net.splitcells.dem.resource.communication.interaction.LogLevel.DEBUG;
import static net.splitcells.gel.common.Language.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

/**
 * {@link #demandsUsed()} ()} and {@link #demandsFree()} contain all {@link Line} of {@link #demands()}.
 * {@link Line} with the same conceptional identity und different {@link Database} contexts have the same {@link Line#index()}.
 * The same applies to {@link #supplies()}.
 * <p/>
 * Line removal from {@link #demands_free} and {@link #supplies_free} has no subscriptions,
 * because {@link Database} lines can be remove from the {@link Allocations} completely
 * or they can be moved to the respectively used tables.
 * <p/>
 * TODO Fix {@link #demandOfAllocation(Line)} by using {@link #demands_used}.
 * <p/>
 * TODO Fix {@link #supplyOfAllocation} by using {@link #supplies_used}.
 * <p/>
 * TODO {@link #add(Line)}: The input argument has to be split into a supply and a demand part and a fitting supply and
 * demand pair already would be searched and used on {@code allocate(supply, demand)}.
 * If a fitting supply and demand pair does not exit the input is invalid.
 * <p/>
 * TODO {@link #path}: Support for multiple path. In this case paths with demand and supplies as base.
 * <p/>
 * TODO {@link #path}: Define this as an convention regarding the meaning of demands and supplies.
 */
public class AllocationsIRef extends AllocationsI {
    /**
     * TODO FIX Make constructor private.
     *
     * @param name
     * @param demand
     * @param supplies
     */
    public AllocationsIRef(String name, Database demand, Database supplies) {
        super(name, demand, supplies);
    }

    @Override
    public Line allocate(Line demand, Line supply) {
        if (TRACING) {
            assertThat(demand).withFailMessage("Cannot allocate without demand.").isNotNull();
            assertThat(supply).withFailMessage("Cannot allocate without supply.").isNotNull();
            domsole().append
                    (event(ALLOCATE.value() + PATH_ACCESS_SYMBOL.value() + Allocations.class.getSimpleName()
                                    , path().toString()
                                    , Xml.elementWithChildren(DEMAND.value(), demand.toDom())
                                    , Xml.elementWithChildren(SUPPLY.value(), supply.toDom()))
                            , this
                            , DEBUG
                    );
        }
        if (ENFORCING_UNIT_CONSISTENCY) {
            assertThat(list(demand.context())).containsAnyOf(demands_free, demands);
            assertThat(list(supply.context())).containsAnyOf(supplies, supplies_free);
            assertThat(demands.rawLinesView().get(demand.index())).isNotNull();
            assertThat(supplies.rawLinesView().get(supply.index())).isNotNull();
            assertThat(supply.context()).isIn(setOfUniques(supplies, supplies_free, supplies_used));
            assertThat(demand.context()).isIn(setOfUniques(demands, demands_free, demands_used));
            if (usedDemandIndexes_to_allocationIndexes.containsKey(demand.index())
                    && usedSupplyIndexes_to_allocationIndexes.containsKey(supply.index())) {
                final var allocationIndexes_of_demand
                        = usedDemandIndexes_to_allocationIndexes.get(demand.index());
                final var allocationIndexes_of_supply
                        = usedSupplyIndexes_to_allocationIndexes.get(supply.index());
                // Checks if there is already an allocation.
                for (final var allocationIndex_of_demand : allocationIndexes_of_demand) {
                    if (allocationIndexes_of_supply.contains(allocationIndex_of_demand)) {
                        /** TODO IDEA Support multiple and partial allocations between {@link Demand} and {@link Supply}.
                         */
                        fail("The demand " + demand.index() + "and the supply" + supply.index() + "are already allocated to each other. Multiple assignments to same variables are currently not supported.");
                    }
                }
            }
            {
                // Multiple allocations per supply or demand are allowed.
                boolean valid = false;
                if (demand.index() < demands_used.rawLinesView().size()) {
                    valid |= demands_used.rawLinesView().get(demand.index()) != null;
                    if (demand.index() < demands_free.rawLinesView().size()) {
                        valid |= demands_free.rawLinesView().get(demand.index()) != null;
                    }
                } else if (demand.index() < demands_free.rawLinesView().size()) {
                    valid |= demands_free.rawLinesView().get(demand.index()) != null;
                    if (demand.index() < demands_used.rawLinesView().size()) {
                        valid |= demands_used.rawLinesView().get(demand.index()) != null;
                    }
                } else {
                    throw new IllegalArgumentException();
                }
                assert valid;
                /**
                 * TODO The same for supplies;
                 * <p/>
                 * TODO Test if the right tables contain the suppy and if other tables do not
                 * contain these {@link Table}
                 */
            }
        }
        return super.allocate(demand, supply);
    }

    @Override
    public void remove(Line allocation) {
        final var demand = demandOfAllocation(allocation);
        final var supply = supplyOfAllocation(allocation);
        if (TRACING) {
            domsole().append
                    (Xml.event(REMOVE.value()
                                            + PATH_ACCESS_SYMBOL.value()
                                            + Allocations.class.getSimpleName()
                                    , path().toString()
                                    , Xml.elementWithChildren(ALLOCATION.value()
                                            , allocation.toDom())
                                    , Xml.elementWithChildren(DEMAND.value()
                                            , demand.toDom())
                                    , Xml.elementWithChildren(SUPPLY.value()
                                            , supply.toDom()))
                            , this
                            , DEBUG
                    );
        }
        if (ENFORCING_UNIT_CONSISTENCY) {
            assertThat(list(demand.context())).containsAnyOf(demands, demands_used);
            assertThat(list(supply.context())).containsAnyOf(supplies, supplies_used);
            assertThat(allocation.context()).isEqualTo(allocations);
            assertThat(usedDemandIndexes_to_allocationIndexes.get(demand.index())).contains(allocation.index());
            assertThat(usedSupplyIndexes_to_allocationIndexes.get(supply.index())).contains(allocation.index());
            assertThat(allocationsIndex_to_usedDemandIndex.get(allocation.index())).isEqualTo(demand.index());
            assertThat(allocationsIndex_to_usedSupplyIndex.get(allocation.index())).isEqualTo(supply.index());
        }
        super.remove(allocation);
    }

    @Override
    public <T> ColumnView<T> columnView(Attribute<T> attribute) {
        if (ENFORCING_UNIT_CONSISTENCY) {
            assert demands.headerView().contains(attribute) || supplies.headerView().contains(attribute);
        }
        return super.columnView(attribute);
    }

    @Override
    public Set<Line> allocationsOfSupply(Line supply) {
        if (StaticFlags.ENFORCING_UNIT_CONSISTENCY) {
            assertThat(usedSupplyIndexes_to_allocationIndexes)
                    .describedAs("No allocations for the given supply are present.")
                    .containsKey(supply.index());
        }
        return super.allocationsOfSupply(supply);
    }

    @Override
    public Set<Line> allocationsOfDemand(Line demand) {
        if (StaticFlags.ENFORCING_UNIT_CONSISTENCY) {
            try {
                assertThat(usedDemandIndexes_to_allocationIndexes).containsKey(demand.index());
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
        return super.allocationsOfDemand(demand);
    }
}