com.github.aptd.simulation.elements.train.CDoor.java Source code

Java tutorial

Introduction

Here is the source code for com.github.aptd.simulation.elements.train.CDoor.java

Source

/*
 * @cond LICENSE
 * ######################################################################################
 * # LGPL License                                                                       #
 * #                                                                                    #
 * # This file is part of the Asimov - Agentbased Passenger Train Delay                 #
 * # This program is free software: you can redistribute it and/or modify               #
 * # it under the terms of the GNU Lesser General Public License as                     #
 * # published by the Free Software Foundation, either version 3 of the                 #
 * # License, or (at your option) any later version.                                    #
 * #                                                                                    #
 * # This program is distributed in the hope that it will be useful,                    #
 * # but WITHOUT ANY WARRANTY; without even the implied warranty of                     #
 * # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                      #
 * # GNU Lesser General Public License for more details.                                #
 * #                                                                                    #
 * # You should have received a copy of the GNU Lesser General Public License           #
 * # along with this program. If not, see http://www.gnu.org/licenses/                  #
 * ######################################################################################
 * @endcond
 */

package com.github.aptd.simulation.elements.train;

import com.fasterxml.jackson.core.JsonGenerator;
import com.github.aptd.simulation.core.messaging.EMessageType;
import com.github.aptd.simulation.core.messaging.IMessage;
import com.github.aptd.simulation.core.messaging.local.CMessage;
import com.github.aptd.simulation.core.time.ITime;
import com.github.aptd.simulation.elements.IElement;
import com.github.aptd.simulation.elements.IStatefulElement;
import com.github.aptd.simulation.elements.passenger.IPassenger;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.lightjason.agentspeak.action.IAction;
import org.lightjason.agentspeak.configuration.IAgentConfiguration;
import org.lightjason.agentspeak.language.ILiteral;

import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

/**
 * door class
 */
public final class CDoor extends IStatefulElement<IDoor<?>> implements IDoor<IDoor<?>> {

    /**
     * serial id
     */
    private static final long serialVersionUID = -5363342346164082638L;
    /**
     * literal functor
     */
    private static final String FUNCTOR = "door";

    private Queue<IPassenger<?>> m_exitqueue = new LinkedList<>();
    private Queue<IPassenger<?>> m_entryqueue = new LinkedList<>();

    /**
     * the train agent this door belongs to
     */
    private String m_train;

    private EDoorState m_state = EDoorState.CLOSED_RELEASED;
    /**
     * the total width of the door (when fully open) (meters)
     */
    private double m_width;
    /**
     * how far the door is actually open at the moment (min 0.0, max m_width), in between in OPENING and CLOSING states (meters)
     */
    private double m_openwidth;
    /**
     * for how long the door must have been unused before it can close (seconds)
     */
    private double m_minfreetimetoclose = 10;
    /**
     * for how long the door has not been used (seconds)
     */
    private double m_freetime;
    /**
     * how fast the door opens (meters per second)
     */
    private double m_openingspeed = 0.4;
    /**
     * how fast the door closes (meters per second)
     */
    private double m_closingspeed = 0.4;
    /**
     * agent ID of the station the door is currently at
     */
    private String m_stationid;
    /**
     * agent ID of the platform the door is currently at
     */
    private String m_platformid;

    /**
     * ctor
     *
     * @param p_configuration agent configuration
     * @param p_id            ...
     * @param p_time          time reference
     * @param p_train train id
     */
    protected CDoor(final IAgentConfiguration<IDoor<?>> p_configuration, final String p_id, final ITime p_time,
            final String p_train, final double p_width, final double p_minfreetimetoclose) {
        super(p_configuration, FUNCTOR, p_id, p_time);
        m_train = p_train;
        m_width = p_width;
        m_minfreetimetoclose = p_minfreetimetoclose;
        m_nextstatechange = determinenextstatechange();
    }

    @Override
    protected Stream<ILiteral> individualliteral(final Stream<IElement<?>> p_object) {
        return Stream.of();
    }

    @Override
    protected Instant determinenextstatechange() {
        switch (m_state) {
        case CLOSED_LOCKED:
        case CLOSED_RELEASED:
        case OPEN_BUSY:
        case OPEN_BUSY_SHALL_CLOSE:
        case OPEN_CLOSEABLE:
            return Instant.MAX;
        case OPENING:
        case OPENING_SHALL_CLOSE:
            return m_lastcontinuousupdate.plus(Math.round((m_width - m_openwidth) / m_openingspeed),
                    ChronoUnit.SECONDS);
        case OPEN_FREE:
        case OPEN_FREE_SHALL_CLOSE:
            return m_lastcontinuousupdate.plus(Math.round(m_minfreetimetoclose - m_freetime), ChronoUnit.SECONDS);
        case CLOSING:
        case CLOSING_LOCKED:
            return m_lastcontinuousupdate.plus(Math.round(m_openwidth / m_closingspeed), ChronoUnit.SECONDS);
        default:
            return Instant.MAX;
        }
    }

    @Override
    protected boolean updatestate() {
        //debugPrintState();
        final EDoorState l_oldstate = m_state;
        final List<IMessage> l_entryrequests = m_input.get(EMessageType.PASSENGER_TO_DOOR_ENQUEUE_ENTRANCE);
        final List<IMessage> l_exitrequests = m_input.get(EMessageType.PASSENGER_TO_DOOR_ENQUEUE_EXIT);
        final List<IMessage> l_finished = m_input.get(EMessageType.PASSENGER_TO_DOOR_FINISHED);

        // enqueuing passengers (if queues are empty, passenger will get immediate access with nextpassengerifpossible() at the end)
        if (m_state != EDoorState.CLOSED_LOCKED) {
            l_entryrequests.stream().sorted(Comparator.comparing(msg -> msg.sender().id()))
                    .forEachOrdered(msg -> m_entryqueue.add((IPassenger<?>) msg.sender()));
            l_exitrequests.stream().sorted(Comparator.comparing(msg -> msg.sender().id()))
                    .forEachOrdered(msg -> m_exitqueue.add((IPassenger<?>) msg.sender()));
        } else {
            l_entryrequests.stream().sorted(Comparator.comparing(msg -> msg.sender().id())).forEachOrdered(
                    msg -> output(new CMessage(this, msg.sender().id(), EMessageType.DOOR_TO_PASSENGER_REJECT)));
            l_exitrequests.stream().sorted(Comparator.comparing(msg -> msg.sender().id())).forEachOrdered(
                    msg -> output(new CMessage(this, msg.sender().id(), EMessageType.DOOR_TO_PASSENGER_REJECT)));
        }

        // locking or unlocking
        handletrainmessages();

        // switches state from busy to free, but if queues are not empty, will become busy again with nextpassengerifpossible() at the end
        if (l_finished.size() > 1)
            throw new RuntimeException(m_id + " received multiple finished messages");
        if (!l_finished.isEmpty())
            passengerfinished(l_finished.get(0));

        // determine from which queue a passenger could be taken next: exit has priority over entry
        final Queue<IPassenger<?>> l_queue = m_exitqueue.isEmpty() ? m_entryqueue.isEmpty() ? null : m_entryqueue
                : m_exitqueue;

        // finish opening/closing process if applicable; switch to closable if door is open and has been unused for m_minfreetimetoclose seconds
        timedstatechangeifnecessary(l_queue);

        // if the door is in a permissible state, let the next passenger pass (or open the door first, if closed), if any
        nextpassengerifpossible(l_queue);

        //debugPrintState();
        return l_oldstate != m_state;
    }

    private void debugPrintState() {
        System.out.println("[DEBUG] " + m_id + " state: " + m_state);
    }

    private void handletrainmessages() {
        final List<IMessage> l_lockrequests = m_input.get(EMessageType.TRAIN_TO_DOOR_LOCK);
        final List<IMessage> l_unlockrequests = m_input.get(EMessageType.TRAIN_TO_DOOR_UNLOCK);
        final List<IMessage> l_departing = m_input.get(EMessageType.TRAIN_TO_DOOR_DEPARTING);

        if (!l_lockrequests.isEmpty() && !l_unlockrequests.isEmpty())
            throw new RuntimeException("door " + m_id + " received lock and unlock request at the same time");

        if ((!l_lockrequests.isEmpty() || !l_unlockrequests.isEmpty()) && !l_departing.isEmpty())
            throw new RuntimeException(
                    "door " + m_id + " received lock/unlock request and departing message at the same time");

        lockifrequested(l_lockrequests);

        unlockifrequested(l_unlockrequests);

        if (l_departing.size() > 1)
            throw new RuntimeException(m_id + " received multiple departing messages");
        if (!l_departing.isEmpty()) {
            if (m_state != EDoorState.CLOSED_LOCKED)
                throw new RuntimeException(m_id + " received departing message although in state " + m_state);
            m_entryqueue.forEach(p -> System.out.println(p.id() + " in entry queue when departing"));
            m_exitqueue.forEach(p -> System.out.println(p.id() + " in exit queue when departing"));
            m_entryqueue.clear();
            m_stationid = null;
            m_platformid = null;
        }
    }

    private void unlockifrequested(final List<IMessage> p_unlockrequests) {
        if (p_unlockrequests.size() > 1)
            throw new RuntimeException(m_id + " received multiple unlock requests");
        if (!p_unlockrequests.isEmpty()) {
            m_stationid = (String) p_unlockrequests.get(0).content()[0];
            m_platformid = (String) p_unlockrequests.get(0).content()[1];
            switch (m_state) {
            case CLOSED_LOCKED:
                m_state = m_exitqueue.isEmpty() && m_entryqueue.isEmpty() ? EDoorState.CLOSED_RELEASED
                        : EDoorState.OPENING;
                break;
            case OPENING_SHALL_CLOSE:
                m_state = EDoorState.OPENING;
                break;
            case OPEN_FREE_SHALL_CLOSE:
                m_state = EDoorState.OPEN_FREE;
                break;
            case OPEN_BUSY_SHALL_CLOSE:
                m_state = EDoorState.OPEN_BUSY;
                break;
            case CLOSING_LOCKED:
                m_state = EDoorState.CLOSING;
                break;
            case CLOSED_RELEASED:
            case OPENING:
            case OPEN_FREE:
            case OPEN_BUSY:
            case OPEN_CLOSEABLE:
            case CLOSING:
                System.out.println("WARNING: door " + m_id + " received unlock request although already in state "
                        + m_state + " at " + m_time.current());
                break;
            default:
                // making checkstyle happy
            }
        }
    }

    private void lockifrequested(final List<IMessage> p_lockrequests) {
        if (p_lockrequests.size() > 1)
            throw new RuntimeException(m_id + " received multiple lock requests");
        if (!p_lockrequests.isEmpty())
            switch (m_state) {
            case CLOSED_RELEASED:
                m_state = EDoorState.CLOSED_LOCKED;
                output(new CMessage(this, m_train, EMessageType.DOOR_TO_TRAIN_CLOSED_LOCKED, ""));
                break;
            case OPENING:
                m_state = EDoorState.OPENING_SHALL_CLOSE;
                break;
            case OPEN_FREE:
                m_state = EDoorState.OPEN_FREE_SHALL_CLOSE;
                break;
            case OPEN_BUSY:
                m_state = EDoorState.OPEN_BUSY_SHALL_CLOSE;
                break;
            case OPEN_CLOSEABLE:
                m_state = EDoorState.CLOSING_LOCKED;
                break;
            case CLOSING:
                m_state = EDoorState.CLOSING_LOCKED;
                break;
            case CLOSED_LOCKED:
            case OPENING_SHALL_CLOSE:
            case OPEN_FREE_SHALL_CLOSE:
            case OPEN_BUSY_SHALL_CLOSE:
            case CLOSING_LOCKED:
                System.out.println(
                        "WARNING: door " + m_id + " received lock request although already in state " + m_state);
                break;
            default:
                // making checkstyle happy
            }
    }

    private void timedstatechangeifnecessary(final Queue<IPassenger<?>> p_queue) {
        final boolean l_timedchange = !m_nextstatechange.isAfter(m_time.current());
        if (l_timedchange) {
            // System.out.println( m_id + " - timer transition at " + m_time.current().toString() + " from state " + m_state );
            switch (m_state) {
            case OPENING:
            case OPENING_SHALL_CLOSE:
                m_openwidth = m_width;
                m_state = m_state == EDoorState.OPENING ? EDoorState.OPEN_FREE : EDoorState.OPEN_FREE_SHALL_CLOSE;
                m_freetime = 0.0;
                break;
            case OPEN_FREE_SHALL_CLOSE:
                if (p_queue == null)
                    m_state = EDoorState.CLOSING_LOCKED;
                // in case of a passenger enqueueing exactly when the door would start to close, the passenger wins and it does not close
                break;
            case OPEN_FREE:
                m_state = EDoorState.OPEN_CLOSEABLE;
                break;
            case CLOSING:
                m_state = EDoorState.CLOSED_RELEASED;
                m_openwidth = 0.0;
                break;
            case CLOSING_LOCKED:
                m_state = EDoorState.CLOSED_LOCKED;
                m_openwidth = 0.0;
                output(new CMessage(this, m_train, EMessageType.DOOR_TO_TRAIN_CLOSED_LOCKED, ""));
                break;
            default:
                // making checkstyle happy
            }
        }
    }

    private void passengerfinished(final IMessage p_message) {
        switch (m_state) {
        case OPEN_BUSY:
            m_state = EDoorState.OPEN_FREE;
            break;
        case OPEN_BUSY_SHALL_CLOSE:
            m_state = EDoorState.OPEN_FREE_SHALL_CLOSE;
            break;
        default:
            throw new RuntimeException(m_id + " received finished message although not busy in state " + m_state);
        }
        final String l_exiting = m_exitqueue.isEmpty() ? null : m_exitqueue.peek().id();
        final String l_entering = m_entryqueue.isEmpty() ? null : m_entryqueue.peek().id();
        if (p_message.sender().id().equals(l_exiting))
            m_exitqueue.poll();
        else if (p_message.sender().id().equals(l_entering))
            m_entryqueue.poll();
        else
            throw new RuntimeException(m_id + " received finished message from " + p_message.sender().id()
                    + " who is not first in either queue");
        m_freetime = 0.0;
    }

    private void nextpassengerifpossible(final Queue<IPassenger<?>> p_queue) {
        if (p_queue != null) {
            switch (m_state) {
            case OPEN_CLOSEABLE:
            case OPEN_FREE:
                m_state = EDoorState.OPEN_BUSY;
                output(new CMessage(this, p_queue.peek().id(), EMessageType.DOOR_TO_PASSENGER_YOURTURN, m_stationid,
                        m_platformid));
                break;
            case OPEN_FREE_SHALL_CLOSE:
                m_state = EDoorState.OPEN_BUSY_SHALL_CLOSE;
                output(new CMessage(this, p_queue.peek().id(), EMessageType.DOOR_TO_PASSENGER_YOURTURN, m_stationid,
                        m_platformid));
                break;
            case CLOSING:
            case CLOSED_RELEASED:
                m_state = EDoorState.OPENING;
                break;
            case CLOSING_LOCKED:
                m_state = EDoorState.OPENING_SHALL_CLOSE;
                break;
            default:
                // making checkstyle happy
            }
        }
    }

    @Override
    protected boolean updatecontinuous(final Duration p_elapsed) {
        switch (m_state) {
        case OPENING:
        case OPENING_SHALL_CLOSE:
            m_openwidth += p_elapsed.get(ChronoUnit.SECONDS) * m_openingspeed;
            return true;
        case OPEN_FREE:
        case OPEN_CLOSEABLE:
        case OPEN_FREE_SHALL_CLOSE:
            m_freetime += p_elapsed.get(ChronoUnit.SECONDS);
            return true;
        case CLOSING:
        case CLOSING_LOCKED:
            m_openwidth -= p_elapsed.get(ChronoUnit.SECONDS) * m_closingspeed;
            return true;
        default:
            // making checkstyle happy
        }
        return false;
    }

    @Override
    protected void writeState(final JsonGenerator p_generator) throws IOException {
        p_generator.writeStringField("state", m_state.name());
        p_generator.writeNumberField("openwidth", m_openwidth);
        p_generator.writeStringField("station", m_stationid);
        p_generator.writeStringField("platform", m_platformid);
        p_generator.writeNumberField("freetime", m_freetime);
        p_generator.writeArrayFieldStart("entryqueue");
        for (final IPassenger<?> l_passenger : m_entryqueue)
            p_generator.writeString(l_passenger.id());
        p_generator.writeEndArray();
        p_generator.writeArrayFieldStart("exitqueue");
        for (final IPassenger<?> l_passenger : m_exitqueue)
            p_generator.writeString(l_passenger.id());
        p_generator.writeEndArray();
    }

    // ---------------------------------------------------------------------------------------------------------------------------------------------------------

    /**
     * generator
     */
    public static final class CGenerator extends IBaseGenerator<IDoor<?>> {
        /**
         * object counter
         */
        private static final AtomicLong COUNTER = new AtomicLong();

        /**
         * generator ctor
         * @param p_stream stream
         * @param p_actions action
         * @param p_time time reference
         * @throws Exception on any error
         */
        public CGenerator(final InputStream p_stream, final Set<IAction> p_actions, final ITime p_time)
                throws Exception {
            super(p_stream, p_actions, CDoor.class, p_time);
        }

        @Override
        protected final Pair<IDoor<?>, Stream<String>> generate(final Object... p_data) {
            return new ImmutablePair<>(new CDoor(m_configuration, (String) p_data[0], m_time, (String) p_data[1],
                    (double) p_data[2], (double) p_data[3]), Stream.of(FUNCTOR));
        }

        @Override
        public final IGenerator<IDoor<?>> resetcount() {
            COUNTER.set(0);
            return this;
        }
    }
}