CdmParser.java

  1. /* Copyright 2002-2022 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 org.orekit.data.DataContext;
  21. import org.orekit.files.ccsds.definitions.TimeSystem;
  22. import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
  23. import org.orekit.files.ccsds.ndm.odm.UserDefined;
  24. import org.orekit.files.ccsds.section.CommentsContainer;
  25. import org.orekit.files.ccsds.section.KvnStructureProcessingState;
  26. import org.orekit.files.ccsds.section.MetadataKey;
  27. import org.orekit.files.ccsds.section.XmlStructureProcessingState;
  28. import org.orekit.files.ccsds.utils.ContextBinding;
  29. import org.orekit.files.ccsds.utils.FileFormat;
  30. import org.orekit.files.ccsds.utils.lexical.ParseToken;
  31. import org.orekit.files.ccsds.utils.lexical.TokenType;
  32. import org.orekit.files.ccsds.utils.parsing.AbstractConstituentParser;
  33. import org.orekit.files.ccsds.utils.parsing.ProcessingState;
  34. import org.orekit.utils.IERSConventions;

  35. /**
  36.  * Base class for Conjunction Data Message parsers.
  37.  * <p>
  38.  * Note than starting with Orekit 11.0, CCSDS message parsers are
  39.  * mutable objects that gather the data being parsed, until the
  40.  * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
  41.  * parseMessage} method has returned. This implies that parsers
  42.  * should <em>not</em> be used in a multi-thread context. The recommended
  43.  * way to use parsers is to either dedicate one parser for each message
  44.  * and drop it afterwards, or to use a single-thread loop.
  45.  * </p>
  46.  * @author Melina Vanel
  47.  * @since 11.2
  48.  */
  49. public class CdmParser extends AbstractConstituentParser<Cdm, CdmParser> {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

  107.     /** {@inheritDoc} */
  108.     @Override
  109.     public CdmHeader getHeader() {
  110.         return header;
  111.     }

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

  137.     /** {@inheritDoc} */
  138.     @Override
  139.     public boolean prepareHeader() {
  140.         anticipateNext(new CdmHeaderProcessingState(this));
  141.         return true;
  142.     }

  143.     /** {@inheritDoc} */
  144.     @Override
  145.     public boolean inHeader() {
  146.         anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processMetadataToken);
  147.         return true;
  148.     }

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

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

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

  181.     /** {@inheritDoc} */
  182.     @Override
  183.     public boolean inMetadata() {
  184.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processGeneralCommentToken);
  185.         return true;
  186.     }

  187.     /** {@inheritDoc} */
  188.     @Override
  189.     public boolean finalizeMetadata() {
  190.         metadata.validate(header.getFormatVersion());
  191.         relativeMetadata.validate();
  192.         anticipateNext(structureProcessor);
  193.         return true;
  194.     }

  195.     /** {@inheritDoc} */
  196.     @Override
  197.     public boolean prepareData() {
  198.         // stateVector and RTNCovariance blocks are 2 mandatory data blocks
  199.         stateVector = new StateVector();
  200.         covMatrix = new RTNCovariance();

  201.         // initialize comments block for general data comments
  202.         commentsBlock = new CommentsContainer();
  203.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processGeneralCommentToken);
  204.         return true;
  205.     }

  206.     /** {@inheritDoc} */
  207.     @Override
  208.     public boolean inData() {
  209.         return true;
  210.     }

  211.     /** {@inheritDoc} */
  212.     @Override
  213.     public boolean finalizeData() {
  214.         // call at the and of data block for object 1 or 2
  215.         if (metadata != null) {

  216.             CdmData data = new CdmData(commentsBlock, odParameters, addParameters,
  217.                                              stateVector, covMatrix, additionalCovMetadata);

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

  225.             data.validate(header.getFormatVersion());
  226.             segments.add(new CdmSegment(metadata, data));

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

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

  259.     /** Add a general comment.
  260.      * @param comment comment to add
  261.      * @return always return true
  262.      */
  263.     boolean addGeneralComment(final String comment) {
  264.         return commentsBlock.addComment(comment);

  265.     }

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

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

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

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

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

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

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

  340.         return true;
  341.     }

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

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

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

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

  408.     /** Process one XML data substructure token.
  409.      * @param token token to process
  410.      * @return true if token was processed, false otherwise
  411.      */
  412.     private boolean processXmlSubStructureToken(final ParseToken token) {

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

  426.         } else if (RELATIVEMETADATA.equals(token.getName()) && TokenType.START.equals(token.getType()) ||
  427.                    METADATA.equals(token.getName()) && TokenType.STOP.equals(token.getType())) {
  428.             final ParseToken replaceToken = new ParseToken(token.getType(), METADATA,
  429.                                       null, token.getUnits(), token.getLineNumber(), token.getFileName());

  430.             return structureProcessor.processToken(replaceToken);

  431.         } else {

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

  436.                 // COMMENT adding for Relative Metadata in XML
  437.                 if (doRelativeMetadata) {
  438.                     if (token.getType() == TokenType.ENTRY) {
  439.                         relativeMetadata.addComment(token.getContentAsNormalizedString());
  440.                         doRelativeMetadata = false;
  441.                         return true;

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

  449.                 // COMMENT adding for Metadata in XML
  450.                 if (!doRelativeMetadata) {
  451.                     if (token.getType() == TokenType.ENTRY) {
  452.                         metadata.addComment(token.getContentAsNormalizedString());
  453.                         return true;

  454.                     } else {
  455.                         // same as above
  456.                         return true;
  457.                     }
  458.                 }
  459.             }

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

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

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

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

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

  552.     /** Process covariance matrix data token.
  553.      * @param token token to process
  554.      * @return true if token was processed, false otherwise
  555.      */
  556.     private boolean processCovMatrixToken(final ParseToken token) {

  557.         if (moveCommentsIfEmpty(stateVector, covMatrix)) {
  558.             // get rid of the empty logical block
  559.             stateVector = null;
  560.         }

  561.         if (metadata.getAltCovType() == null) {
  562.             anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
  563.         } else {
  564.             anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAltCovarianceToken);
  565.         }

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

  575.     /** Process alternate covariance data token.
  576.      * @param token token to process
  577.      * @return true if token was processed, false otherwise
  578.      */
  579.     private boolean processAltCovarianceToken(final ParseToken token) {

  580.         // Covariance is provided in XYZ
  581.         if (metadata.getAltCovType() == AltCovarianceType.XYZ && xyzCovMatrix == null) {
  582.             xyzCovMatrix = new XYZCovariance(true);

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

  591.             if (moveCommentsIfEmpty(covMatrix, sig3eigvec3)) {
  592.                 // get rid of the empty logical block
  593.                 covMatrix = null;
  594.             }
  595.         }


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

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

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

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

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

  604.             } else {

  605.                 // token has not been recognized
  606.                 return false;

  607.             }

  608.         } catch (IllegalArgumentException iae) {
  609.             // token has not been recognized
  610.             return false;
  611.         }
  612.     }

  613.     /** Process additional covariance metadata token.
  614.      * @param token token to process
  615.      * @return true if token was processed, false otherwise
  616.      */
  617.     private boolean processAdditionalCovMetadataToken(final ParseToken token) {

  618.         // Additional covariance metadata
  619.         if ( additionalCovMetadata == null) {
  620.             additionalCovMetadata = new AdditionalCovarianceMetadata();
  621.         }

  622.         if (moveCommentsIfEmpty(xyzCovMatrix, additionalCovMetadata)) {
  623.             // get rid of the empty logical block
  624.             xyzCovMatrix = null;
  625.         } else if (moveCommentsIfEmpty(sig3eigvec3, additionalCovMetadata)) {
  626.             // get rid of the empty logical block
  627.             sig3eigvec3 = null;
  628.         }

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

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

  643.         if (userDefinedBlock == null) {
  644.             userDefinedBlock = new UserDefined();
  645.         }

  646.         if (moveCommentsIfEmpty(additionalCovMetadata, userDefinedBlock)) {
  647.             // get rid of the empty logical block
  648.             additionalCovMetadata = null;
  649.         }

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

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


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

  680. }