ParserBuilder.java

  1. /* Copyright 2002-2025 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;

  18. import java.lang.reflect.Array;
  19. import java.util.List;
  20. import java.util.function.Function;

  21. import org.orekit.annotation.DefaultDataContext;
  22. import org.orekit.data.DataContext;
  23. import org.orekit.files.ccsds.ndm.adm.acm.AcmParser;
  24. import org.orekit.files.ccsds.ndm.adm.aem.AemParser;
  25. import org.orekit.files.ccsds.ndm.adm.apm.ApmParser;
  26. import org.orekit.files.ccsds.ndm.cdm.CdmParser;
  27. import org.orekit.files.ccsds.ndm.odm.ocm.OcmParser;
  28. import org.orekit.files.ccsds.ndm.odm.oem.OemParser;
  29. import org.orekit.files.ccsds.ndm.odm.omm.OmmParser;
  30. import org.orekit.files.ccsds.ndm.odm.opm.OpmParser;
  31. import org.orekit.files.ccsds.ndm.tdm.IdentityConverter;
  32. import org.orekit.files.ccsds.ndm.tdm.RangeUnits;
  33. import org.orekit.files.ccsds.ndm.tdm.RangeUnitsConverter;
  34. import org.orekit.files.ccsds.ndm.tdm.TdmParser;
  35. import org.orekit.files.ccsds.utils.lexical.ParseToken;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.utils.IERSConventions;

  38. /** Builder for all {@link NdmConstituent CCSDS Message} files parsers.
  39.  * <p>
  40.  * This builder can be used for building all CCSDS Messages parsers types.
  41.  * It is particularly useful in multi-threaded context as parsers cannot
  42.  * be shared between threads and thus several independent parsers must be
  43.  * built in this case.
  44.  * </p>
  45.  * @author Luc Maisonobe
  46.  * @since 11.0
  47.  */
  48. public class ParserBuilder extends AbstractBuilder<ParserBuilder> {

  49.     /** Indicator for simple or accurate EOP interpolation. */
  50.     private final  boolean simpleEOP;

  51.     /** Gravitational coefficient. */
  52.     private final double mu;

  53.     /** Default mass. */
  54.     private final double defaultMass;

  55.     /** Default interpolation degree. */
  56.     private final int defaultInterpolationDegree;

  57.     /** Behavior adopted for units that have been parsed from a CCSDS message. */
  58.     private final ParsedUnitsBehavior parsedUnitsBehavior;

  59.     /** Filters for parse tokens.
  60.      * @since 12.0
  61.      */
  62.     private final Function<ParseToken, List<ParseToken>>[] filters;

  63.     /**
  64.      * Simple constructor.
  65.      * <p>
  66.      * This constructor creates a builder with
  67.      * <ul>
  68.      *   <li>{@link #getConventions() IERS conventions} set to {@link IERSConventions#IERS_2010}</li>
  69.      *   <li>{@link #isSimpleEOP() simple EOP} set to {@code true}</li>
  70.      *   <li>{@link #getDataContext() data context} set to {@link DataContext#getDefault() default context}</li>
  71.      *   <li>{@link #getMissionReferenceDate() mission reference date} set to {@code null}</li>
  72.      *   <li>{@link #getMu() gravitational coefficient} set to {@code Double.NaN}</li>
  73.      *   <li>{@link #getEquatorialRadius() central body equatorial radius} set to {@code Double.NaN}</li>
  74.      *   <li>{@link #getFlattening() central body flattening} set to {@code Double.NaN}</li>
  75.      *   <li>{@link #getDefaultMass() default mass} set to {@code Double.NaN}</li>
  76.      *   <li>{@link #getDefaultInterpolationDegree() default interpolation degree} set to {@code 1}</li>
  77.      *   <li>{@link #getParsedUnitsBehavior() parsed unit behavior} set to {@link ParsedUnitsBehavior#CONVERT_COMPATIBLE}</li>
  78.      *   <li>{@link #getRangeUnitsConverter() converter for range units} set to {@link IdentityConverter}</li>
  79.      * </ul>
  80.      */
  81.     @DefaultDataContext
  82.     public ParserBuilder() {
  83.         this(DataContext.getDefault());
  84.     }

  85.     /**
  86.      * Simple constructor.
  87.      * <p>
  88.      * This constructor creates a builder with
  89.      * <ul>
  90.      *   <li>{@link #getConventions() IERS conventions} set to {@link IERSConventions#IERS_2010}</li>
  91.      *   <li>{@link #isSimpleEOP() simple EOP} set to {@code true}</li>
  92.      *   <li>{@link #getMissionReferenceDate() mission reference date} set to {@code null}</li>
  93.      *   <li>{@link #getMu() gravitational coefficient} set to {@code Double.NaN}</li>
  94.      *   <li>{@link #getEquatorialRadius() central body equatorial radius} set to {@code Double.NaN}</li>
  95.      *   <li>{@link #getFlattening() central body flattening} set to {@code Double.NaN}</li>
  96.      *   <li>{@link #getDefaultMass() default mass} set to {@code Double.NaN}</li>
  97.      *   <li>{@link #getDefaultInterpolationDegree() default interpolation degree} set to {@code 1}</li>
  98.      *   <li>{@link #getParsedUnitsBehavior() parsed unit behavior} set to {@link ParsedUnitsBehavior#CONVERT_COMPATIBLE}</li>
  99.      *   <li>{@link #getRangeUnitsConverter() converter for range units} set to {@link IdentityConverter}</li>
  100.      * </ul>
  101.      * @param dataContext data context used to retrieve frames, time scales, etc.
  102.      */
  103.     @SuppressWarnings("unchecked")
  104.     public ParserBuilder(final DataContext dataContext) {
  105.         this(IERSConventions.IERS_2010, Double.NaN, Double.NaN, dataContext,
  106.              null, new IdentityConverter(), true, Double.NaN, Double.NaN,
  107.              1, ParsedUnitsBehavior.CONVERT_COMPATIBLE,
  108.              (Function<ParseToken, List<ParseToken>>[]) Array.newInstance(Function.class, 0));
  109.     }

  110.     /** Complete constructor.
  111.      * @param conventions IERS Conventions
  112.      * @param equatorialRadius central body equatorial radius
  113.      * @param flattening central body flattening
  114.      * @param dataContext used to retrieve frames, time scales, etc.
  115.      * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
  116.      * @param rangeUnitsConverter converter for {@link RangeUnits#RU Range Units}
  117.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  118.      * @param mu gravitational coefficient
  119.      * @param defaultMass default mass
  120.      * @param defaultInterpolationDegree default interpolation degree
  121.      * @param parsedUnitsBehavior behavior to adopt for handling parsed units
  122.      * @param filters filters to apply to parse tokens
  123.      * @since 12.0
  124.      */
  125.     private ParserBuilder(final IERSConventions conventions,
  126.                           final double equatorialRadius, final double flattening,
  127.                           final DataContext dataContext, final AbsoluteDate missionReferenceDate,
  128.                           final RangeUnitsConverter rangeUnitsConverter,
  129.                           final boolean simpleEOP, final double mu,
  130.                           final double defaultMass, final int defaultInterpolationDegree,
  131.                           final ParsedUnitsBehavior parsedUnitsBehavior,
  132.                           final Function<ParseToken, List<ParseToken>>[] filters) {
  133.         super(conventions, equatorialRadius, flattening, dataContext, missionReferenceDate, rangeUnitsConverter);
  134.         this.simpleEOP                  = simpleEOP;
  135.         this.mu                         = mu;
  136.         this.defaultMass                = defaultMass;
  137.         this.defaultInterpolationDegree = defaultInterpolationDegree;
  138.         this.parsedUnitsBehavior        = parsedUnitsBehavior;
  139.         this.filters                    = filters.clone();
  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     protected ParserBuilder create(final IERSConventions newConventions,
  144.                                    final double newEquatorialRadius, final double newFlattening,
  145.                                    final DataContext newDataContext,
  146.                                    final AbsoluteDate newMissionReferenceDate, final RangeUnitsConverter newRangeUnitsConverter) {
  147.         return new ParserBuilder(newConventions, newEquatorialRadius, newFlattening, newDataContext,
  148.                                  newMissionReferenceDate, newRangeUnitsConverter, simpleEOP, mu,
  149.                                  defaultMass, defaultInterpolationDegree, parsedUnitsBehavior, filters);
  150.     }

  151.     /** Set up flag for ignoring tidal effects when interpolating EOP.
  152.      * @param newSimpleEOP true if tidal effects are ignored when interpolating EOP
  153.      * @return a new builder with updated configuration (the instance is not changed)
  154.      */
  155.     public ParserBuilder withSimpleEOP(final boolean newSimpleEOP) {
  156.         return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
  157.                                  getMissionReferenceDate(), getRangeUnitsConverter(), newSimpleEOP, getMu(), getDefaultMass(),
  158.                                  getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters());
  159.     }

  160.     /** Check if tidal effects are ignored when interpolating EOP.
  161.      * @return true if tidal effects are ignored when interpolating EOP
  162.      */
  163.     public boolean isSimpleEOP() {
  164.         return simpleEOP;
  165.     }

  166.     /** Set up the gravitational coefficient.
  167.      * @param newMu gravitational coefficient
  168.      * @return a new builder with updated configuration (the instance is not changed)
  169.      */
  170.     public ParserBuilder withMu(final double newMu) {
  171.         return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
  172.                                  getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), newMu, getDefaultMass(),
  173.                                  getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters());
  174.     }

  175.     /** Get the gravitational coefficient.
  176.      * @return gravitational coefficient
  177.      */
  178.     public double getMu() {
  179.         return mu;
  180.     }

  181.     /** Set up the default mass.
  182.      * <p>
  183.      * The default mass is used only by {@link OpmParser}.
  184.      * </p>
  185.      * @param newDefaultMass default mass
  186.      * @return a new builder with updated configuration (the instance is not changed)
  187.      */
  188.     public ParserBuilder withDefaultMass(final double newDefaultMass) {
  189.         return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
  190.                                  getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), getMu(), newDefaultMass,
  191.                                  getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters());
  192.     }

  193.     /** Get the default mass.
  194.      * @return default mass
  195.      */
  196.     public double getDefaultMass() {
  197.         return defaultMass;
  198.     }

  199.     /** Set up the default interpolation degree.
  200.      * <p>
  201.      * The default interpolation degree is used only by {@link AemParser}
  202.      * and {@link OemParser}.
  203.      * </p>
  204.      * @param newDefaultInterpolationDegree default interpolation degree
  205.      * @return a new builder with updated configuration (the instance is not changed)
  206.      */
  207.     public ParserBuilder withDefaultInterpolationDegree(final int newDefaultInterpolationDegree) {
  208.         return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
  209.                                  getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), getMu(), getDefaultMass(),
  210.                                  newDefaultInterpolationDegree, getParsedUnitsBehavior(), getFilters());
  211.     }

  212.     /** Get the default interpolation degree.
  213.      * @return default interpolation degree
  214.      */
  215.     public int getDefaultInterpolationDegree() {
  216.         return defaultInterpolationDegree;
  217.     }

  218.     /** Set up the behavior to adopt for handling parsed units.
  219.      * @param newParsedUnitsBehavior behavior to adopt for handling parsed units
  220.      * @return a new builder with updated configuration (the instance is not changed)
  221.      */
  222.     public ParserBuilder withParsedUnitsBehavior(final ParsedUnitsBehavior newParsedUnitsBehavior) {
  223.         return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
  224.                                  getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), getMu(), getDefaultMass(),
  225.                                  getDefaultInterpolationDegree(), newParsedUnitsBehavior, getFilters());
  226.     }

  227.     /** Get the behavior to adopt for handling parsed units.
  228.      * @return behavior to adopt for handling parsed units
  229.      */
  230.     public ParsedUnitsBehavior getParsedUnitsBehavior() {
  231.         return parsedUnitsBehavior;
  232.     }

  233.     /** Add a filter for parsed tokens.
  234.      * <p>
  235.      * This filter allows to change parsed tokens. This method can be called several times,
  236.      * once for each filter to set up. The filters are always applied in the order they were set.
  237.      * There are several use cases for this feature.
  238.      * </p>
  239.      * <p>
  240.      * The first use case is to allow parsing malformed CCSDS messages with some known
  241.      * discrepancies that can be fixed. One real life example (the one that motivated the
  242.      * development of this feature) is OMM files in XML format that add an empty
  243.      * OBJECT_ID. This could be fixed by setting a filter as follows:
  244.      * </p>
  245.      * <pre>{@code
  246.      * Omm omm = new ParserBuilder().
  247.      *           withFilter(token -> {
  248.      *                          if ("OBJECT_ID".equals(token.getName()) &&
  249.      *                              (token.getRawContent() == null || token.getRawContent().isEmpty())) {
  250.      *                              // replace null/empty entries with "unknown"
  251.      *                              return Collections.singletonList(new ParseToken(token.getType(), token.getName(),
  252.      *                                                                              "unknown", token.getUnits(),
  253.      *                                                                              token.getLineNumber(), token.getFileName()));
  254.      *                          } else {
  255.      *                              return Collections.singletonList(token);
  256.      *                          }
  257.      *                     }).
  258.      *           buildOmmParser().
  259.      *           parseMessage(message);
  260.      * }</pre>
  261.      * <p>
  262.      * A second use case is to remove unwanted data. For example in order to remove all user-defined data
  263.      * one could use:
  264.      * </p>
  265.      * <pre>{@code
  266.      * Omm omm = new ParserBuilder().
  267.      *           withFilter(token -> {
  268.      *                          if (token.getName().startsWith("USER_DEFINED")) {
  269.      *                              return Collections.emptyList();
  270.      *                          } else {
  271.      *                              return Collections.singletonList(token);
  272.      *                          }
  273.      *                     }).
  274.      *           buildOmmmParser().
  275.      *           parseMessage(message);
  276.      * }</pre>
  277.      * <p>
  278.      * A third use case is to add data not originally present in the file. For example in order
  279.      * to add a generated ODM V3 message id to an ODM V2 message that lacks it, one could do:
  280.      * </p>
  281.      * <pre>{@code
  282.      * final String myMessageId = ...; // this could be computed from a counter, or a SHA256 digest, or some metadata
  283.      * Omm omm = new ParserBuilder()
  284.      *           withFilter(token -> {
  285.      *                          if ("CCSDS_OMM_VERS".equals(token.getName())) {
  286.      *                              // enforce ODM V3
  287.      *                              return Collections.singletonList(new ParseToken(token.getType(), token.getName(),
  288.      *                                                                              "3.0", token.getUnits(),
  289.      *                                                                              token.getLineNumber(), token.getFileName()));
  290.      *                          } else {
  291.      *                              return Collections.singletonList(token);
  292.      *                          }
  293.      *                      }).
  294.      *           withFilter(token -> {
  295.      *                          if ("ORIGINATOR".equals(token.getName())) {
  296.      *                              // add generated message ID after ORIGINATOR entry
  297.      *                              return Arrays.asList(token,
  298.      *                                                   new ParseToken(TokenType.ENTRY, "MESSAGE_ID",
  299.      *                                                                  myMessageId, null,
  300.      *                                                                  -1, token.getFileName()));
  301.      *                          } else {
  302.      *                              return Collections.singletonList(token);
  303.      *                          }
  304.      *                      }).
  305.      *           buildOmmmParser().
  306.      *           parseMessage(message);
  307.      * }</pre>
  308.      * @param filter token filter to add
  309.      * @return a new builder with updated configuration (the instance is not changed)
  310.      * @since 12.0
  311.      */
  312.     public ParserBuilder withFilter(final Function<ParseToken, List<ParseToken>> filter) {

  313.         // populate new filters array
  314.         @SuppressWarnings("unchecked")
  315.         final Function<ParseToken, List<ParseToken>>[] newFilters =
  316.                         (Function<ParseToken, List<ParseToken>>[]) Array.newInstance(Function.class, filters.length + 1);
  317.         System.arraycopy(filters, 0, newFilters, 0, filters.length);
  318.         newFilters[filters.length] = filter;

  319.         return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
  320.                                  getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), getMu(), getDefaultMass(),
  321.                                  getDefaultInterpolationDegree(), getParsedUnitsBehavior(),
  322.                                  newFilters);

  323.     }

  324.     /** Get the filters to apply to parse tokens.
  325.      * @return filters to apply to parse tokens
  326.      * @since 12.0
  327.      */
  328.     public Function<ParseToken, List<ParseToken>>[] getFilters() {
  329.         return filters.clone();
  330.     }

  331.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.Ndm Navigation Data Messages}.
  332.      * @return a new parser
  333.      */
  334.     public NdmParser buildNdmParser() {
  335.         return new NdmParser(this, getFilters());
  336.     }

  337.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.opm.Opm Orbit Parameters Messages}.
  338.      * @return a new parser
  339.      */
  340.     public OpmParser buildOpmParser() {
  341.         return new OpmParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
  342.                              getMu(), getDefaultMass(), getParsedUnitsBehavior(), getFilters());
  343.     }

  344.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.omm.Omm Orbit Mean elements Messages}.
  345.      * @return a new parser
  346.      */
  347.     public OmmParser buildOmmParser() {
  348.         return new OmmParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
  349.                              getMu(), getDefaultMass(), getParsedUnitsBehavior(), getFilters());
  350.     }

  351.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.oem.Oem Orbit Ephemeris Messages}.
  352.      * @return a new parser
  353.      */
  354.     public OemParser buildOemParser() {
  355.         return new OemParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
  356.                              getMu(), getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters());
  357.     }

  358.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.ocm.Ocm Orbit Comprehensive Messages}.
  359.      * @return a new parser
  360.      */
  361.     public OcmParser buildOcmParser() {
  362.         return new OcmParser(getConventions(), getEquatorialRadius(), getFlattening(),
  363.                              isSimpleEOP(), getDataContext(), getMu(),
  364.                              getParsedUnitsBehavior(), getFilters());
  365.     }

  366.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.adm.apm.Apm Attitude Parameters Messages}.
  367.      * @return a new parser
  368.      */
  369.     public ApmParser buildApmParser() {
  370.         return new ApmParser(getConventions(), isSimpleEOP(), getDataContext(),
  371.                              getMissionReferenceDate(), getParsedUnitsBehavior(), getFilters());
  372.     }

  373.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.adm.aem.Aem Attitude Ephemeris Messages}.
  374.      * @return a new parser
  375.      */
  376.     public AemParser buildAemParser() {
  377.         return new AemParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
  378.                              getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters());
  379.     }

  380.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.adm.acm.Acm Attitude Comprehensive Messages}.
  381.      * @return a new parser
  382.      * @since 12.0
  383.      */
  384.     public AcmParser buildAcmParser() {
  385.         return new AcmParser(getConventions(), isSimpleEOP(), getDataContext(),
  386.                              getParsedUnitsBehavior(), getFilters());
  387.     }

  388.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.tdm.Tdm Tracking Data Messages}.
  389.      * @return a new parser
  390.      */
  391.     public TdmParser buildTdmParser() {
  392.         return new TdmParser(getConventions(), isSimpleEOP(), getDataContext(),
  393.                              getParsedUnitsBehavior(), getRangeUnitsConverter(), getFilters());
  394.     }

  395.     /** Build a parser for {@link org.orekit.files.ccsds.ndm.cdm.Cdm Conjunction Data Messages}.
  396.      * @return a new parser
  397.      */
  398.     public CdmParser buildCdmParser() {
  399.         return new CdmParser(getConventions(), isSimpleEOP(), getDataContext(),
  400.                              getParsedUnitsBehavior(), getFilters());
  401.     }

  402. }