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.cdm;
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.TimeSystem;
25  import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
26  import org.orekit.files.ccsds.ndm.odm.UserDefined;
27  import org.orekit.files.ccsds.section.CommentsContainer;
28  import org.orekit.files.ccsds.section.KvnStructureProcessingState;
29  import org.orekit.files.ccsds.section.MetadataKey;
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.lexical.TokenType;
35  import org.orekit.files.ccsds.utils.parsing.AbstractConstituentParser;
36  import org.orekit.files.ccsds.utils.parsing.ProcessingState;
37  import org.orekit.utils.IERSConventions;
38  
39  /**
40   * Base class for Conjunction Data Message parsers.
41   * <p>
42   * Note than starting with Orekit 11.0, CCSDS message parsers are
43   * mutable objects that gather the data being parsed, until the
44   * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
45   * parseMessage} method has returned. This implies that parsers
46   * should <em>not</em> be used in a multi-thread context. The recommended
47   * way to use parsers is to either dedicate one parser for each message
48   * and drop it afterwards, or to use a single-thread loop.
49   * </p>
50   * @author Melina Vanel
51   * @since 11.2
52   */
53  public class CdmParser extends AbstractConstituentParser<CdmHeader, Cdm, CdmParser> {
54  
55      /** Comment key. */
56      private static String COMMENT = "COMMENT";
57  
58      /** XML relative metadata key. */
59      private static String RELATIVEMETADATA = "relativeMetadataData";
60  
61      /** XML metadata key. */
62      private static String METADATA = "metadata";
63  
64      /** File header. */
65      private CdmHeader header;
66  
67      /** File segments. */
68      private List<CdmSegment> segments;
69  
70      /** CDM metadata being read. */
71      private CdmMetadata metadata;
72  
73      /** CDM relative metadata being read. */
74      private CdmRelativeMetadata relativeMetadata;
75  
76      /** Context binding valid for current metadata. */
77      private ContextBinding context;
78  
79      /** CDM general data comments block being read. */
80      private CommentsContainer commentsBlock;
81  
82      /** CDM OD parameters logical block being read. */
83      private ODParameters odParameters;
84  
85      /** CDM additional parameters logical block being read. */
86      private AdditionalParameters addParameters;
87  
88      /** CDM state vector logical block being read. */
89      private StateVector stateVector;
90  
91      /** CDM covariance matrix logical block being read. */
92      private RTNCovariance covMatrix;
93  
94      /** CDM XYZ covariance matrix logical block being read. */
95      private XYZCovariance xyzCovMatrix;
96  
97      /** CDM Sigma/Eigenvectors covariance logical block being read. */
98      private SigmaEigenvectorsCovariance sig3eigvec3;
99  
100     /** CDM additional covariance metadata logical block being read. */
101     private AdditionalCovarianceMetadata additionalCovMetadata;
102 
103     /** Processor for global message structure. */
104     private ProcessingState structureProcessor;
105 
106     /** Flag to only compute once relative metadata. */
107     private boolean doRelativeMetadata;
108 
109     /** Flag to indicate that data block parsing is finished. */
110     private boolean isDatafinished;
111 
112     /** CDM user defined logical block being read. */
113     private UserDefined userDefinedBlock;
114 
115 
116     /** Complete constructor.
117      * <p>
118      * Calling this constructor directly is not recommended. Users should rather use
119      * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildCdmParser()
120      * parserBuilder.buildCdmParser()}.
121      * </p>
122      * @param conventions IERS Conventions
123      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
124      * @param dataContext used to retrieve frames, time scales, etc.
125      * @param parsedUnitsBehavior behavior to adopt for handling parsed units
126      * @param filters filters to apply to parse tokens
127      * @since 12.0
128      */
129     public CdmParser(final IERSConventions conventions, final boolean simpleEOP, final DataContext dataContext,
130                      final ParsedUnitsBehavior parsedUnitsBehavior,
131                      final Function<ParseToken, List<ParseToken>>[] filters) {
132         super(Cdm.ROOT, Cdm.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext, parsedUnitsBehavior, filters);
133         this.doRelativeMetadata = true;
134         this.isDatafinished = false;
135     }
136 
137     /** {@inheritDoc} */
138     @Override
139     public CdmHeader getHeader() {
140         return header;
141     }
142 
143     /** {@inheritDoc} */
144     @Override
145     public void reset(final FileFormat fileFormat) {
146         header                    = new CdmHeader();
147         segments                  = new ArrayList<>();
148         metadata                  = null;
149         relativeMetadata          = null;
150         context                   = null;
151         odParameters              = null;
152         addParameters             = null;
153         stateVector               = null;
154         covMatrix                 = null;
155         xyzCovMatrix              = null;
156         sig3eigvec3               = null;
157         additionalCovMetadata     = null;
158         userDefinedBlock          = null;
159         commentsBlock             = null;
160         if (fileFormat == FileFormat.XML) {
161             structureProcessor = new XmlStructureProcessingState(Cdm.ROOT, this);
162             reset(fileFormat, structureProcessor);
163         } else {
164             structureProcessor = new KvnStructureProcessingState(this);
165             reset(fileFormat, new CdmHeaderProcessingState(this));
166         }
167     }
168 
169     /** {@inheritDoc} */
170     @Override
171     public boolean prepareHeader() {
172         anticipateNext(new CdmHeaderProcessingState(this));
173         return true;
174     }
175 
176     /** {@inheritDoc} */
177     @Override
178     public boolean inHeader() {
179         anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processMetadataToken);
180         return true;
181     }
182 
183     /** {@inheritDoc} */
184     @Override
185     public boolean finalizeHeader() {
186         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : structureProcessor);
187         header.validate(header.getFormatVersion());
188         return true;
189     }
190 
191     /** {@inheritDoc} */
192     @Override
193     public boolean prepareMetadata() {
194         if (metadata != null) {
195             return false;
196         }
197         if (doRelativeMetadata) {
198             // if parser is just after header it is time to create / read relative metadata,
199             // their are only initialized once and then shared between metadata for object 1 and 2
200             relativeMetadata = new CdmRelativeMetadata();
201             relativeMetadata.setTimeSystem(TimeSystem.UTC);
202         }
203         metadata  = new CdmMetadata(getDataContext());
204         metadata.setRelativeMetadata(relativeMetadata);
205 
206         // As no time system is defined in CDM because all dates are given in UTC,
207         // time system is set here to UTC, we use relative metadata and not metadata
208         // because setting time system on metadata implies refusingfurthercomments
209         // witch would be a problem as metadata comments have not been read yet.
210         context   = new ContextBinding(this::getConventions, this::isSimpleEOP,
211                                        this::getDataContext, this::getParsedUnitsBehavior,
212                                        () -> null, relativeMetadata::getTimeSystem,
213                                        () -> 0.0, () -> 1.0);
214         anticipateNext(this::processMetadataToken);
215         return true;
216     }
217 
218     /** {@inheritDoc} */
219     @Override
220     public boolean inMetadata() {
221         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processGeneralCommentToken);
222         return true;
223     }
224 
225     /** {@inheritDoc} */
226     @Override
227     public boolean finalizeMetadata() {
228         metadata.validate(header.getFormatVersion());
229         relativeMetadata.validate();
230         anticipateNext(structureProcessor);
231         return true;
232     }
233 
234     /** {@inheritDoc} */
235     @Override
236     public boolean prepareData() {
237         // stateVector and RTNCovariance blocks are 2 mandatory data blocks
238         stateVector = new StateVector();
239         covMatrix = new RTNCovariance();
240 
241         // initialize comments block for general data comments
242         commentsBlock = new CommentsContainer();
243         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processGeneralCommentToken);
244         return true;
245     }
246 
247     /** {@inheritDoc} */
248     @Override
249     public boolean inData() {
250         return true;
251     }
252 
253     /** {@inheritDoc} */
254     @Override
255     public boolean finalizeData() {
256         // call at the and of data block for object 1 or 2
257         if (metadata != null) {
258 
259             CdmData data = new CdmData(commentsBlock, odParameters, addParameters,
260                                              stateVector, covMatrix, additionalCovMetadata);
261 
262             if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.XYZ) {
263                 data = new CdmData(commentsBlock, odParameters, addParameters,
264                                              stateVector, covMatrix, xyzCovMatrix, additionalCovMetadata);
265             } else if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3) {
266                 data = new CdmData(commentsBlock, odParameters, addParameters,
267                                              stateVector, covMatrix, sig3eigvec3, additionalCovMetadata);
268             }
269 
270             data.validate(header.getFormatVersion());
271             segments.add(new CdmSegment(metadata, data));
272 
273             // Add the user defined block to both Objects data sections
274             if (userDefinedBlock != null && !userDefinedBlock.getParameters().isEmpty()) {
275                 for (CdmSegment segment : segments) {
276                     segment.getData().setUserDefinedBlock(userDefinedBlock);
277                 }
278             }
279         }
280         metadata                  = null;
281         context                   = null;
282         odParameters              = null;
283         addParameters             = null;
284         stateVector               = null;
285         covMatrix                 = null;
286         xyzCovMatrix              = null;
287         sig3eigvec3               = null;
288         additionalCovMetadata     = null;
289         userDefinedBlock          = null;
290         commentsBlock             = null;
291         return true;
292     }
293 
294     /** {@inheritDoc} */
295     @Override
296     public Cdm build() {
297         // CDM KVN file lack a DATA_STOP keyword, hence we can't call finalizeData()
298         // automatically before the end of the file
299         finalizeData();
300         if (userDefinedBlock != null && userDefinedBlock.getParameters().isEmpty()) {
301             userDefinedBlock = null;
302         }
303         final Cdm file = new Cdm(header, segments, getConventions(), getDataContext());
304         return file;
305     }
306 
307     /** Add a general comment.
308      * @param comment comment to add
309      * @return always return true
310      */
311     boolean addGeneralComment(final String comment) {
312         return commentsBlock.addComment(comment);
313 
314     }
315 
316     /** Manage relative metadata section.
317      * @param starting if true, parser is entering the section
318      * otherwise it is leaving the section
319      * @return always return true
320      */
321     boolean manageRelativeMetadataSection(final boolean starting) {
322         anticipateNext(starting ? this::processMetadataToken : structureProcessor);
323         return true;
324     }
325 
326     /** Manage relative metadata state vector section.
327      * @param starting if true, parser is entering the section
328      * otherwise it is leaving the section
329      * @return always return true
330      */
331     boolean manageRelativeStateVectorSection(final boolean starting) {
332         anticipateNext(this::processMetadataToken);
333         return true;
334     }
335 
336     /** Manage OD parameters section.
337      * @param starting if true, parser is entering the section
338      * otherwise it is leaving the section
339      * @return always return true
340      */
341     boolean manageODParametersSection(final boolean starting) {
342         commentsBlock.refuseFurtherComments();
343         anticipateNext(starting ? this::processODParamToken : structureProcessor);
344         return true;
345     }
346 
347     /** Manage additional parameters section.
348      * @param starting if true, parser is entering the section
349      * otherwise it is leaving the section
350      * @return always return true
351      */
352     boolean manageAdditionalParametersSection(final boolean starting) {
353         commentsBlock.refuseFurtherComments();
354         anticipateNext(starting ? this::processAdditionalParametersToken : structureProcessor);
355         return true;
356     }
357 
358     /** Manage state vector section.
359      * @param starting if true, parser is entering the section
360      * otherwise it is leaving the section
361      * @return always return true
362      */
363     boolean manageStateVectorSection(final boolean starting) {
364         commentsBlock.refuseFurtherComments();
365         anticipateNext(starting ? this::processStateVectorToken : structureProcessor);
366         return true;
367     }
368 
369     /** Manage general covariance section in XML file.
370      * @param starting if true, parser is entering the section
371      * otherwise it is leaving the section
372      * @return always return true
373      */
374     boolean manageXmlGeneralCovarianceSection(final boolean starting) {
375         commentsBlock.refuseFurtherComments();
376 
377         if (starting) {
378             if (metadata.getAltCovType() == null) {
379                 anticipateNext(this::processCovMatrixToken);
380             } else {
381                 if (Double.isNaN(covMatrix.getCrr())) {
382                     // First, handle mandatory RTN covariance section
383                     anticipateNext(this::processCovMatrixToken);
384                 } else if ( metadata.getAltCovType() == AltCovarianceType.XYZ && xyzCovMatrix == null ||
385                                 metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3 && sig3eigvec3 == null ) {
386                     // Second, add the alternate covariance if provided
387                     anticipateNext(this::processAltCovarianceToken);
388                 } else if (additionalCovMetadata == null) {
389                     // Third, process the additional covariance metadata
390                     anticipateNext(this::processAdditionalCovMetadataToken);
391                 }
392             }
393         } else {
394             anticipateNext(structureProcessor);
395         }
396 
397         return true;
398     }
399 
400     /** Manage user-defined parameters section.
401      * @param starting if true, parser is entering the section
402      * otherwise it is leaving the section
403      * @return always return true
404      */
405     boolean manageUserDefinedParametersSection(final boolean starting) {
406         commentsBlock.refuseFurtherComments();
407         if (starting) {
408             if (userDefinedBlock == null) {
409                 // this is the first (and unique) user-defined parameters block, we need to allocate the container
410                 userDefinedBlock = new UserDefined();
411             }
412             anticipateNext(this::processUserDefinedToken);
413         } else {
414             anticipateNext(structureProcessor);
415         }
416         return true;
417     }
418 
419     /** Process one metadata token.
420      * @param token token to process
421      * @return true if token was processed, false otherwise
422      */
423     private boolean processMetadataToken(final ParseToken token) {
424         if (isDatafinished && getFileFormat() != FileFormat.XML) {
425             finalizeData();
426             isDatafinished = false;
427         }
428         if (metadata == null) {
429             // CDM KVN file lack a META_START keyword, hence we can't call prepareMetadata()
430             // automatically before the first metadata token arrives
431             prepareMetadata();
432         }
433         inMetadata();
434 
435         // There can be a COMMENT key at the beginning of relative metadata, but as the relative
436         // metadata are processed in the same try and catch loop than metadata because Orekit is
437         // build to read metadata and then data (and not relative metadata), it would be problematic
438         // to make relative metadata extends comments container(because of the COMMENTS in the middle
439         // of relativemetadata and metadata section. Indeed, as said in {@link
440         // #CommentsContainer} COMMENT should only be at the beginning of sections but in this case
441         // there is a comment at the beginning corresponding to the relative metadata comment
442         // and 1 in the middle for object 1 metadata and one further for object 2 metadata. That
443         // is why this special syntax was used and initializes the relative metadata COMMENT once
444         // at the beginning as relative metadata is not a comment container
445         final String tokenName = token.getName();  // may be null
446         if (COMMENT.equals(tokenName) && doRelativeMetadata ) {
447             if (token.getType() == TokenType.ENTRY) {
448                 relativeMetadata.addComment(token.getContentAsNormalizedString());
449                 return true;
450             }
451         }
452         doRelativeMetadata = false;
453 
454         // null check here to avoid NPE later
455         if (tokenName == null) {
456             return false;
457         }
458         // try...catch structured so that process(...) is not in the try block.
459         // Needed so that IAE indicates enum lookup failure.
460         final CdmRelativeMetadataKey cdmRelativeMetadataKey;
461         try {
462             cdmRelativeMetadataKey = CdmRelativeMetadataKey.valueOf(tokenName);
463         } catch (IllegalArgumentException iaeM) {
464             final MetadataKey metadataKey;
465             try {
466                 metadataKey = MetadataKey.valueOf(tokenName);
467             } catch (IllegalArgumentException iaeD) {
468                 final CdmMetadataKey cdmMetadataKey;
469                 try {
470                     cdmMetadataKey = CdmMetadataKey.valueOf(tokenName);
471                 } catch (IllegalArgumentException iaeC) {
472                     // token has not been recognized
473                     return false;
474                 }
475                 return cdmMetadataKey.process(token, context, metadata);
476             }
477             return metadataKey.process(token, context, metadata);
478         }
479         return cdmRelativeMetadataKey.process(token, context, relativeMetadata);
480     }
481 
482     /** Process one XML data substructure token.
483      * @param token token to process
484      * @return true if token was processed, false otherwise
485      */
486     private boolean processXmlSubStructureToken(final ParseToken token) {
487 
488         // As no relativemetadata token exists in the structure processor and as RelativeMetadata keys are
489         // processed in the same try and catch loop in processMetadatatoken as CdmMetadata keys, if the relativemetadata
490         // token is read it should be as if the token was equal to metadata to start to initialize relative metadata
491         // and metadata and to go in the processMetadataToken try and catch loop. The following relativemetadata
492         // stop should be ignored to stay in the processMetadataToken try and catch loop and the following metadata
493         // start also ignored to stay in the processMetadataToken try and catch loop. Then arrives the end of metadata
494         // so we call structure processor with metadata stop. This distinction of cases is useful for relativemetadata
495         // block followed by metadata block for object 1 and also useful to only close metadata block for object 2.
496         // The metadata start for object 2 is processed by structureProcessor
497         if (METADATA.equals(token.getName()) && TokenType.START.equals(token.getType()) ||
498             RELATIVEMETADATA.equals(token.getName()) && TokenType.STOP.equals(token.getType())) {
499             anticipateNext(this::processMetadataToken);
500             return true;
501 
502         } else if (RELATIVEMETADATA.equals(token.getName()) && TokenType.START.equals(token.getType()) ||
503                    METADATA.equals(token.getName()) && TokenType.STOP.equals(token.getType())) {
504             final ParseToken replaceToken = new ParseToken(token.getType(), METADATA,
505                                       null, token.getUnits(), token.getLineNumber(), token.getFileName());
506 
507             return structureProcessor.processToken(replaceToken);
508 
509         } else {
510 
511             // Relative metadata COMMENT and metadata COMMENT should not be read by XmlSubStructureKey that
512             // is why 2 cases are distinguished here : the COMMENT for relative metadata and the COMMENT
513             // for metadata.
514             if (commentsBlock == null && COMMENT.equals(token.getName())) {
515 
516                 // COMMENT adding for Relative Metadata in XML
517                 if (doRelativeMetadata) {
518                     if (token.getType() == TokenType.ENTRY) {
519                         relativeMetadata.addComment(token.getContentAsNormalizedString());
520                         doRelativeMetadata = false;
521                         return true;
522 
523                     } else {
524                         // if the token Type is still not ENTRY we return true as at the next step
525                         // it will be ENTRY ad we will be able to store the comment (similar treatment
526                         // as OD parameter or Additional parameter or State Vector ... COMMENT treatment.)
527                         return true;
528                     }
529                 }
530 
531                 // COMMENT adding for Metadata in XML
532                 if (!doRelativeMetadata) {
533                     if (token.getType() == TokenType.ENTRY) {
534                         metadata.addComment(token.getContentAsNormalizedString());
535                         return true;
536 
537                     } else {
538                         // same as above
539                         return true;
540                     }
541                 }
542             }
543 
544             // to treat XmlSubStructureKey keys ( OD parameters, relative Metadata ...)
545             try {
546                 return token.getName() != null && !doRelativeMetadata &&
547                        XmlSubStructureKey.valueOf(token.getName()).process(token, this);
548             } catch (IllegalArgumentException iae) {
549                 // token has not been recognized
550                 return false;
551             }
552         }
553     }
554 
555     /** Process one comment token.
556      * @param token token to process
557      * @return true if token was processed, false otherwise
558      */
559     private boolean processGeneralCommentToken(final ParseToken token) {
560         if (commentsBlock == null) {
561             // CDM KVN file lack a META_STOP keyword, hence we can't call finalizeMetadata()
562             // automatically before the first data token arrives
563             finalizeMetadata();
564             // CDM KVN file lack a DATA_START keyword, hence we can't call prepareData()
565             // automatically before the first data token arrives
566             prepareData();
567         }
568         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processODParamToken);
569         if (COMMENT.equals(token.getName()) && commentsBlock.acceptComments()) {
570             if (token.getType() == TokenType.ENTRY) {
571                 commentsBlock.addComment(token.getContentAsNormalizedString());
572             }
573             // in order to be able to differentiate general data comments and next block comment (OD parameters if not empty)
574             // only 1 line comment is allowed for general data comment.
575             commentsBlock.refuseFurtherComments();
576             return true;
577         } else {
578             return false;
579         }
580     }
581 
582     /** Process one od parameter data token.
583      * @param token token to process
584      * @return true if token was processed, false otherwise
585      */
586     private boolean processODParamToken(final ParseToken token) {
587         if (odParameters == null) {
588             odParameters = new ODParameters();
589         }
590         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAdditionalParametersToken);
591         try {
592             return token.getName() != null &&
593                    ODParametersKey.valueOf(token.getName()).process(token, context, odParameters);
594         } catch (IllegalArgumentException iae) {
595             // token has not been recognized
596             return false;
597         }
598     }
599 
600     /** Process one additional parameter data token.
601      * @param token token to process
602      * @return true if token was processed, false otherwise
603      */
604     private boolean processAdditionalParametersToken(final ParseToken token) {
605         if (addParameters == null) {
606             addParameters = new AdditionalParameters();
607         }
608         if (moveCommentsIfEmpty(odParameters, addParameters)) {
609             // get rid of the empty logical block
610             odParameters = null;
611         }
612         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processStateVectorToken);
613         try {
614             return token.getName() != null &&
615                    AdditionalParametersKey.valueOf(token.getName()).process(token, context, addParameters);
616         } catch (IllegalArgumentException iae) {
617             // token has not been recognized
618             return false;
619         }
620     }
621 
622     /** Process one state vector data token.
623      * @param token token to process
624      * @return true if token was processed, false otherwise
625      */
626     private boolean processStateVectorToken(final ParseToken token) {
627         if (moveCommentsIfEmpty(addParameters, stateVector)) {
628             // get rid of the empty logical block
629             addParameters = null;
630         }
631         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processCovMatrixToken);
632         try {
633             return token.getName() != null &&
634                    StateVectorKey.valueOf(token.getName()).process(token, context, stateVector);
635         } catch (IllegalArgumentException iae) {
636             // token has not been recognized
637             return false;
638         }
639     }
640 
641     /** Process covariance matrix data token.
642      * @param token token to process
643      * @return true if token was processed, false otherwise
644      */
645     private boolean processCovMatrixToken(final ParseToken token) {
646 
647         if (moveCommentsIfEmpty(stateVector, covMatrix)) {
648             // get rid of the empty logical block
649             stateVector = null;
650         }
651 
652         if (metadata.getAltCovType() == null) {
653             anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
654         } else {
655             anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAltCovarianceToken);
656         }
657 
658         isDatafinished = true;
659         try {
660             return token.getName() != null &&
661                    RTNCovarianceKey.valueOf(token.getName()).process(token, context, covMatrix);
662         } catch (IllegalArgumentException iae) {
663             // token has not been recognized
664             return false;
665         }
666     }
667 
668     /** Process alternate covariance data token.
669      * @param token token to process
670      * @return true if token was processed, false otherwise
671      */
672     private boolean processAltCovarianceToken(final ParseToken token) {
673 
674         // Covariance is provided in XYZ
675         if (metadata.getAltCovType() == AltCovarianceType.XYZ && xyzCovMatrix == null) {
676             xyzCovMatrix = new XYZCovariance(true);
677 
678             if (moveCommentsIfEmpty(covMatrix, xyzCovMatrix)) {
679                 // get rid of the empty logical block
680                 covMatrix = null;
681             }
682         }
683         // Covariance is provided in CSIG3EIGVEC3 format
684         if (metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3 && sig3eigvec3 == null) {
685             sig3eigvec3 = new SigmaEigenvectorsCovariance(true);
686 
687             if (moveCommentsIfEmpty(covMatrix, sig3eigvec3)) {
688                 // get rid of the empty logical block
689                 covMatrix = null;
690             }
691         }
692 
693 
694         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAdditionalCovMetadataToken);
695         try {
696 
697             if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.XYZ) {
698 
699                 return token.getName() != null &&
700                            XYZCovarianceKey.valueOf(token.getName()).process(token, context, xyzCovMatrix);
701 
702             } else if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3) {
703 
704                 return token.getName() != null &&
705                            SigmaEigenvectorsCovarianceKey.valueOf(token.getName()).process(token, context, sig3eigvec3);
706 
707             } else {
708 
709                 // token has not been recognized
710                 return false;
711 
712             }
713 
714         } catch (IllegalArgumentException iae) {
715             // token has not been recognized
716             return false;
717         }
718     }
719 
720     /** Process additional covariance metadata token.
721      * @param token token to process
722      * @return true if token was processed, false otherwise
723      */
724     private boolean processAdditionalCovMetadataToken(final ParseToken token) {
725 
726         // Additional covariance metadata
727         if ( additionalCovMetadata == null) {
728             additionalCovMetadata = new AdditionalCovarianceMetadata();
729         }
730 
731         if (moveCommentsIfEmpty(xyzCovMatrix, additionalCovMetadata)) {
732             // get rid of the empty logical block
733             xyzCovMatrix = null;
734         } else if (moveCommentsIfEmpty(sig3eigvec3, additionalCovMetadata)) {
735             // get rid of the empty logical block
736             sig3eigvec3 = null;
737         }
738 
739         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processUserDefinedToken);
740         try {
741             return token.getName() != null &&
742                            AdditionalCovarianceMetadataKey.valueOf(token.getName()).process(token, context, additionalCovMetadata);
743         } catch (IllegalArgumentException iae) {
744             // token has not been recognized
745             return false;
746         }
747     }
748 
749     /** Process one user-defined parameter data token.
750      * @param token token to process
751      * @return true if token was processed, false otherwise
752      */
753     private boolean processUserDefinedToken(final ParseToken token) {
754 
755         if (userDefinedBlock == null) {
756             userDefinedBlock = new UserDefined();
757         }
758 
759         if (moveCommentsIfEmpty(additionalCovMetadata, userDefinedBlock)) {
760             // get rid of the empty logical block
761             additionalCovMetadata = null;
762         }
763 
764         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
765 
766         if (COMMENT.equals(token.getName())) {
767             return token.getType() == TokenType.ENTRY ? userDefinedBlock.addComment(token.getContentAsNormalizedString()) : true;
768         } else if (token.getName().startsWith(UserDefined.USER_DEFINED_PREFIX)) {
769             if (token.getType() == TokenType.ENTRY) {
770                 userDefinedBlock.addEntry(token.getName().substring(UserDefined.USER_DEFINED_PREFIX.length()),
771                                           token.getContentAsNormalizedString());
772             }
773             return true;
774         } else {
775             // the token was not processed
776             return false;
777         }
778     }
779 
780 
781     /** Move comments from one empty logical block to another logical block.
782      * @param origin origin block
783      * @param destination destination block
784      * @return true if origin block was empty
785      */
786     private boolean moveCommentsIfEmpty(final CommentsContainer origin, final CommentsContainer destination) {
787         if (origin != null && origin.acceptComments()) {
788             // origin block is empty, move the existing comments
789             for (final String comment : origin.getComments()) {
790                 destination.addComment(comment);
791             }
792             return true;
793         } else {
794             return false;
795         }
796     }
797 
798 }
799