1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.propagation.sampling;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.Iterator;
22  import java.util.List;
23  
24  import org.orekit.propagation.SpacecraftState;
25  import org.orekit.time.AbsoluteDate;
26  
27  /** This class gathers several {@link OrekitStepHandler} instances into one.
28   *
29   * @author Luc Maisonobe
30   */
31  public class StepHandlerMultiplexer implements OrekitStepHandler {
32  
33      /** Underlying step handlers. */
34      private final List<OrekitStepHandler> handlers;
35  
36      /** Target time. */
37      private AbsoluteDate target;
38  
39      /** Last known state. */
40      private SpacecraftState last;
41  
42      /** Simple constructor.
43       */
44      public StepHandlerMultiplexer() {
45          handlers = new ArrayList<>();
46      }
47  
48      /** Add a handler for variable size step.
49       * <p>
50       * If propagation is ongoing (i.e. global {@link #init(SpacecraftState, AbsoluteDate)
51       * init} already called and global {@link #finish(SpacecraftState) finish} not called
52       * yet), then the local {@link OrekitStepHandler#init(SpacecraftState, AbsoluteDate)
53       * OrekitStepHandler.init} method of the added handler will be called with the last
54       * known state, so the handler starts properly.
55       * </p>
56       * @param handler step handler to add
57       */
58      public void add(final OrekitStepHandler handler) {
59          handlers.add(handler);
60          if (last != null) {
61              // propagation is ongoing, we need to call init now for this handler
62              handler.init(last, target);
63          }
64      }
65  
66      /** Add a handler for fixed size step.
67       * <p>
68       * If propagation is ongoing (i.e. global {@link #init(SpacecraftState, AbsoluteDate)
69       * init} already called and global {@link #finish(SpacecraftState) finish} not called
70       * yet), then the local {@link OrekitFixedStepHandler#init(SpacecraftState, AbsoluteDate, double)
71       * OrekitFixedStepHandler.init} method of the added handler will be called with the
72       * last known state, so the handler starts properly.
73       * </p>
74       * @param h fixed stepsize (s)
75       * @param handler handler called at the end of each finalized step
76       * @since 11.0
77       */
78      public void add(final double h, final OrekitFixedStepHandler handler) {
79          final OrekitStepHandler normalized = new OrekitStepNormalizer(h, handler);
80          handlers.add(normalized);
81          if (last != null) {
82              // propagation is ongoing, we need to call init now for this handler
83              normalized.init(last, target);
84          }
85      }
86  
87      /** Get an unmodifiable view of all handlers.
88       * <p>
89       * Note that if {@link OrekitFixedStepHandler fixed step handlers} have
90       * been {@link #add(double, OrekitFixedStepHandler)}, then they will
91       * show up wrapped within {@link OrekitStepNormalizer step normalizers}.
92       * </p>
93       * @return an unmodifiable view of all handlers
94       * @since 11.0
95       */
96      public List<OrekitStepHandler> getHandlers() {
97          return Collections.unmodifiableList(handlers);
98      }
99  
100     /** Remove a handler.
101      * <p>
102      * If propagation is ongoing (i.e. global {@link #init(SpacecraftState, AbsoluteDate)
103      * init} already called and global {@link #finish(SpacecraftState) finish} not called
104      * yet), then the local {@link OrekitStepHandler#finish(SpacecraftState)
105      * OrekitStepHandler.finish} method of the removed handler will be called with the last
106      * known state, so the handler stops properly.
107      * </p>
108      * @param handler step handler to remove
109      * @since 11.0
110      */
111     public void remove(final OrekitStepHandler handler) {
112         final Iterator<OrekitStepHandler> iterator = handlers.iterator();
113         while (iterator.hasNext()) {
114             if (iterator.next() == handler) {
115                 if (last != null) {
116                     // propagation is ongoing, we need to call finish now for this handler
117                     handler.finish(last);
118                 }
119                 iterator.remove();
120                 return;
121             }
122         }
123     }
124 
125     /** Remove a handler.
126      * <p>
127      * If propagation is ongoing (i.e. global {@link #init(SpacecraftState, AbsoluteDate)
128      * init} already called and global {@link #finish(SpacecraftState) finish} not called
129      * yet), then the local {@link OrekitFixedStepHandler#finish(SpacecraftState)
130      * OrekitFixedStepHandler.finish} method of the removed handler will be called with the
131      * last known state, so the handler stops properly.
132      * </p>
133      * @param handler step handler to remove
134      * @since 11.0
135      */
136     public void remove(final OrekitFixedStepHandler handler) {
137         final Iterator<OrekitStepHandler> iterator = handlers.iterator();
138         while (iterator.hasNext()) {
139             final OrekitStepHandler current = iterator.next();
140             if (current instanceof OrekitStepNormalizer &&
141                 ((OrekitStepNormalizer) current).getFixedStepHandler() == handler) {
142                 if (last != null) {
143                     // propagation is ongoing, we need to call finish now for this handler
144                     current.finish(last);
145                 }
146                 iterator.remove();
147                 return;
148             }
149         }
150     }
151 
152     /** Remove all handlers managed by this multiplexer.
153      * <p>
154      * If propagation is ongoing (i.e. global {@link #init(SpacecraftState, AbsoluteDate)
155      * init} already called and global {@link #finish(SpacecraftState) finish} not called
156      * yet), then the local {@link OrekitStepHandler#finish(SpacecraftState)
157      * OrekitStepHandler.finish} and {@link OrekitFixedStepHandler#finish(SpacecraftState)
158      * OrekitFixedStepHandler.finish} methods of the removed handlers will be called with the
159      * last known state, so the handlers stop properly.
160      * </p>
161      * @since 11.0
162      */
163     public void clear() {
164         if (last != null) {
165             // propagation is ongoing, we need to call finish now for all handlers
166             handlers.forEach(h -> h.finish(last));
167         }
168         handlers.clear();
169     }
170 
171     /** {@inheritDoc} */
172     public void init(final SpacecraftState s0, final AbsoluteDate t) {
173         this.target = t;
174         this.last   = s0;
175         for (final OrekitStepHandler handler : handlers) {
176             handler.init(s0, t);
177         }
178     }
179 
180     /** {@inheritDoc} */
181     public void handleStep(final OrekitStepInterpolator interpolator) {
182         this.last = interpolator.getCurrentState();
183         for (final OrekitStepHandler handler : handlers) {
184             handler.handleStep(interpolator);
185         }
186     }
187 
188     /** {@inheritDoc} */
189     public void finish(final SpacecraftState finalState) {
190         this.target = null;
191         this.last   = null;
192         for (final OrekitStepHandler handler : handlers) {
193             handler.finish(finalState);
194         }
195     }
196 
197 }