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