AemParser.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.adm.aem;

  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.function.Function;
  21. import java.util.regex.Pattern;

  22. import org.orekit.data.DataContext;
  23. import org.orekit.data.DataSource;
  24. import org.orekit.errors.OrekitException;
  25. import org.orekit.errors.OrekitMessages;
  26. import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
  27. import org.orekit.files.ccsds.ndm.adm.AdmCommonMetadataKey;
  28. import org.orekit.files.ccsds.ndm.adm.AdmHeader;
  29. import org.orekit.files.ccsds.ndm.adm.AdmMetadataKey;
  30. import org.orekit.files.ccsds.ndm.adm.AdmParser;
  31. import org.orekit.files.ccsds.section.HeaderProcessingState;
  32. import org.orekit.files.ccsds.section.KvnStructureProcessingState;
  33. import org.orekit.files.ccsds.section.MetadataKey;
  34. import org.orekit.files.ccsds.section.XmlStructureProcessingState;
  35. import org.orekit.files.ccsds.utils.ContextBinding;
  36. import org.orekit.files.ccsds.utils.FileFormat;
  37. import org.orekit.files.ccsds.utils.lexical.ParseToken;
  38. import org.orekit.files.ccsds.utils.lexical.TokenType;
  39. import org.orekit.files.ccsds.utils.parsing.ProcessingState;
  40. import org.orekit.files.general.AttitudeEphemerisFileParser;
  41. import org.orekit.time.AbsoluteDate;
  42. import org.orekit.utils.IERSConventions;

  43. /**
  44.  * A parser for the CCSDS AEM (Attitude Ephemeris Message).
  45.  * <p>
  46.  * Note than starting with Orekit 11.0, CCSDS message parsers are
  47.  * mutable objects that gather the data being parsed, until the
  48.  * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
  49.  * parseMessage} method has returned. This implies that parsers
  50.  * should <em>not</em> be used in a multi-thread context. The recommended
  51.  * way to use parsers is to either dedicate one parser for each message
  52.  * and drop it afterwards, or to use a single-thread loop.
  53.  * </p>
  54.  * @author Bryan Cazabonne
  55.  * @since 10.2
  56.  */
  57. public class AemParser extends AdmParser<Aem, AemParser> implements AttitudeEphemerisFileParser<Aem> {

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

  60.     /** File header. */
  61.     private AdmHeader header;

  62.     /** File segments. */
  63.     private List<AemSegment> segments;

  64.     /** Metadata for current observation block. */
  65.     private AemMetadata metadata;

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

  68.     /** Current Ephemerides block being parsed. */
  69.     private AemData currentBlock;

  70.     /** Default interpolation degree. */
  71.     private int defaultInterpolationDegree;

  72.     /** Processor for global message structure. */
  73.     private ProcessingState structureProcessor;

  74.     /** Current attitude entry. */
  75.     private AttitudeEntry currentEntry;

  76.     /**Complete constructor.
  77.      * <p>
  78.      * Calling this constructor directly is not recommended. Users should rather use
  79.      * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildAemParser()
  80.      * parserBuilder.buildAemParser()}.
  81.      * </p>
  82.      * @param conventions IERS Conventions
  83.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  84.      * @param dataContext used to retrieve frames, time scales, etc.
  85.      * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
  86.      * (may be null if time system is absolute)
  87.      * @param defaultInterpolationDegree default interpolation degree
  88.      * @param parsedUnitsBehavior behavior to adopt for handling parsed units
  89.      * @param filters filters to apply to parse tokens
  90.      * @since 12.0
  91.      */
  92.     public AemParser(final IERSConventions conventions, final boolean simpleEOP,
  93.                      final DataContext dataContext, final AbsoluteDate missionReferenceDate,
  94.                      final int defaultInterpolationDegree, final ParsedUnitsBehavior parsedUnitsBehavior,
  95.                      final Function<ParseToken, List<ParseToken>>[] filters) {
  96.         super(Aem.ROOT, Aem.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext,
  97.               missionReferenceDate, parsedUnitsBehavior, filters);
  98.         this.defaultInterpolationDegree  = defaultInterpolationDegree;
  99.     }

  100.     /** {@inheritDoc} */
  101.     @Override
  102.     public Aem parse(final DataSource source) {
  103.         return parseMessage(source);
  104.     }

  105.     /** {@inheritDoc} */
  106.     @Override
  107.     public AdmHeader getHeader() {
  108.         return header;
  109.     }

  110.     /** {@inheritDoc} */
  111.     @Override
  112.     public void reset(final FileFormat fileFormat) {
  113.         header   = new AdmHeader();
  114.         segments = new ArrayList<>();
  115.         metadata = null;
  116.         context  = null;
  117.         if (fileFormat == FileFormat.XML) {
  118.             structureProcessor = new XmlStructureProcessingState(Aem.ROOT, this);
  119.             reset(fileFormat, structureProcessor);
  120.         } else {
  121.             structureProcessor = new KvnStructureProcessingState(this);
  122.             reset(fileFormat, new HeaderProcessingState(this));
  123.         }
  124.     }

  125.     /** {@inheritDoc} */
  126.     @Override
  127.     public boolean prepareHeader() {
  128.         anticipateNext(new HeaderProcessingState(this));
  129.         return true;
  130.     }

  131.     /** {@inheritDoc} */
  132.     @Override
  133.     public boolean inHeader() {
  134.         anticipateNext(structureProcessor);
  135.         return true;
  136.     }

  137.     /** {@inheritDoc} */
  138.     @Override
  139.     public boolean finalizeHeader() {
  140.         header.validate(header.getFormatVersion());
  141.         return true;
  142.     }

  143.     /** {@inheritDoc} */
  144.     @Override
  145.     public boolean prepareMetadata() {
  146.         if (metadata != null) {
  147.             return false;
  148.         }
  149.         metadata  = new AemMetadata(defaultInterpolationDegree);
  150.         context   = new ContextBinding(this::getConventions, this::isSimpleEOP,
  151.                                        this::getDataContext, this::getParsedUnitsBehavior,
  152.                                        this::getMissionReferenceDate,
  153.                                        metadata::getTimeSystem, () -> 0.0, () -> 1.0);
  154.         anticipateNext(this::processMetadataToken);
  155.         return true;
  156.     }

  157.     /** {@inheritDoc} */
  158.     @Override
  159.     public boolean inMetadata() {
  160.         anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processKvnDataToken);
  161.         return true;
  162.     }

  163.     /** {@inheritDoc} */
  164.     @Override
  165.     public boolean finalizeMetadata() {
  166.         metadata.validate(header.getFormatVersion());
  167.         return true;
  168.     }

  169.     /** {@inheritDoc} */
  170.     @Override
  171.     public boolean prepareData() {
  172.         currentBlock = new AemData();
  173.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
  174.         return true;
  175.     }

  176.     /** {@inheritDoc} */
  177.     @Override
  178.     public boolean inData() {
  179.         anticipateNext(structureProcessor);
  180.         return true;
  181.     }

  182.     /** {@inheritDoc} */
  183.     @Override
  184.     public boolean finalizeData() {
  185.         if (metadata != null) {
  186.             currentBlock.validate(header.getFormatVersion());
  187.             segments.add(new AemSegment(metadata, currentBlock));
  188.         }
  189.         metadata = null;
  190.         context  = null;
  191.         return true;
  192.     }

  193.     /** {@inheritDoc} */
  194.     @Override
  195.     public Aem build() {
  196.         return new Aem(header, segments, getConventions(), getDataContext());
  197.     }

  198.     /** Manage attitude state section in a XML message.
  199.      * @param starting if true, parser is entering the section
  200.      * otherwise it is leaving the section
  201.      * @return always return true
  202.      */
  203.     boolean manageXmlAttitudeStateSection(final boolean starting) {
  204.         if (starting) {
  205.             currentEntry = new AttitudeEntry(metadata);
  206.             anticipateNext(this::processXmlDataToken);
  207.         } else {
  208.             currentBlock.addData(currentEntry.getCoordinates());
  209.             currentEntry = null;
  210.             anticipateNext(structureProcessor);
  211.         }
  212.         return true;
  213.     }

  214.     /** Add a comment to the data section.
  215.      * @param comment comment to add
  216.      * @return always return true
  217.      */
  218.     boolean addDataComment(final String comment) {
  219.         currentBlock.addComment(comment);
  220.         return true;
  221.     }

  222.     /** Process one metadata token.
  223.      * @param token token to process
  224.      * @return true if token was processed, false otherwise
  225.      */
  226.     private boolean processMetadataToken(final ParseToken token) {
  227.         inMetadata();
  228.         try {
  229.             return token.getName() != null &&
  230.                    MetadataKey.valueOf(token.getName()).process(token, context, metadata);
  231.         } catch (IllegalArgumentException iaeM) {
  232.             try {
  233.                 return AdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
  234.             } catch (IllegalArgumentException iaeD) {
  235.                 try {
  236.                     return AdmCommonMetadataKey.valueOf(token.getName()).process(token, context, metadata);
  237.                 } catch (IllegalArgumentException iaeC) {
  238.                     try {
  239.                         return AemMetadataKey.valueOf(token.getName()).process(token, context, metadata);
  240.                     } catch (IllegalArgumentException iaeE) {
  241.                         // token has not been recognized
  242.                         return false;
  243.                     }
  244.                 }
  245.             }
  246.         }
  247.     }

  248.     /** Process one XML data substructure token.
  249.      * @param token token to process
  250.      * @return true if token was processed, false otherwise
  251.      */
  252.     private boolean processXmlSubStructureToken(final ParseToken token) {
  253.         try {
  254.             return token.getName() != null &&
  255.                    XmlSubStructureKey.valueOf(token.getName()).process(token, this);
  256.         } catch (IllegalArgumentException iae) {
  257.             // token has not been recognized
  258.             return false;
  259.         }
  260.     }

  261.     /** Process one data token in a KVN message.
  262.      * @param token token to process
  263.      * @return true if token was processed, false otherwise
  264.      */
  265.     private boolean processKvnDataToken(final ParseToken token) {
  266.         inData();
  267.         if ("COMMENT".equals(token.getName())) {
  268.             return token.getType() == TokenType.ENTRY ? currentBlock.addComment(token.getContentAsNormalizedString()) : true;
  269.         } else if (token.getType() == TokenType.RAW_LINE) {
  270.             try {
  271.                 if (metadata.getAttitudeType() == null) {
  272.                     throw new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
  273.                                               AemMetadataKey.ATTITUDE_TYPE.name(), token.getFileName());
  274.                 }
  275.                 return currentBlock.addData(metadata.getAttitudeType().parse(metadata.isFirst(),
  276.                                                                              metadata.getEndpoints().isExternal2SpacecraftBody(),
  277.                                                                              metadata.getEulerRotSeq(),
  278.                                                                              metadata.isSpacecraftBodyRate(),
  279.                                                                              context, SPLIT_AT_BLANKS.split(token.getRawContent().trim())));
  280.             } catch (NumberFormatException nfe) {
  281.                 throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  282.                                           token.getLineNumber(), token.getFileName(), token.getRawContent());
  283.             }
  284.         } else {
  285.             // not a raw line, it is most probably the end of the data section
  286.             return false;
  287.         }
  288.     }

  289.     /** Process one data token in a XML message.
  290.      * @param token token to process
  291.      * @return true if token was processed, false otherwise
  292.      */
  293.     private boolean processXmlDataToken(final ParseToken token) {
  294.         anticipateNext(this::processXmlSubStructureToken);
  295.         try {
  296.             return token.getName() != null &&
  297.                    AttitudeEntryKey.valueOf(token.getName()).process(token, context, currentEntry);
  298.         } catch (IllegalArgumentException iae) {
  299.             // token has not been recognized
  300.             return false;
  301.         }
  302.     }

  303. }