1   /* Copyright 2002-2026 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.files.ccsds.ndm.tdm;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.function.Function;
22  
23  import org.orekit.data.DataContext;
24  import org.orekit.files.ccsds.definitions.CcsdsFrameMapper;
25  import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
26  import org.orekit.files.ccsds.section.HeaderProcessingState;
27  import org.orekit.files.ccsds.section.KvnStructureProcessingState;
28  import org.orekit.files.ccsds.section.MetadataKey;
29  import org.orekit.files.ccsds.section.Segment;
30  import org.orekit.files.ccsds.section.XmlStructureProcessingState;
31  import org.orekit.files.ccsds.utils.ContextBinding;
32  import org.orekit.files.ccsds.utils.FileFormat;
33  import org.orekit.files.ccsds.utils.lexical.ParseToken;
34  import org.orekit.files.ccsds.utils.parsing.AbstractConstituentParser;
35  import org.orekit.files.ccsds.utils.parsing.ProcessingState;
36  import org.orekit.frames.Frame;
37  import org.orekit.utils.IERSConventions;
38  
39  
40  /**
41   * Class for CCSDS Tracking Data Message parsers.
42   * <p>
43   * Note than starting with Orekit 11.0, CCSDS message parsers are
44   * mutable objects that gather the data being parsed, until the
45   * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
46   * parseMessage} method has returned. This implies that parsers
47   * should <em>not</em> be used in a multi-thread context. The recommended
48   * way to use parsers is to either dedicate one parser for each message
49   * and drop it afterwards, or to use a single-thread loop.
50   * </p>
51   *
52   * <p>References:</p>
53   * <ul>
54   *   <li><a href="https://public.ccsds.org/Pubs/503x0b1c1.pdf">CCSDS 503.0-B-1 recommended standard</a> ("Tracking Data Message", Blue Book, Issue 1, November 2007)</li>
55   *   <li><a href="https://public.ccsds.org/Pubs/505x0b1.pdf">CCSDS 505.0-B-1 recommended standard</a> ("XML Specification for Navigation Data Message", Blue Book, Issue 1, December 2010)</li>
56   * </ul>
57   *
58   * @author Maxime Journot
59   * @since 9.0
60   */
61  public class TdmParser extends AbstractConstituentParser<TdmHeader, Tdm, TdmParser> {
62  
63      /** Converter for {@link RangeUnits#RU Range Units} (may be null). */
64      private final RangeUnitsConverter converter;
65  
66      /** Metadata for current observation block. */
67      private TdmMetadata metadata;
68  
69      /** Context binding valid for current metadata. */
70      private ContextBinding context;
71  
72      /** Current Observation Block being parsed. */
73      private ObservationsBlock observationsBlock;
74  
75      /** File header. */
76      private TdmHeader header;
77  
78      /** File segments. */
79      private List<Segment<TdmMetadata, ObservationsBlock>> segments;
80  
81      /** Processor for global message structure. */
82      private ProcessingState structureProcessor;
83  
84      /** Complete constructor.
85       * <p>
86       * Calling this constructor directly is not recommended. Users should rather use
87       * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildTdmParser()
88       * parserBuilder.buildTdmParser()}.
89       * </p>
90       * @param conventions IERS Conventions
91       * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
92       * @param dataContext used to retrieve frames, time scales, etc.
93       * @param parsedUnitsBehavior behavior to adopt for handling parsed units
94       * @param converter converter for {@link RangeUnits#RU Range Units} (may be null if there
95       * are no range observations in {@link RangeUnits#RU Range Units})
96       * @param filters filters to apply to parse tokens
97       * @param frameMapper for creating an Orekit {@link Frame}.
98       * @since 13.1.5
99       */
100     public TdmParser(final IERSConventions conventions, final boolean simpleEOP, final DataContext dataContext,
101                      final ParsedUnitsBehavior parsedUnitsBehavior, final RangeUnitsConverter converter,
102                      final Function<ParseToken, List<ParseToken>>[] filters,
103                      final CcsdsFrameMapper frameMapper) {
104         super(Tdm.ROOT, Tdm.FORMAT_VERSION_KEY, conventions, simpleEOP,
105                 dataContext, parsedUnitsBehavior, filters, frameMapper);
106         this.converter = converter;
107     }
108 
109     /** {@inheritDoc} */
110     @Override
111     public TdmHeader getHeader() {
112         return header;
113     }
114 
115     /** {@inheritDoc} */
116     @Override
117     public void reset(final FileFormat fileFormat) {
118         header             = new TdmHeader();
119         segments           = new ArrayList<>();
120         metadata           = null;
121         context            = null;
122         observationsBlock  = null;
123         if (fileFormat == FileFormat.XML) {
124             structureProcessor = new XmlStructureProcessingState(Tdm.ROOT, this);
125             reset(fileFormat, structureProcessor);
126         } else {
127             structureProcessor = new KvnStructureProcessingState(this);
128             reset(fileFormat, new HeaderProcessingState(this));
129         }
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     public Tdm build() {
135         return new Tdm(header, segments, getConventions(), getDataContext());
136     }
137 
138     /** {@inheritDoc} */
139     @Override
140     public boolean prepareHeader() {
141         anticipateNext(new HeaderProcessingState(this));
142         return true;
143     }
144 
145     /** {@inheritDoc} */
146     @Override
147     public boolean inHeader() {
148         anticipateNext(structureProcessor);
149         return true;
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public boolean finalizeHeader() {
155         header.validate(header.getFormatVersion());
156         return true;
157     }
158 
159     /** {@inheritDoc} */
160     @Override
161     public boolean prepareMetadata() {
162         if (metadata != null) {
163             return false;
164         }
165         metadata  = new TdmMetadata(getFrameMapper());
166         context   = new ContextBinding(
167             this::getConventions, this::isSimpleEOP,
168             this::getDataContext, this::getParsedUnitsBehavior,
169             () -> null, metadata::getTimeSystem, () -> 0.0, () -> 1.0);
170         anticipateNext(this::processMetadataToken);
171         return true;
172     }
173 
174     /** {@inheritDoc} */
175     @Override
176     public boolean inMetadata() {
177         anticipateNext(structureProcessor);
178         return true;
179     }
180 
181     /** {@inheritDoc} */
182     @Override
183     public boolean finalizeMetadata() {
184         metadata.validate(header.getFormatVersion());
185         return true;
186     }
187 
188     /** {@inheritDoc} */
189     @Override
190     public boolean prepareData() {
191         observationsBlock = new ObservationsBlock();
192         anticipateNext(this::processDataToken);
193         return true;
194     }
195 
196     /** {@inheritDoc} */
197     @Override
198     public boolean inData() {
199         anticipateNext(structureProcessor);
200         return true;
201     }
202 
203     /** {@inheritDoc} */
204     @Override
205     public boolean finalizeData() {
206         segments.add(new Segment<>(metadata, observationsBlock));
207         metadata          = null;
208         context           = null;
209         observationsBlock = null;
210         return true;
211     }
212 
213     /** Process one metadata token.
214      * @param token token to process
215      * @return true if token was processed, false otherwise
216      */
217     private boolean processMetadataToken(final ParseToken token) {
218         inMetadata();
219         try {
220             return token.getName() != null &&
221                    MetadataKey.valueOf(token.getName()).process(token, context, metadata);
222         } catch (IllegalArgumentException iaeM) {
223             try {
224                 return TdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
225             } catch (IllegalArgumentException iaeT) {
226                 // token has not been recognized
227                 return false;
228             }
229         }
230     }
231 
232     /** Process one data token.
233      * @param token token to process
234      * @return true if token was processed, false otherwise
235      */
236     private boolean processDataToken(final ParseToken token) {
237         try {
238             inData();
239             try {
240                 // global tokens (observation wrapper, comments, epoch in XML)
241                 return token.getName() != null &&
242                        TdmDataKey.valueOf(token.getName()).process(token, context, observationsBlock);
243             } catch (IllegalArgumentException iae) {
244                 // observation
245                 return ObservationType.valueOf(token.getName()).process(token, context, converter, metadata, observationsBlock);
246             }
247         } catch (IllegalArgumentException iae) {
248             // token has not been recognized
249             return false;
250         }
251     }
252 
253 }