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.adm.aem;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.regex.Pattern;
22  
23  import org.orekit.data.DataContext;
24  import org.orekit.data.DataSource;
25  import org.orekit.errors.OrekitException;
26  import org.orekit.errors.OrekitMessages;
27  import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
28  import org.orekit.files.ccsds.ndm.adm.AdmMetadataKey;
29  import org.orekit.files.ccsds.ndm.adm.AdmParser;
30  import org.orekit.files.ccsds.section.Header;
31  import org.orekit.files.ccsds.section.HeaderProcessingState;
32  import org.orekit.files.ccsds.section.KvnStructureProcessingState;
33  import org.orekit.files.ccsds.section.MetadataKey;
34  import org.orekit.files.ccsds.section.XmlStructureProcessingState;
35  import org.orekit.files.ccsds.utils.ContextBinding;
36  import org.orekit.files.ccsds.utils.FileFormat;
37  import org.orekit.files.ccsds.utils.lexical.ParseToken;
38  import org.orekit.files.ccsds.utils.lexical.TokenType;
39  import org.orekit.files.ccsds.utils.parsing.ProcessingState;
40  import org.orekit.files.general.AttitudeEphemerisFileParser;
41  import org.orekit.time.AbsoluteDate;
42  import org.orekit.utils.IERSConventions;
43  
44  /**
45   * A parser for the CCSDS AEM (Attitude Ephemeris Message).
46   * <p>
47   * Note than starting with Orekit 11.0, CCSDS message parsers are
48   * mutable objects that gather the data being parsed, until the
49   * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
50   * parseMessage} method has returned. This implies that parsers
51   * should <em>not</em> be used in a multi-thread context. The recommended
52   * way to use parsers is to either dedicate one parser for each message
53   * and drop it afterwards, or to use a single-thread loop.
54   * </p>
55   * @author Bryan Cazabonne
56   * @since 10.2
57   */
58  public class AemParser extends AdmParser<Aem, AemParser> implements AttitudeEphemerisFileParser<Aem> {
59  
60      /** Pattern for splitting strings at blanks. */
61      private static final Pattern SPLIT_AT_BLANKS = Pattern.compile("\\s+");
62  
63      /** File header. */
64      private Header header;
65  
66      /** File segments. */
67      private List<AemSegment> segments;
68  
69      /** Metadata for current observation block. */
70      private AemMetadata metadata;
71  
72      /** Context binding valid for current metadata. */
73      private ContextBinding context;
74  
75      /** Current Ephemerides block being parsed. */
76      private AemData currentBlock;
77  
78      /** Default interpolation degree. */
79      private int defaultInterpolationDegree;
80  
81      /** Processor for global message structure. */
82      private ProcessingState structureProcessor;
83  
84      /** Current attitude entry. */
85      private AttitudeEntry currentEntry;
86  
87      /**Complete constructor.
88       * <p>
89       * Calling this constructor directly is not recommended. Users should rather use
90       * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildAemParser()
91       * parserBuilder.buildAemParser()}.
92       * </p>
93       * @param conventions IERS Conventions
94       * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
95       * @param dataContext used to retrieve frames, time scales, etc.
96       * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
97       * (may be null if time system is absolute)
98       * @param defaultInterpolationDegree default interpolation degree
99       * @param parsedUnitsBehavior behavior to adopt for handling parsed units
100      */
101     public AemParser(final IERSConventions conventions, final boolean simpleEOP,
102                      final DataContext dataContext, final AbsoluteDate missionReferenceDate,
103                      final int defaultInterpolationDegree, final ParsedUnitsBehavior parsedUnitsBehavior) {
104         super(Aem.ROOT, Aem.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext,
105               missionReferenceDate, parsedUnitsBehavior);
106         this.defaultInterpolationDegree  = defaultInterpolationDegree;
107     }
108 
109     /** {@inheritDoc} */
110     @Override
111     public Aem parse(final DataSource source) {
112         return parseMessage(source);
113     }
114 
115     /** {@inheritDoc} */
116     @Override
117     public Header getHeader() {
118         return header;
119     }
120 
121     /** {@inheritDoc} */
122     @Override
123     public void reset(final FileFormat fileFormat) {
124         header   = new Header(2.0);
125         segments = new ArrayList<>();
126         metadata = null;
127         context  = null;
128         if (fileFormat == FileFormat.XML) {
129             structureProcessor = new XmlStructureProcessingState(Aem.ROOT, this);
130             reset(fileFormat, structureProcessor);
131         } else {
132             structureProcessor = new KvnStructureProcessingState(this);
133             reset(fileFormat, new HeaderProcessingState(this));
134         }
135     }
136 
137     /** {@inheritDoc} */
138     @Override
139     public boolean prepareHeader() {
140         anticipateNext(new HeaderProcessingState(this));
141         return true;
142     }
143 
144     /** {@inheritDoc} */
145     @Override
146     public boolean inHeader() {
147         anticipateNext(structureProcessor);
148         return true;
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     public boolean finalizeHeader() {
154         header.validate(header.getFormatVersion());
155         return true;
156     }
157 
158     /** {@inheritDoc} */
159     @Override
160     public boolean prepareMetadata() {
161         if (metadata != null) {
162             return false;
163         }
164         metadata  = new AemMetadata(defaultInterpolationDegree);
165         context   = new ContextBinding(this::getConventions, this::isSimpleEOP,
166                                        this::getDataContext, this::getParsedUnitsBehavior,
167                                        this::getMissionReferenceDate,
168                                        metadata::getTimeSystem, () -> 0.0, () -> 1.0);
169         anticipateNext(this::processMetadataToken);
170         return true;
171     }
172 
173     /** {@inheritDoc} */
174     @Override
175     public boolean inMetadata() {
176         anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processKvnDataToken);
177         return true;
178     }
179 
180     /** {@inheritDoc} */
181     @Override
182     public boolean finalizeMetadata() {
183         metadata.validate(header.getFormatVersion());
184         return true;
185     }
186 
187     /** {@inheritDoc} */
188     @Override
189     public boolean prepareData() {
190         currentBlock = new AemData();
191         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
192         return true;
193     }
194 
195     /** {@inheritDoc} */
196     @Override
197     public boolean inData() {
198         anticipateNext(structureProcessor);
199         return true;
200     }
201 
202     /** {@inheritDoc} */
203     @Override
204     public boolean finalizeData() {
205         if (metadata != null) {
206             currentBlock.validate(header.getFormatVersion());
207             segments.add(new AemSegment(metadata, currentBlock));
208         }
209         metadata = null;
210         context  = null;
211         return true;
212     }
213 
214     /** {@inheritDoc} */
215     @Override
216     public Aem build() {
217         final Aem file = new Aem(header, segments, getConventions(), getDataContext());
218         file.checkTimeSystems();
219         return file;
220     }
221 
222     /** Manage attitude state section in a XML message.
223      * @param starting if true, parser is entering the section
224      * otherwise it is leaving the section
225      * @return always return true
226      */
227     boolean manageXmlAttitudeStateSection(final boolean starting) {
228         if (starting) {
229             currentEntry = new AttitudeEntry(metadata);
230             anticipateNext(this::processXmlDataToken);
231         } else {
232             currentBlock.addData(currentEntry.getCoordinates());
233             currentEntry = null;
234             anticipateNext(structureProcessor);
235         }
236         return true;
237     }
238 
239     /** Add a comment to the data section.
240      * @param comment comment to add
241      * @return always return true
242      */
243     boolean addDataComment(final String comment) {
244         currentBlock.addComment(comment);
245         return true;
246     }
247 
248     /** Process one metadata token.
249      * @param token token to process
250      * @return true if token was processed, false otherwise
251      */
252     private boolean processMetadataToken(final ParseToken token) {
253         inMetadata();
254         try {
255             return token.getName() != null &&
256                    MetadataKey.valueOf(token.getName()).process(token, context, metadata);
257         } catch (IllegalArgumentException iaeM) {
258             try {
259                 return AdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
260             } catch (IllegalArgumentException iaeD) {
261                 try {
262                     return AemMetadataKey.valueOf(token.getName()).process(token, context, metadata);
263                 } catch (IllegalArgumentException iaeE) {
264                     // token has not been recognized
265                     return false;
266                 }
267             }
268         }
269     }
270 
271     /** Process one XML data substructure token.
272      * @param token token to process
273      * @return true if token was processed, false otherwise
274      */
275     private boolean processXmlSubStructureToken(final ParseToken token) {
276         try {
277             return token.getName() != null &&
278                    XmlSubStructureKey.valueOf(token.getName()).process(token, this);
279         } catch (IllegalArgumentException iae) {
280             // token has not been recognized
281             return false;
282         }
283     }
284 
285     /** Process one data token in a KVN message.
286      * @param token token to process
287      * @return true if token was processed, false otherwise
288      */
289     private boolean processKvnDataToken(final ParseToken token) {
290         inData();
291         if ("COMMENT".equals(token.getName())) {
292             return token.getType() == TokenType.ENTRY ? currentBlock.addComment(token.getContentAsNormalizedString()) : true;
293         } else if (token.getType() == TokenType.RAW_LINE) {
294             try {
295                 if (metadata.getAttitudeType() == null) {
296                     throw new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
297                                               AemMetadataKey.ATTITUDE_TYPE.name(), token.getFileName());
298                 }
299                 return currentBlock.addData(metadata.getAttitudeType().parse(metadata.isFirst(),
300                                                                              metadata.getEndpoints().isExternal2SpacecraftBody(),
301                                                                              metadata.getEulerRotSeq(),
302                                                                              metadata.isSpacecraftBodyRate(),
303                                                                              context, SPLIT_AT_BLANKS.split(token.getRawContent().trim())));
304             } catch (NumberFormatException nfe) {
305                 throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
306                                           token.getLineNumber(), token.getFileName(), token.getRawContent());
307             }
308         } else {
309             // not a raw line, it is most probably the end of the data section
310             return false;
311         }
312     }
313 
314     /** Process one data token in a XML message.
315      * @param token token to process
316      * @return true if token was processed, false otherwise
317      */
318     private boolean processXmlDataToken(final ParseToken token) {
319         anticipateNext(this::processXmlSubStructureToken);
320         try {
321             return token.getName() != null &&
322                    AttitudeEntryKey.valueOf(token.getName()).process(token, context, currentEntry);
323         } catch (IllegalArgumentException iae) {
324             // token has not been recognized
325             return false;
326         }
327     }
328 
329 }