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