OcmParser.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.odm.ocm;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.regex.Pattern;

  23. import org.orekit.data.DataContext;
  24. import org.orekit.data.DataSource;
  25. import org.orekit.errors.OrekitException;
  26. import org.orekit.errors.OrekitIllegalArgumentException;
  27. import org.orekit.errors.OrekitMessages;
  28. import org.orekit.files.ccsds.ndm.odm.OdmParser;
  29. import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
  30. import org.orekit.files.ccsds.ndm.odm.OdmMetadataKey;
  31. import org.orekit.files.ccsds.ndm.odm.UserDefined;
  32. import org.orekit.files.ccsds.section.Header;
  33. import org.orekit.files.ccsds.section.HeaderProcessingState;
  34. import org.orekit.files.ccsds.section.KvnStructureProcessingState;
  35. import org.orekit.files.ccsds.section.MetadataKey;
  36. import org.orekit.files.ccsds.section.Segment;
  37. import org.orekit.files.ccsds.section.XmlStructureProcessingState;
  38. import org.orekit.files.ccsds.utils.ContextBinding;
  39. import org.orekit.files.ccsds.utils.FileFormat;
  40. import org.orekit.files.ccsds.utils.lexical.ParseToken;
  41. import org.orekit.files.ccsds.utils.lexical.TokenType;
  42. import org.orekit.files.ccsds.utils.lexical.UserDefinedXmlTokenBuilder;
  43. import org.orekit.files.ccsds.utils.lexical.XmlTokenBuilder;
  44. import org.orekit.files.ccsds.utils.parsing.ProcessingState;
  45. import org.orekit.files.general.EphemerisFileParser;
  46. import org.orekit.time.AbsoluteDate;
  47. import org.orekit.utils.IERSConventions;
  48. import org.orekit.utils.units.Unit;

  49. /** A parser for the CCSDS OCM (Orbit Comprehensive Message).
  50.  * <p>
  51.  * Note than starting with Orekit 11.0, CCSDS message parsers are
  52.  * mutable objects that gather the data being parsed, until the
  53.  * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
  54.  * parseMessage} method has returned. This implies that parsers
  55.  * should <em>not</em> be used in a multi-thread context. The recommended
  56.  * way to use parsers is to either dedicate one parser for each message
  57.  * and drop it afterwards, or to use a single-thread loop.
  58.  * </p>
  59.  * @author Luc Maisonobe
  60.  * @since 11.0
  61.  */
  62. public class OcmParser extends OdmParser<Ocm, OcmParser> implements EphemerisFileParser<Ocm> {

  63.     /** Pattern for splitting strings at blanks. */
  64.     private static final Pattern SPLIT_AT_BLANKS = Pattern.compile("\\s+");

  65.     /** File header. */
  66.     private Header header;

  67.     /** Metadata for current observation block. */
  68.     private OcmMetadata metadata;

  69.     /** Context binding valid for current metadata. */
  70.     private ContextBinding context;

  71.     /** Trajectory state histories logical blocks. */
  72.     private List<TrajectoryStateHistory> trajectoryBlocks;

  73.     /** Current trajectory state metadata. */
  74.     private TrajectoryStateHistoryMetadata currentTrajectoryStateHistoryMetadata;

  75.     /** Current trajectory state time history being read. */
  76.     private List<TrajectoryState> currentTrajectoryStateHistory;

  77.     /** Physical properties logical block. */
  78.     private PhysicalProperties physicBlock;

  79.     /** Covariance logical blocks. */
  80.     private List<CovarianceHistory> covarianceBlocks;

  81.     /** Current covariance metadata. */
  82.     private CovarianceHistoryMetadata currentCovarianceHistoryMetadata;

  83.     /** Current covariance history being read. */
  84.     private List<Covariance> currentCovarianceHistory;

  85.     /** Maneuver logical blocks. */
  86.     private List<ManeuverHistory> maneuverBlocks;

  87.     /** Current maneuver metadata. */
  88.     private ManeuverHistoryMetadata currentManeuverHistoryMetadata;

  89.     /** Current maneuver history being read. */
  90.     private List<Maneuver> currentManeuverHistory;

  91.     /** Perturbations logical block. */
  92.     private Perturbations perturbationsBlock;

  93.     /** Orbit determination logical block. */
  94.     private OrbitDetermination orbitDeterminationBlock;

  95.     /** User defined parameters logical block. */
  96.     private UserDefined userDefinedBlock;

  97.     /** Processor for global message structure. */
  98.     private ProcessingState structureProcessor;

  99.     /**
  100.      * Complete constructor.
  101.      * <p>
  102.      * Calling this constructor directly is not recommended. Users should rather use
  103.      * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildOcmParser()
  104.      * parserBuilder.buildOcmParser()}.
  105.      * </p>
  106.      * @param conventions IERS Conventions
  107.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  108.      * @param dataContext used to retrieve frames, time scales, etc.
  109.      * @param mu gravitational coefficient
  110.      * @param parsedUnitsBehavior behavior to adopt for handling parsed units
  111.      */
  112.     public OcmParser(final IERSConventions conventions, final boolean simpleEOP, final DataContext dataContext,
  113.                      final double mu, final ParsedUnitsBehavior parsedUnitsBehavior) {
  114.         super(Ocm.ROOT, Ocm.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext, null, mu, parsedUnitsBehavior);
  115.     }

  116.     /** {@inheritDoc} */
  117.     @Override
  118.     public Map<String, XmlTokenBuilder> getSpecialXmlElementsBuilders() {

  119.         final Map<String, XmlTokenBuilder> builders = super.getSpecialXmlElementsBuilders();

  120.         // special handling of user-defined parameters
  121.         builders.put(UserDefined.USER_DEFINED_XML_TAG, new UserDefinedXmlTokenBuilder());

  122.         return builders;

  123.     }

  124.     /** {@inheritDoc} */
  125.     @Override
  126.     public Ocm parse(final DataSource source) {
  127.         return parseMessage(source);
  128.     }

  129.     /** {@inheritDoc} */
  130.     @Override
  131.     public Header getHeader() {
  132.         return header;
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override
  136.     public void reset(final FileFormat fileFormat) {
  137.         header                  = new Header(3.0);
  138.         metadata                = null;
  139.         context                 = null;
  140.         trajectoryBlocks        = null;
  141.         physicBlock             = null;
  142.         covarianceBlocks        = null;
  143.         maneuverBlocks          = null;
  144.         perturbationsBlock      = null;
  145.         orbitDeterminationBlock = null;
  146.         userDefinedBlock        = null;
  147.         if (fileFormat == FileFormat.XML) {
  148.             structureProcessor = new XmlStructureProcessingState(Ocm.ROOT, this);
  149.             reset(fileFormat, structureProcessor);
  150.         } else {
  151.             structureProcessor = new KvnStructureProcessingState(this);
  152.             reset(fileFormat, new HeaderProcessingState(this));
  153.         }
  154.     }

  155.     /** {@inheritDoc} */
  156.     @Override
  157.     public boolean prepareHeader() {
  158.         anticipateNext(new HeaderProcessingState(this));
  159.         return true;
  160.     }

  161.     /** {@inheritDoc} */
  162.     @Override
  163.     public boolean inHeader() {
  164.         anticipateNext(structureProcessor);
  165.         return true;
  166.     }

  167.     /** {@inheritDoc} */
  168.     @Override
  169.     public boolean finalizeHeader() {
  170.         header.validate(header.getFormatVersion());
  171.         return true;
  172.     }

  173.     /** {@inheritDoc} */
  174.     @Override
  175.     public boolean prepareMetadata() {
  176.         if (metadata != null) {
  177.             return false;
  178.         }
  179.         metadata  = new OcmMetadata(getDataContext());
  180.         context   = new ContextBinding(this::getConventions, this::isSimpleEOP, this::getDataContext,
  181.                                        this::getParsedUnitsBehavior, metadata::getEpochT0, metadata::getTimeSystem,
  182.                                        metadata::getSclkOffsetAtEpoch, metadata::getSclkSecPerSISec);
  183.         anticipateNext(this::processMetadataToken);
  184.         return true;
  185.     }

  186.     /** {@inheritDoc} */
  187.     @Override
  188.     public boolean inMetadata() {
  189.         anticipateNext(structureProcessor);
  190.         return true;
  191.     }

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

  199.     /** {@inheritDoc} */
  200.     @Override
  201.     public boolean prepareData() {
  202.         anticipateNext(this::processDataSubStructureToken);
  203.         return true;
  204.     }

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

  210.     /** {@inheritDoc} */
  211.     @Override
  212.     public boolean finalizeData() {
  213.         // fix gravitational parameter now that all sections have been completed
  214.         final List<TrajectoryStateHistory> old = trajectoryBlocks;
  215.         if (old != null) {
  216.             trajectoryBlocks = new ArrayList<>(old.size());
  217.             for (final TrajectoryStateHistory osh : old) {
  218.                 trajectoryBlocks.add(new TrajectoryStateHistory(osh.getMetadata(), osh.getTrajectoryStates(), getSelectedMu()));
  219.             }
  220.         }
  221.         return true;
  222.     }

  223.     /** Manage trajectory state history section.
  224.      * @param starting if true, parser is entering the section
  225.      * otherwise it is leaving the section
  226.      * @return always return true
  227.      */
  228.     boolean manageTrajectoryStateSection(final boolean starting) {
  229.         if (starting) {
  230.             if (trajectoryBlocks == null) {
  231.                 // this is the first trajectory block, we need to allocate the container
  232.                 trajectoryBlocks = new ArrayList<>();
  233.             }
  234.             currentTrajectoryStateHistoryMetadata = new TrajectoryStateHistoryMetadata(metadata.getEpochT0(),
  235.                                                                                        getDataContext());
  236.             currentTrajectoryStateHistory         = new ArrayList<>();
  237.             anticipateNext(this::processTrajectoryStateToken);
  238.         } else {
  239.             anticipateNext(structureProcessor);
  240.             if (currentTrajectoryStateHistoryMetadata.getCenter().getBody() != null) {
  241.                 setMuCreated(currentTrajectoryStateHistoryMetadata.getCenter().getBody().getGM());
  242.             }
  243.             // we temporarily set gravitational parameter to NaN,
  244.             // as we may get a proper one in the perturbations section
  245.             trajectoryBlocks.add(new TrajectoryStateHistory(currentTrajectoryStateHistoryMetadata,
  246.                                                             currentTrajectoryStateHistory,
  247.                                                             Double.NaN));
  248.         }
  249.         return true;
  250.     }

  251.     /** Manage physical properties section.
  252.      * @param starting if true, parser is entering the section
  253.      * otherwise it is leaving the section
  254.      * @return always return true
  255.      */
  256.     boolean managePhysicalPropertiesSection(final boolean starting) {
  257.         if (starting) {
  258.             if (physicBlock == null) {
  259.                 // this is the first (and unique) physical properties block, we need to allocate the container
  260.                 physicBlock = new PhysicalProperties(metadata.getEpochT0());
  261.             }
  262.             anticipateNext(this::processPhysicalPropertyToken);
  263.         } else {
  264.             anticipateNext(structureProcessor);
  265.         }
  266.         return true;
  267.     }

  268.     /** Manage covariance history section.
  269.      * @param starting if true, parser is entering the section
  270.      * otherwise it is leaving the section
  271.      * @return always return true
  272.      */
  273.     boolean manageCovarianceHistorySection(final boolean starting) {
  274.         if (starting) {
  275.             if (covarianceBlocks == null) {
  276.                 // this is the first covariance block, we need to allocate the container
  277.                 covarianceBlocks = new ArrayList<>();
  278.             }
  279.             currentCovarianceHistoryMetadata = new CovarianceHistoryMetadata(metadata.getEpochT0());
  280.             currentCovarianceHistory         = new ArrayList<>();
  281.             anticipateNext(this::processCovarianceToken);
  282.         } else {
  283.             anticipateNext(structureProcessor);
  284.             covarianceBlocks.add(new CovarianceHistory(currentCovarianceHistoryMetadata,
  285.                                                        currentCovarianceHistory));
  286.             currentCovarianceHistoryMetadata = null;
  287.             currentCovarianceHistory         = null;
  288.         }
  289.         return true;
  290.     }

  291.     /** Manage maneuvers section.
  292.      * @param starting if true, parser is entering the section
  293.      * otherwise it is leaving the section
  294.      * @return always return true
  295.      */
  296.     boolean manageManeuversSection(final boolean starting) {
  297.         if (starting) {
  298.             if (maneuverBlocks == null) {
  299.                 // this is the first maneuver block, we need to allocate the container
  300.                 maneuverBlocks = new ArrayList<>();
  301.             }
  302.             currentManeuverHistoryMetadata = new ManeuverHistoryMetadata(metadata.getEpochT0());
  303.             currentManeuverHistory         = new ArrayList<>();
  304.             anticipateNext(this::processManeuverToken);
  305.         } else {
  306.             anticipateNext(structureProcessor);
  307.             maneuverBlocks.add(new ManeuverHistory(currentManeuverHistoryMetadata,
  308.                                                    currentManeuverHistory));
  309.             currentManeuverHistoryMetadata = null;
  310.             currentManeuverHistory         = null;
  311.         }
  312.         return true;
  313.     }

  314.     /** Manage perturbation parameters section.
  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 managePerturbationParametersSection(final boolean starting) {
  320.         if (starting) {
  321.             if (perturbationsBlock == null) {
  322.                 // this is the first (and unique) perturbations parameters block, we need to allocate the container
  323.                 perturbationsBlock = new Perturbations(context.getDataContext().getCelestialBodies());
  324.             }
  325.             anticipateNext(this::processPerturbationToken);
  326.         } else {
  327.             anticipateNext(structureProcessor);
  328.         }
  329.         return true;
  330.     }

  331.     /** Manage orbit determination section.
  332.      * @param starting if true, parser is entering the section
  333.      * otherwise it is leaving the section
  334.      * @return always return true
  335.      */
  336.     boolean manageOrbitDeterminationSection(final boolean starting) {
  337.         if (starting) {
  338.             if (orbitDeterminationBlock == null) {
  339.                 // this is the first (and unique) orbit determination block, we need to allocate the container
  340.                 orbitDeterminationBlock = new OrbitDetermination();
  341.             }
  342.             anticipateNext(this::processOrbitDeterminationToken);
  343.         } else {
  344.             anticipateNext(structureProcessor);
  345.         }
  346.         return true;
  347.     }

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

  365.     /** {@inheritDoc} */
  366.     @Override
  367.     public Ocm build() {
  368.         // OCM KVN file lack a DATA_STOP keyword, hence we can't call finalizeData()
  369.         // automatically before the end of the file
  370.         finalizeData();
  371.         if (userDefinedBlock != null && userDefinedBlock.getParameters().isEmpty()) {
  372.             userDefinedBlock = null;
  373.         }
  374.         if (perturbationsBlock != null) {
  375.             // this may be Double.NaN, but it will be handled correctly
  376.             setMuParsed(perturbationsBlock.getGm());
  377.         }
  378.         final OcmData data = new OcmData(trajectoryBlocks, physicBlock, covarianceBlocks,
  379.                                          maneuverBlocks, perturbationsBlock,
  380.                                          orbitDeterminationBlock, userDefinedBlock);
  381.         data.validate(header.getFormatVersion());
  382.         return new Ocm(header, Collections.singletonList(new Segment<>(metadata, data)),
  383.                            getConventions(), getDataContext(), getSelectedMu());
  384.     }

  385.     /** Process one metadata token.
  386.      * @param token token to process
  387.      * @return true if token was processed, false otherwise
  388.      */
  389.     private boolean processMetadataToken(final ParseToken token) {
  390.         inMetadata();
  391.         try {
  392.             return token.getName() != null &&
  393.                    MetadataKey.valueOf(token.getName()).process(token, context, metadata);
  394.         } catch (IllegalArgumentException iaeM) {
  395.             try {
  396.                 return OdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
  397.             } catch (IllegalArgumentException iaeD) {
  398.                 try {
  399.                     return OcmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
  400.                 } catch (IllegalArgumentException iaeC) {
  401.                     // token has not been recognized
  402.                     return false;
  403.                 }
  404.             }
  405.         }
  406.     }

  407.     /** Process one data substructure token.
  408.      * @param token token to process
  409.      * @return true if token was processed, false otherwise
  410.      */
  411.     private boolean processDataSubStructureToken(final ParseToken token) {
  412.         try {
  413.             return token.getName() != null &&
  414.                    OcmDataSubStructureKey.valueOf(token.getName()).process(token, this);
  415.         } catch (IllegalArgumentException iae) {
  416.             // token has not been recognized
  417.             return false;
  418.         }
  419.     }

  420.     /** Process one trajectory state history data token.
  421.      * @param token token to process
  422.      * @return true if token was processed, false otherwise
  423.      */
  424.     private boolean processTrajectoryStateToken(final ParseToken token) {
  425.         if (token.getName() != null && !token.getName().equals(Ocm.TRAJ_LINE)) {
  426.             // we are in the section metadata part
  427.             try {
  428.                 return TrajectoryStateHistoryMetadataKey.valueOf(token.getName()).
  429.                        process(token, context, currentTrajectoryStateHistoryMetadata);
  430.             } catch (IllegalArgumentException iae) {
  431.                 // token has not been recognized
  432.                 return false;
  433.             }
  434.         } else {
  435.             // we are in the section data part
  436.             if (currentTrajectoryStateHistory.isEmpty()) {
  437.                 // we are starting the real data section, we can now check metadata is complete
  438.                 currentTrajectoryStateHistoryMetadata.validate(header.getFormatVersion());
  439.                 anticipateNext(this::processDataSubStructureToken);
  440.             }
  441.             if (token.getType() == TokenType.START || token.getType() == TokenType.STOP) {
  442.                 return true;
  443.             }
  444.             try {
  445.                 final String[] fields = SPLIT_AT_BLANKS.split(token.getRawContent().trim());
  446.                 // as TRAJ_UNITS is optional and indeed MUST match type, get them directly from type
  447.                 final List<Unit> units = currentTrajectoryStateHistoryMetadata.getTrajType().getUnits();
  448.                 if (fields.length != units.size() + 1) {
  449.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  450.                                               token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
  451.                 }
  452.                 final AbsoluteDate epoch = context.getTimeSystem().getConverter(context).parse(fields[0]);
  453.                 return currentTrajectoryStateHistory.add(new TrajectoryState(currentTrajectoryStateHistoryMetadata.getTrajType(),
  454.                                                                    epoch, fields, 1, units));
  455.             } catch (NumberFormatException | OrekitIllegalArgumentException e) {
  456.                 throw new OrekitException(e, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  457.                                           token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
  458.             }
  459.         }
  460.     }

  461.     /** Process one physical property data token.
  462.      * @param token token to process
  463.      * @return true if token was processed, false otherwise
  464.      */
  465.     private boolean processPhysicalPropertyToken(final ParseToken token) {
  466.         if (physicBlock == null) {
  467.             physicBlock = new PhysicalProperties(metadata.getEpochT0());
  468.         }
  469.         anticipateNext(this::processDataSubStructureToken);
  470.         try {
  471.             return token.getName() != null &&
  472.                    PhysicalPropertiesKey.valueOf(token.getName()).process(token, context, physicBlock);
  473.         } catch (IllegalArgumentException iae) {
  474.             // token has not been recognized
  475.             return false;
  476.         }
  477.     }

  478.     /** Process one covariance history history data token.
  479.      * @param token token to process
  480.      * @return true if token was processed, false otherwise
  481.      */
  482.     private boolean processCovarianceToken(final ParseToken token) {
  483.         if (token.getName() != null && !token.getName().equals(Ocm.COV_LINE)) {
  484.             // we are in the section metadata part
  485.             try {
  486.                 return CovarianceHistoryMetadataKey.valueOf(token.getName()).
  487.                        process(token, context, currentCovarianceHistoryMetadata);
  488.             } catch (IllegalArgumentException iae) {
  489.                 // token has not been recognized
  490.                 return false;
  491.             }
  492.         } else {
  493.             // we are in the section data part
  494.             if (currentCovarianceHistory.isEmpty()) {
  495.                 // we are starting the real data section, we can now check metadata is complete
  496.                 currentCovarianceHistoryMetadata.validate(header.getFormatVersion());
  497.                 anticipateNext(this::processDataSubStructureToken);
  498.             }
  499.             if (token.getType() == TokenType.START || token.getType() == TokenType.STOP) {
  500.                 return true;
  501.             }
  502.             try {
  503.                 final String[] fields = SPLIT_AT_BLANKS.split(token.getRawContent().trim());
  504.                 final int n = currentCovarianceHistoryMetadata.getCovUnits().size();
  505.                 if (fields.length - 1 != currentCovarianceHistoryMetadata.getCovOrdering().nbElements(n)) {
  506.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  507.                                               token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
  508.                 }
  509.                 currentCovarianceHistory.add(new Covariance(currentCovarianceHistoryMetadata.getCovType(),
  510.                                                             currentCovarianceHistoryMetadata.getCovOrdering(),
  511.                                                             context.getTimeSystem().getConverter(context).parse(fields[0]),
  512.                                                             fields, 1));
  513.                 return true;
  514.             } catch (NumberFormatException nfe) {
  515.                 throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  516.                                           token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
  517.             }
  518.         }
  519.     }

  520.     /** Process one maneuver data token.
  521.      * @param token token to process
  522.      * @return true if token was processed, false otherwise
  523.      */
  524.     private boolean processManeuverToken(final ParseToken token) {
  525.         if (token.getName() != null && !token.getName().equals(Ocm.MAN_LINE)) {
  526.             // we are in the section metadata part
  527.             try {
  528.                 return ManeuverHistoryMetadataKey.valueOf(token.getName()).
  529.                        process(token, context, currentManeuverHistoryMetadata);
  530.             } catch (IllegalArgumentException iae) {
  531.                 // token has not been recognized
  532.                 return false;
  533.             }
  534.         } else {
  535.             // we are in the section data part
  536.             if (currentManeuverHistory.isEmpty()) {
  537.                 // we are starting the real data section, we can now check metadata is complete
  538.                 currentManeuverHistoryMetadata.validate(header.getFormatVersion());
  539.                 anticipateNext(this::processDataSubStructureToken);
  540.             }
  541.             if (token.getType() == TokenType.START || token.getType() == TokenType.STOP) {
  542.                 return true;
  543.             }
  544.             try {
  545.                 final String[] fields = SPLIT_AT_BLANKS.split(token.getRawContent().trim());
  546.                 final List<ManeuverFieldType> types = currentManeuverHistoryMetadata.getManComposition();
  547.                 if (fields.length != types.size()) {
  548.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  549.                                               token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
  550.                 }
  551.                 final Maneuver maneuver = new Maneuver();
  552.                 for (int i = 0; i < fields.length; ++i) {
  553.                     types.get(i).process(fields[i], context, maneuver, token.getLineNumber(), token.getFileName());
  554.                 }
  555.                 currentManeuverHistory.add(maneuver);
  556.                 return true;
  557.             } catch (NumberFormatException nfe) {
  558.                 throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  559.                                           token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
  560.             }
  561.         }
  562.     }

  563.     /** Process one perturbation parameter data token.
  564.      * @param token token to process
  565.      * @return true if token was processed, false otherwise
  566.      */
  567.     private boolean processPerturbationToken(final ParseToken token) {
  568.         anticipateNext(this::processDataSubStructureToken);
  569.         try {
  570.             return token.getName() != null &&
  571.                    PerturbationsKey.valueOf(token.getName()).process(token, context, perturbationsBlock);
  572.         } catch (IllegalArgumentException iae) {
  573.             // token has not been recognized
  574.             return false;
  575.         }
  576.     }

  577.     /** Process one orbit determination data token.
  578.      * @param token token to process
  579.      * @return true if token was processed, false otherwise
  580.      */
  581.     private boolean processOrbitDeterminationToken(final ParseToken token) {
  582.         if (orbitDeterminationBlock == null) {
  583.             orbitDeterminationBlock = new OrbitDetermination();
  584.         }
  585.         anticipateNext(this::processDataSubStructureToken);
  586.         try {
  587.             return token.getName() != null &&
  588.                    OrbitDeterminationKey.valueOf(token.getName()).process(token, context, orbitDeterminationBlock);
  589.         } catch (IllegalArgumentException iae) {
  590.             // token has not been recognized
  591.             return false;
  592.         }
  593.     }

  594.     /** Process one user-defined parameter data token.
  595.      * @param token token to process
  596.      * @return true if token was processed, false otherwise
  597.      */
  598.     private boolean processUserDefinedToken(final ParseToken token) {
  599.         if (userDefinedBlock == null) {
  600.             userDefinedBlock = new UserDefined();
  601.         }
  602.         anticipateNext(this::processDataSubStructureToken);
  603.         if ("COMMENT".equals(token.getName())) {
  604.             return token.getType() == TokenType.ENTRY ? userDefinedBlock.addComment(token.getContentAsNormalizedString()) : true;
  605.         } else if (token.getName().startsWith(UserDefined.USER_DEFINED_PREFIX)) {
  606.             if (token.getType() == TokenType.ENTRY) {
  607.                 userDefinedBlock.addEntry(token.getName().substring(UserDefined.USER_DEFINED_PREFIX.length()),
  608.                                           token.getContentAsNormalizedString());
  609.             }
  610.             return true;
  611.         } else {
  612.             // the token was not processed
  613.             return false;
  614.         }
  615.     }

  616. }