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

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

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

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

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

  58.     /** File header. */
  59.     private Header header;

  60.     /** File segments. */
  61.     private List<AemSegment> segments;

  62.     /** Metadata for current observation block. */
  63.     private AemMetadata metadata;

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

  66.     /** Current Ephemerides block being parsed. */
  67.     private AemData currentBlock;

  68.     /** Default interpolation degree. */
  69.     private int defaultInterpolationDegree;

  70.     /** Processor for global message structure. */
  71.     private ProcessingState structureProcessor;

  72.     /** Current attitude entry. */
  73.     private AttitudeEntry currentEntry;

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

  95.     /** {@inheritDoc} */
  96.     @Override
  97.     public Aem parse(final DataSource source) {
  98.         return parseMessage(source);
  99.     }

  100.     /** {@inheritDoc} */
  101.     @Override
  102.     public Header getHeader() {
  103.         return header;
  104.     }

  105.     /** {@inheritDoc} */
  106.     @Override
  107.     public void reset(final FileFormat fileFormat) {
  108.         header   = new Header(2.0);
  109.         segments = new ArrayList<>();
  110.         metadata = null;
  111.         context  = null;
  112.         if (fileFormat == FileFormat.XML) {
  113.             structureProcessor = new XmlStructureProcessingState(Aem.ROOT, this);
  114.             reset(fileFormat, structureProcessor);
  115.         } else {
  116.             structureProcessor = new KvnStructureProcessingState(this);
  117.             reset(fileFormat, new HeaderProcessingState(this));
  118.         }
  119.     }

  120.     /** {@inheritDoc} */
  121.     @Override
  122.     public boolean prepareHeader() {
  123.         anticipateNext(new HeaderProcessingState(this));
  124.         return true;
  125.     }

  126.     /** {@inheritDoc} */
  127.     @Override
  128.     public boolean inHeader() {
  129.         anticipateNext(structureProcessor);
  130.         return true;
  131.     }

  132.     /** {@inheritDoc} */
  133.     @Override
  134.     public boolean finalizeHeader() {
  135.         header.validate(header.getFormatVersion());
  136.         return true;
  137.     }

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

  152.     /** {@inheritDoc} */
  153.     @Override
  154.     public boolean inMetadata() {
  155.         anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processKvnDataToken);
  156.         return true;
  157.     }

  158.     /** {@inheritDoc} */
  159.     @Override
  160.     public boolean finalizeMetadata() {
  161.         metadata.validate(header.getFormatVersion());
  162.         return true;
  163.     }

  164.     /** {@inheritDoc} */
  165.     @Override
  166.     public boolean prepareData() {
  167.         currentBlock = new AemData();
  168.         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
  169.         return true;
  170.     }

  171.     /** {@inheritDoc} */
  172.     @Override
  173.     public boolean inData() {
  174.         anticipateNext(structureProcessor);
  175.         return true;
  176.     }

  177.     /** {@inheritDoc} */
  178.     @Override
  179.     public boolean finalizeData() {
  180.         if (metadata != null) {
  181.             currentBlock.validate(header.getFormatVersion());
  182.             segments.add(new AemSegment(metadata, currentBlock));
  183.         }
  184.         metadata = null;
  185.         context  = null;
  186.         return true;
  187.     }

  188.     /** {@inheritDoc} */
  189.     @Override
  190.     public Aem build() {
  191.         final Aem file = new Aem(header, segments, getConventions(), getDataContext());
  192.         file.checkTimeSystems();
  193.         return file;
  194.     }

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

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

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

  241.     /** Process one XML data substructure token.
  242.      * @param token token to process
  243.      * @return true if token was processed, false otherwise
  244.      */
  245.     private boolean processXmlSubStructureToken(final ParseToken token) {
  246.         try {
  247.             return token.getName() != null &&
  248.                    XmlSubStructureKey.valueOf(token.getName()).process(token, this);
  249.         } catch (IllegalArgumentException iae) {
  250.             // token has not been recognized
  251.             return false;
  252.         }
  253.     }

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

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

  296. }