CdmParser.java

  1. /* Copyright 2002-2023 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. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.function.Function;

  21. import org.orekit.data.DataContext;
  22. import org.orekit.files.ccsds.definitions.TimeSystem;
  23. import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
  24. import org.orekit.files.ccsds.ndm.odm.UserDefined;
  25. import org.orekit.files.ccsds.section.CommentsContainer;
  26. import org.orekit.files.ccsds.section.KvnStructureProcessingState;
  27. import org.orekit.files.ccsds.section.MetadataKey;
  28. import org.orekit.files.ccsds.section.XmlStructureProcessingState;
  29. import org.orekit.files.ccsds.utils.ContextBinding;
  30. import org.orekit.files.ccsds.utils.FileFormat;
  31. import org.orekit.files.ccsds.utils.lexical.ParseToken;
  32. import org.orekit.files.ccsds.utils.lexical.TokenType;
  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.  * Base class for Conjunction Data Message parsers.
  38.  * <p>
  39.  * Note than starting with Orekit 11.0, CCSDS message parsers are
  40.  * mutable objects that gather the data being parsed, until the
  41.  * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
  42.  * parseMessage} method has returned. This implies that parsers
  43.  * should <em>not</em> be used in a multi-thread context. The recommended
  44.  * way to use parsers is to either dedicate one parser for each message
  45.  * and drop it afterwards, or to use a single-thread loop.
  46.  * </p>
  47.  * @author Melina Vanel
  48.  * @since 11.2
  49.  */
  50. public class CdmParser extends AbstractConstituentParser<CdmHeader, Cdm, CdmParser> {

  51.     /** Comment key. */
  52.     private static String COMMENT = "COMMENT";

  53.     /** XML relative metadata key. */
  54.     private static String RELATIVEMETADATA = "relativeMetadataData";

  55.     /** XML metadata key. */
  56.     private static String METADATA = "metadata";

  57.     /** File header. */
  58.     private CdmHeader header;

  59.     /** File segments. */
  60.     private List<CdmSegment> segments;

  61.     /** CDM metadata being read. */
  62.     private CdmMetadata metadata;

  63.     /** CDM relative metadata being read. */
  64.     private CdmRelativeMetadata relativeMetadata;

  65.     /** Context binding valid for current metadata. */
  66.     private ContextBinding context;

  67.     /** CDM general data comments block being read. */
  68.     private CommentsContainer commentsBlock;

  69.     /** CDM OD parameters logical block being read. */
  70.     private ODParameters odParameters;

  71.     /** CDM additional parameters logical block being read. */
  72.     private AdditionalParameters addParameters;

  73.     /** CDM state vector logical block being read. */
  74.     private StateVector stateVector;

  75.     /** CDM covariance matrix logical block being read. */
  76.     private RTNCovariance covMatrix;

  77.     /** CDM XYZ covariance matrix logical block being read. */
  78.     private XYZCovariance xyzCovMatrix;

  79.     /** CDM Sigma/Eigenvectors covariance logical block being read. */
  80.     private SigmaEigenvectorsCovariance sig3eigvec3;

  81.     /** CDM additional covariance metadata logical block being read. */
  82.     private AdditionalCovarianceMetadata additionalCovMetadata;

  83.     /** Processor for global message structure. */
  84.     private ProcessingState structureProcessor;

  85.     /** Flag to only compute once relative metadata. */
  86.     private boolean doRelativeMetadata;

  87.     /** Flag to indicate that data block parsing is finished. */
  88.     private boolean isDatafinished;

  89.     /** CDM user defined logical block being read. */
  90.     private UserDefined userDefinedBlock;


  91.     /** Complete constructor.
  92.      * <p>
  93.      * Calling this constructor directly is not recommended. Users should rather use
  94.      * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildCdmParser()
  95.      * parserBuilder.buildCdmParser()}.
  96.      * </p>
  97.      * @param conventions IERS Conventions
  98.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  99.      * @param dataContext used to retrieve frames, time scales, etc.
  100.      * @param parsedUnitsBehavior behavior to adopt for handling parsed units
  101.      * @param filters filters to apply to parse tokens
  102.      * @since 12.0
  103.      */
  104.     public CdmParser(final IERSConventions conventions, final boolean simpleEOP, final DataContext dataContext,
  105.                      final ParsedUnitsBehavior parsedUnitsBehavior,
  106.                      final Function<ParseToken, List<ParseToken>>[] filters) {
  107.         super(Cdm.ROOT, Cdm.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext, parsedUnitsBehavior, filters);
  108.         this.doRelativeMetadata = true;
  109.         this.isDatafinished = false;
  110.     }

  111.     /** {@inheritDoc} */
  112.     @Override
  113.     public CdmHeader getHeader() {
  114.         return header;
  115.     }

  116.     /** {@inheritDoc} */
  117.     @Override
  118.     public void reset(final FileFormat fileFormat) {
  119.         header                    = new CdmHeader();
  120.         segments                  = new ArrayList<>();
  121.         metadata                  = null;
  122.         relativeMetadata          = null;
  123.         context                   = null;
  124.         odParameters              = null;
  125.         addParameters             = null;
  126.         stateVector               = null;
  127.         covMatrix                 = null;
  128.         xyzCovMatrix              = null;
  129.         sig3eigvec3               = null;
  130.         additionalCovMetadata     = null;
  131.         userDefinedBlock          = null;
  132.         commentsBlock             = null;
  133.         if (fileFormat == FileFormat.XML) {
  134.             structureProcessor = new XmlStructureProcessingState(Cdm.ROOT, this);
  135.             reset(fileFormat, structureProcessor);
  136.         } else {
  137.             structureProcessor = new KvnStructureProcessingState(this);
  138.             reset(fileFormat, new CdmHeaderProcessingState(this));
  139.         }
  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public boolean prepareHeader() {
  144.         anticipateNext(new CdmHeaderProcessingState(this));
  145.         return true;
  146.     }

  147.     /** {@inheritDoc} */
  148.     @Override
  149.     public boolean inHeader() {
  150.         anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processMetadataToken);
  151.         return true;
  152.     }

  153.     /** {@inheritDoc} */
  154.     @Override
  155.     public boolean finalizeHeader() {
  156.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : structureProcessor);
  157.         header.validate(header.getFormatVersion());
  158.         return true;
  159.     }

  160.     /** {@inheritDoc} */
  161.     @Override
  162.     public boolean prepareMetadata() {
  163.         if (metadata != null) {
  164.             return false;
  165.         }
  166.         if (doRelativeMetadata) {
  167.             // if parser is just after header it is time to create / read relative metadata,
  168.             // their are only initialized once and then shared between metadata for object 1 and 2
  169.             relativeMetadata = new CdmRelativeMetadata();
  170.             relativeMetadata.setTimeSystem(TimeSystem.UTC);
  171.         }
  172.         metadata  = new CdmMetadata(getDataContext());
  173.         metadata.setRelativeMetadata(relativeMetadata);

  174.         // As no time system is defined in CDM because all dates are given in UTC,
  175.         // time system is set here to UTC, we use relative metadata and not metadata
  176.         // because setting time system on metadata implies refusingfurthercomments
  177.         // witch would be a problem as metadata comments have not been read yet.
  178.         context   = new ContextBinding(this::getConventions, this::isSimpleEOP,
  179.                                        this::getDataContext, this::getParsedUnitsBehavior,
  180.                                        () -> null, relativeMetadata::getTimeSystem,
  181.                                        () -> 0.0, () -> 1.0);
  182.         anticipateNext(this::processMetadataToken);
  183.         return true;
  184.     }

  185.     /** {@inheritDoc} */
  186.     @Override
  187.     public boolean inMetadata() {
  188.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processGeneralCommentToken);
  189.         return true;
  190.     }

  191.     /** {@inheritDoc} */
  192.     @Override
  193.     public boolean finalizeMetadata() {
  194.         metadata.validate(header.getFormatVersion());
  195.         relativeMetadata.validate();
  196.         anticipateNext(structureProcessor);
  197.         return true;
  198.     }

  199.     /** {@inheritDoc} */
  200.     @Override
  201.     public boolean prepareData() {
  202.         // stateVector and RTNCovariance blocks are 2 mandatory data blocks
  203.         stateVector = new StateVector();
  204.         covMatrix = new RTNCovariance();

  205.         // initialize comments block for general data comments
  206.         commentsBlock = new CommentsContainer();
  207.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processGeneralCommentToken);
  208.         return true;
  209.     }

  210.     /** {@inheritDoc} */
  211.     @Override
  212.     public boolean inData() {
  213.         return true;
  214.     }

  215.     /** {@inheritDoc} */
  216.     @Override
  217.     public boolean finalizeData() {
  218.         // call at the and of data block for object 1 or 2
  219.         if (metadata != null) {

  220.             CdmData data = new CdmData(commentsBlock, odParameters, addParameters,
  221.                                              stateVector, covMatrix, additionalCovMetadata);

  222.             if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.XYZ) {
  223.                 data = new CdmData(commentsBlock, odParameters, addParameters,
  224.                                              stateVector, covMatrix, xyzCovMatrix, additionalCovMetadata);
  225.             } else if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3) {
  226.                 data = new CdmData(commentsBlock, odParameters, addParameters,
  227.                                              stateVector, covMatrix, sig3eigvec3, additionalCovMetadata);
  228.             }

  229.             data.validate(header.getFormatVersion());
  230.             segments.add(new CdmSegment(metadata, data));

  231.             // Add the user defined block to both Objects data sections
  232.             if (userDefinedBlock != null && !userDefinedBlock.getParameters().isEmpty()) {
  233.                 for (int i = 0; i < segments.size(); i++) {
  234.                     segments.get(i).getData().setUserDefinedBlock(userDefinedBlock);
  235.                 }
  236.             }
  237.         }
  238.         metadata                  = null;
  239.         context                   = null;
  240.         odParameters              = null;
  241.         addParameters             = null;
  242.         stateVector               = null;
  243.         covMatrix                 = null;
  244.         xyzCovMatrix              = null;
  245.         sig3eigvec3               = null;
  246.         additionalCovMetadata     = null;
  247.         userDefinedBlock          = null;
  248.         commentsBlock             = null;
  249.         return true;
  250.     }

  251.     /** {@inheritDoc} */
  252.     @Override
  253.     public Cdm build() {
  254.         // CDM KVN file lack a DATA_STOP keyword, hence we can't call finalizeData()
  255.         // automatically before the end of the file
  256.         finalizeData();
  257.         if (userDefinedBlock != null && userDefinedBlock.getParameters().isEmpty()) {
  258.             userDefinedBlock = null;
  259.         }
  260.         final Cdm file = new Cdm(header, segments, getConventions(), getDataContext());
  261.         return file;
  262.     }

  263.     /** Add a general comment.
  264.      * @param comment comment to add
  265.      * @return always return true
  266.      */
  267.     boolean addGeneralComment(final String comment) {
  268.         return commentsBlock.addComment(comment);

  269.     }

  270.     /** Manage relative metadata section.
  271.      * @param starting if true, parser is entering the section
  272.      * otherwise it is leaving the section
  273.      * @return always return true
  274.      */
  275.     boolean manageRelativeMetadataSection(final boolean starting) {
  276.         anticipateNext(starting ? this::processMetadataToken : structureProcessor);
  277.         return true;
  278.     }

  279.     /** Manage relative metadata state vector section.
  280.      * @param starting if true, parser is entering the section
  281.      * otherwise it is leaving the section
  282.      * @return always return true
  283.      */
  284.     boolean manageRelativeStateVectorSection(final boolean starting) {
  285.         anticipateNext(this::processMetadataToken);
  286.         return true;
  287.     }

  288.     /** Manage OD parameters section.
  289.      * @param starting if true, parser is entering the section
  290.      * otherwise it is leaving the section
  291.      * @return always return true
  292.      */
  293.     boolean manageODParametersSection(final boolean starting) {
  294.         commentsBlock.refuseFurtherComments();
  295.         anticipateNext(starting ? this::processODParamToken : structureProcessor);
  296.         return true;
  297.     }

  298.     /** Manage additional parameters section.
  299.      * @param starting if true, parser is entering the section
  300.      * otherwise it is leaving the section
  301.      * @return always return true
  302.      */
  303.     boolean manageAdditionalParametersSection(final boolean starting) {
  304.         commentsBlock.refuseFurtherComments();
  305.         anticipateNext(starting ? this::processAdditionalParametersToken : structureProcessor);
  306.         return true;
  307.     }

  308.     /** Manage state vector section.
  309.      * @param starting if true, parser is entering the section
  310.      * otherwise it is leaving the section
  311.      * @return always return true
  312.      */
  313.     boolean manageStateVectorSection(final boolean starting) {
  314.         commentsBlock.refuseFurtherComments();
  315.         anticipateNext(starting ? this::processStateVectorToken : structureProcessor);
  316.         return true;
  317.     }

  318.     /** Manage general covariance section in XML file.
  319.      * @param starting if true, parser is entering the section
  320.      * otherwise it is leaving the section
  321.      * @return always return true
  322.      */
  323.     boolean manageXmlGeneralCovarianceSection(final boolean starting) {
  324.         commentsBlock.refuseFurtherComments();

  325.         if (starting) {
  326.             if (metadata.getAltCovType() == null) {
  327.                 anticipateNext(this::processCovMatrixToken);
  328.             } else {
  329.                 if (Double.isNaN(covMatrix.getCrr())) {
  330.                     // First, handle mandatory RTN covariance section
  331.                     anticipateNext(this::processCovMatrixToken);
  332.                 } else if ( metadata.getAltCovType() == AltCovarianceType.XYZ && xyzCovMatrix == null ||
  333.                                 metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3 && sig3eigvec3 == null ) {
  334.                     // Second, add the alternate covariance if provided
  335.                     anticipateNext(this::processAltCovarianceToken);
  336.                 } else if (additionalCovMetadata == null) {
  337.                     // Third, process the additional covariance metadata
  338.                     anticipateNext(this::processAdditionalCovMetadataToken);
  339.                 }
  340.             }
  341.         } else {
  342.             anticipateNext(structureProcessor);
  343.         }

  344.         return true;
  345.     }

  346.     /** Manage user-defined parameters section.
  347.      * @param starting if true, parser is entering the section
  348.      * otherwise it is leaving the section
  349.      * @return always return true
  350.      */
  351.     boolean manageUserDefinedParametersSection(final boolean starting) {
  352.         commentsBlock.refuseFurtherComments();
  353.         if (starting) {
  354.             if (userDefinedBlock == null) {
  355.                 // this is the first (and unique) user-defined parameters block, we need to allocate the container
  356.                 userDefinedBlock = new UserDefined();
  357.             }
  358.             anticipateNext(this::processUserDefinedToken);
  359.         } else {
  360.             anticipateNext(structureProcessor);
  361.         }
  362.         return true;
  363.     }

  364.     /** Process one metadata token.
  365.      * @param token token to process
  366.      * @return true if token was processed, false otherwise
  367.      */
  368.     private boolean processMetadataToken(final ParseToken token) {
  369.         if (isDatafinished && getFileFormat() != FileFormat.XML) {
  370.             finalizeData();
  371.             isDatafinished = false;
  372.         }
  373.         if (metadata == null) {
  374.             // CDM KVN file lack a META_START keyword, hence we can't call prepareMetadata()
  375.             // automatically before the first metadata token arrives
  376.             prepareMetadata();
  377.         }
  378.         inMetadata();

  379.         // There can be a COMMENT key at the beginning of relative metadata, but as the relative
  380.         // metadata are processed in the same try and catch loop than metadata because Orekit is
  381.         // build to read metadata and then data (and not relative metadata), it would be problematic
  382.         // to make relative metadata extends comments container(because of the COMMENTS in the middle
  383.         // of relativemetadata and metadata section. Indeed, as said in {@link
  384.         // #CommentsContainer} COMMENT should only be at the beginning of sections but in this case
  385.         // there is a comment at the beginning corresponding to the relative metadata comment
  386.         // and 1 in the middle for object 1 metadata and one further for object 2 metadata. That
  387.         // is why this special syntax was used and initializes the relative metadata COMMENT once
  388.         // at the beginning as relative metadata is not a comment container
  389.         if (COMMENT.equals(token.getName()) && doRelativeMetadata ) {
  390.             if (token.getType() == TokenType.ENTRY) {
  391.                 relativeMetadata.addComment(token.getContentAsNormalizedString());
  392.                 return true;
  393.             }
  394.         }
  395.         doRelativeMetadata = false;

  396.         try {
  397.             return token.getName() != null &&
  398.                    CdmRelativeMetadataKey.valueOf(token.getName()).process(token, context, relativeMetadata);
  399.         } catch (IllegalArgumentException iaeM) {
  400.             try {
  401.                 return MetadataKey.valueOf(token.getName()).process(token, context, metadata);
  402.             } catch (IllegalArgumentException iaeD) {
  403.                 try {
  404.                     return CdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
  405.                 } catch (IllegalArgumentException iaeC) {
  406.                     // token has not been recognized
  407.                     return false;
  408.                 }
  409.             }
  410.         }
  411.     }

  412.     /** Process one XML data substructure token.
  413.      * @param token token to process
  414.      * @return true if token was processed, false otherwise
  415.      */
  416.     private boolean processXmlSubStructureToken(final ParseToken token) {

  417.         // As no relativemetadata token exists in the structure processor and as RelativeMetadata keys are
  418.         // processed in the same try and catch loop in processMetadatatoken as CdmMetadata keys, if the relativemetadata
  419.         // token is read it should be as if the token was equal to metadata to start to initialize relative metadata
  420.         // and metadata and to go in the processMetadataToken try and catch loop. The following relativemetadata
  421.         // stop should be ignored to stay in the processMetadataToken try and catch loop and the following metadata
  422.         // start also ignored to stay in the processMetadataToken try and catch loop. Then arrives the end of metadata
  423.         // so we call structure processor with metadata stop. This distinction of cases is useful for relativemetadata
  424.         // block followed by metadata block for object 1 and also useful to only close metadata block for object 2.
  425.         // The metadata start for object 2 is processed by structureProcessor
  426.         if (METADATA.equals(token.getName()) && TokenType.START.equals(token.getType()) ||
  427.             RELATIVEMETADATA.equals(token.getName()) && TokenType.STOP.equals(token.getType())) {
  428.             anticipateNext(this::processMetadataToken);
  429.             return true;

  430.         } else if (RELATIVEMETADATA.equals(token.getName()) && TokenType.START.equals(token.getType()) ||
  431.                    METADATA.equals(token.getName()) && TokenType.STOP.equals(token.getType())) {
  432.             final ParseToken replaceToken = new ParseToken(token.getType(), METADATA,
  433.                                       null, token.getUnits(), token.getLineNumber(), token.getFileName());

  434.             return structureProcessor.processToken(replaceToken);

  435.         } else {

  436.             // Relative metadata COMMENT and metadata COMMENT should not be read by XmlSubStructureKey that
  437.             // is why 2 cases are distinguished here : the COMMENT for relative metadata and the COMMENT
  438.             // for metadata.
  439.             if (commentsBlock == null && COMMENT.equals(token.getName())) {

  440.                 // COMMENT adding for Relative Metadata in XML
  441.                 if (doRelativeMetadata) {
  442.                     if (token.getType() == TokenType.ENTRY) {
  443.                         relativeMetadata.addComment(token.getContentAsNormalizedString());
  444.                         doRelativeMetadata = false;
  445.                         return true;

  446.                     } else {
  447.                         // if the token Type is still not ENTRY we return true as at the next step
  448.                         // it will be ENTRY ad we will be able to store the comment (similar treatment
  449.                         // as OD parameter or Additional parameter or State Vector ... COMMENT treatment.)
  450.                         return true;
  451.                     }
  452.                 }

  453.                 // COMMENT adding for Metadata in XML
  454.                 if (!doRelativeMetadata) {
  455.                     if (token.getType() == TokenType.ENTRY) {
  456.                         metadata.addComment(token.getContentAsNormalizedString());
  457.                         return true;

  458.                     } else {
  459.                         // same as above
  460.                         return true;
  461.                     }
  462.                 }
  463.             }

  464.             // to treat XmlSubStructureKey keys ( OD parameters, relative Metadata ...)
  465.             try {
  466.                 return token.getName() != null && !doRelativeMetadata &&
  467.                        XmlSubStructureKey.valueOf(token.getName()).process(token, this);
  468.             } catch (IllegalArgumentException iae) {
  469.                 // token has not been recognized
  470.                 return false;
  471.             }
  472.         }
  473.     }

  474.     /** Process one comment token.
  475.      * @param token token to process
  476.      * @return true if token was processed, false otherwise
  477.      */
  478.     private boolean processGeneralCommentToken(final ParseToken token) {
  479.         if (commentsBlock == null) {
  480.             // CDM KVN file lack a META_STOP keyword, hence we can't call finalizeMetadata()
  481.             // automatically before the first data token arrives
  482.             finalizeMetadata();
  483.             // CDM KVN file lack a DATA_START keyword, hence we can't call prepareData()
  484.             // automatically before the first data token arrives
  485.             prepareData();
  486.         }
  487.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processODParamToken);
  488.         if (COMMENT.equals(token.getName()) && commentsBlock.acceptComments()) {
  489.             if (token.getType() == TokenType.ENTRY) {
  490.                 commentsBlock.addComment(token.getContentAsNormalizedString());
  491.             }
  492.             // in order to be able to differentiate general data comments and next block comment (OD parameters if not empty)
  493.             // only 1 line comment is allowed for general data comment.
  494.             commentsBlock.refuseFurtherComments();
  495.             return true;
  496.         } else {
  497.             return false;
  498.         }
  499.     }

  500.     /** Process one od parameter data token.
  501.      * @param token token to process
  502.      * @return true if token was processed, false otherwise
  503.      */
  504.     private boolean processODParamToken(final ParseToken token) {
  505.         if (odParameters == null) {
  506.             odParameters = new ODParameters();
  507.         }
  508.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAdditionalParametersToken);
  509.         try {
  510.             return token.getName() != null &&
  511.                    ODParametersKey.valueOf(token.getName()).process(token, context, odParameters);
  512.         } catch (IllegalArgumentException iae) {
  513.             // token has not been recognized
  514.             return false;
  515.         }
  516.     }

  517.     /** Process one additional parameter data token.
  518.      * @param token token to process
  519.      * @return true if token was processed, false otherwise
  520.      */
  521.     private boolean processAdditionalParametersToken(final ParseToken token) {
  522.         if (addParameters == null) {
  523.             addParameters = new AdditionalParameters();
  524.         }
  525.         if (moveCommentsIfEmpty(odParameters, addParameters)) {
  526.             // get rid of the empty logical block
  527.             odParameters = null;
  528.         }
  529.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processStateVectorToken);
  530.         try {
  531.             return token.getName() != null &&
  532.                    AdditionalParametersKey.valueOf(token.getName()).process(token, context, addParameters);
  533.         } catch (IllegalArgumentException iae) {
  534.             // token has not been recognized
  535.             return false;
  536.         }
  537.     }

  538.     /** Process one state vector data token.
  539.      * @param token token to process
  540.      * @return true if token was processed, false otherwise
  541.      */
  542.     private boolean processStateVectorToken(final ParseToken token) {
  543.         if (moveCommentsIfEmpty(addParameters, stateVector)) {
  544.             // get rid of the empty logical block
  545.             addParameters = null;
  546.         }
  547.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processCovMatrixToken);
  548.         try {
  549.             return token.getName() != null &&
  550.                    StateVectorKey.valueOf(token.getName()).process(token, context, stateVector);
  551.         } catch (IllegalArgumentException iae) {
  552.             // token has not been recognized
  553.             return false;
  554.         }
  555.     }

  556.     /** Process covariance matrix data token.
  557.      * @param token token to process
  558.      * @return true if token was processed, false otherwise
  559.      */
  560.     private boolean processCovMatrixToken(final ParseToken token) {

  561.         if (moveCommentsIfEmpty(stateVector, covMatrix)) {
  562.             // get rid of the empty logical block
  563.             stateVector = null;
  564.         }

  565.         if (metadata.getAltCovType() == null) {
  566.             anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
  567.         } else {
  568.             anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAltCovarianceToken);
  569.         }

  570.         isDatafinished = true;
  571.         try {
  572.             return token.getName() != null &&
  573.                    RTNCovarianceKey.valueOf(token.getName()).process(token, context, covMatrix);
  574.         } catch (IllegalArgumentException iae) {
  575.             // token has not been recognized
  576.             return false;
  577.         }
  578.     }

  579.     /** Process alternate covariance data token.
  580.      * @param token token to process
  581.      * @return true if token was processed, false otherwise
  582.      */
  583.     private boolean processAltCovarianceToken(final ParseToken token) {

  584.         // Covariance is provided in XYZ
  585.         if (metadata.getAltCovType() == AltCovarianceType.XYZ && xyzCovMatrix == null) {
  586.             xyzCovMatrix = new XYZCovariance(true);

  587.             if (moveCommentsIfEmpty(covMatrix, xyzCovMatrix)) {
  588.                 // get rid of the empty logical block
  589.                 covMatrix = null;
  590.             }
  591.         }
  592.         // Covariance is provided in CSIG3EIGVEC3 format
  593.         if (metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3 && sig3eigvec3 == null) {
  594.             sig3eigvec3 = new SigmaEigenvectorsCovariance(true);

  595.             if (moveCommentsIfEmpty(covMatrix, sig3eigvec3)) {
  596.                 // get rid of the empty logical block
  597.                 covMatrix = null;
  598.             }
  599.         }


  600.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAdditionalCovMetadataToken);
  601.         try {

  602.             if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.XYZ) {

  603.                 return token.getName() != null &&
  604.                            XYZCovarianceKey.valueOf(token.getName()).process(token, context, xyzCovMatrix);

  605.             } else if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3) {

  606.                 return token.getName() != null &&
  607.                            SigmaEigenvectorsCovarianceKey.valueOf(token.getName()).process(token, context, sig3eigvec3);

  608.             } else {

  609.                 // token has not been recognized
  610.                 return false;

  611.             }

  612.         } catch (IllegalArgumentException iae) {
  613.             // token has not been recognized
  614.             return false;
  615.         }
  616.     }

  617.     /** Process additional covariance metadata token.
  618.      * @param token token to process
  619.      * @return true if token was processed, false otherwise
  620.      */
  621.     private boolean processAdditionalCovMetadataToken(final ParseToken token) {

  622.         // Additional covariance metadata
  623.         if ( additionalCovMetadata == null) {
  624.             additionalCovMetadata = new AdditionalCovarianceMetadata();
  625.         }

  626.         if (moveCommentsIfEmpty(xyzCovMatrix, additionalCovMetadata)) {
  627.             // get rid of the empty logical block
  628.             xyzCovMatrix = null;
  629.         } else if (moveCommentsIfEmpty(sig3eigvec3, additionalCovMetadata)) {
  630.             // get rid of the empty logical block
  631.             sig3eigvec3 = null;
  632.         }

  633.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processUserDefinedToken);
  634.         try {
  635.             return token.getName() != null &&
  636.                            AdditionalCovarianceMetadataKey.valueOf(token.getName()).process(token, context, additionalCovMetadata);
  637.         } catch (IllegalArgumentException iae) {
  638.             // token has not been recognized
  639.             return false;
  640.         }
  641.     }

  642.     /** Process one user-defined parameter data token.
  643.      * @param token token to process
  644.      * @return true if token was processed, false otherwise
  645.      */
  646.     private boolean processUserDefinedToken(final ParseToken token) {

  647.         if (userDefinedBlock == null) {
  648.             userDefinedBlock = new UserDefined();
  649.         }

  650.         if (moveCommentsIfEmpty(additionalCovMetadata, userDefinedBlock)) {
  651.             // get rid of the empty logical block
  652.             additionalCovMetadata = null;
  653.         }

  654.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);

  655.         if (COMMENT.equals(token.getName())) {
  656.             return token.getType() == TokenType.ENTRY ? userDefinedBlock.addComment(token.getContentAsNormalizedString()) : true;
  657.         } else if (token.getName().startsWith(UserDefined.USER_DEFINED_PREFIX)) {
  658.             if (token.getType() == TokenType.ENTRY) {
  659.                 userDefinedBlock.addEntry(token.getName().substring(UserDefined.USER_DEFINED_PREFIX.length()),
  660.                                           token.getContentAsNormalizedString());
  661.             }
  662.             return true;
  663.         } else {
  664.             // the token was not processed
  665.             return false;
  666.         }
  667.     }


  668.     /** Move comments from one empty logical block to another logical block.
  669.      * @param origin origin block
  670.      * @param destination destination block
  671.      * @return true if origin block was empty
  672.      */
  673.     private boolean moveCommentsIfEmpty(final CommentsContainer origin, final CommentsContainer destination) {
  674.         if (origin != null && origin.acceptComments()) {
  675.             // origin block is empty, move the existing comments
  676.             for (final String comment : origin.getComments()) {
  677.                 destination.addComment(comment);
  678.             }
  679.             return true;
  680.         } else {
  681.             return false;
  682.         }
  683.     }

  684. }