EventMultipleHandler.java

/* Copyright 2020 Airbus Defence and Space
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS 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.orekit.propagation.events.handlers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.hipparchus.ode.events.Action;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.AbstractDetector;
import org.orekit.propagation.events.EventDetector;
import org.orekit.time.AbsoluteDate;

/**
 * Facade handlers that allows to use several handlers for one detector.
 * Otherwise, the use of several detectors, each associated with one handler, that detect
 * the same event can lead to non-deterministic behaviour.
 * This handler manages several handlers. The action returned is based on a priority rule
 * (see {@link #eventOccurred}) :
 * {@link Action#STOP stop} > {@link Action#RESET_STATE resetState} > {@link Action#RESET_DERIVATIVES resetDerivatives} > {@link Action#RESET_EVENTS resetRevents} > {@link Action#CONTINUE continue}
 *
 * @author Lara Hué
 *
 * @param <D> object type of the detector which is linked to the handler
 * @since 10.3
 */
public class EventMultipleHandler<D extends EventDetector> implements EventHandler<D> {

    /** Default list of handlers for event overrides. */
    private List<EventHandler<D>> handlers;

    /** List of handlers whose Action returned is RESET_STATE. */
    private List<EventHandler<D>> resetStateHandlers;

    /** Constructor with list initialisation. */
    public EventMultipleHandler() {
        handlers = new ArrayList<>();
        resetStateHandlers = new ArrayList<>();
    }

    /** Initialize event handler at the start of a propagation.
     * <p>
     * This method is called once at the start of the propagation. It
     * may be used by the event handler to initialize some internal data
     * if needed.
     * </p>
     * <p>
     * The default implementation does nothing
     * </p>
     * <p>
     * All handlers' init methods are successively called, the order method is
     * the order in which handlers are added
     * </p>
     * @param initialState initial state
     * @param target target date for the propagation
     *
     */
    @Override
    public void init(final SpacecraftState initialState, final AbsoluteDate target) {
        handlers.forEach(handler -> handler.init(initialState, target));
    }

    /**
     * eventOccurred method mirrors the same interface method as in {@link EventDetector}
     * and its subclasses, but with an additional parameter that allows the calling
     * method to pass in an object from the detector which would have potential
     * additional data to allow the implementing class to determine the correct
     * return state.
     *
     * The MultipleEventHandler class implies a different behaviour on event detections
     * than with other handlers :
     * Without the MultipleEventHandler, there is a total order on event occurrences.
     * Handlers H1, H2, ... that are associated with different instances of
     * {@link AbstractDetector} are successively called and Action from H1 can prevent
     * H2 from happening if H1 returned {@link Action#RESET_STATE resetState}.
     * With the MultipleEventHandler class, when event E occurs, all methods eventOccurred
     * of Handlers H1, H2... from MultiEventHandler attributes are called, then Action is decided.
     *
     * @param s SpaceCraft state to be used in the evaluation
     * @param detector object with appropriate type that can be used in determining correct return state
     * @param increasing with the event occurred in an "increasing" or "decreasing" slope direction
     * @return the Action that the calling detector should pass back to the evaluation system
     *
     */
    @Override
    public Action eventOccurred(final SpacecraftState s, final D detector, final boolean increasing) {
        final Map<EventHandler<D>, Action> actions =
                handlers.stream().
                         collect(Collectors.toMap(Function.identity(),
                             handler -> handler.eventOccurred(s, detector, increasing)));

        if (actions.containsValue(Action.STOP)) {
            return Action.STOP;
        }

        if (actions.containsValue(Action.RESET_STATE)) {
            resetStateHandlers = actions.entrySet()
                                        .stream()
                                        .filter(entry -> Action.RESET_STATE.equals(entry.getValue()))
                                        .map(Map.Entry::getKey)
                                        .collect(Collectors.toList());
            return Action.RESET_STATE;
        }

        if (actions.containsValue(Action.RESET_DERIVATIVES)) {
            return Action.RESET_DERIVATIVES;
        }

        if (actions.containsValue(Action.RESET_EVENTS)) {
            return Action.RESET_EVENTS;
        }

        return Action.CONTINUE;
    }

    /** Reset the state prior to continue propagation.
     * <p>
     * All handlers that return {@link Action#RESET_STATE resetState} when calling {@link #eventOccurred}
     * are saved in resetStateHandlers. Their methods resetState are successively called.
     * The order for calling resetState methods is the order in which handlers are added.
     * </p>
     * @param detector object with appropriate type that can be used in determining correct return state
     * @param oldState old state
     * @return new state
     */
    @Override
    public SpacecraftState resetState(final D detector, final SpacecraftState oldState) {
        SpacecraftState newState = oldState;
        for (EventHandler<D> handler : resetStateHandlers) {
            newState = handler.resetState(detector, newState);
        }
        return newState;
    }

    /** Add one handler to the managed handlers list.
     * @param handler handler associated with D detector
     * @return this object
     */
    public EventMultipleHandler<D> addHandler(final EventHandler<D> handler) {
        handlers.add(handler);
        return this;
    }

    /** Add several handlers to the managed handlers list.
     * @param newHandlers handlers associated with D detector
     * @return this object
     */
    @SafeVarargs // this method is safe
    public final EventMultipleHandler<D> addHandlers(final EventHandler<D>... newHandlers) {
        Arrays.stream(newHandlers).forEach(this::addHandler);
        return this;
    }

    /** Change handlers list with user input.
     * @param newHandlers new handlers list associated with D detector
     *
     */
    public void setHandlers(final List<EventHandler<D>> newHandlers) {
        handlers = newHandlers;
    }

    /** Retrieve managed handlers list.
     * @return list of handlers for event overrides
     */
    public List<EventHandler<D>> getHandlers() {
        return this.handlers;
    }
}