AbstractMessageParser.java

  1. /* Copyright 2002-2024 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.utils.parsing;

  18. import java.io.IOException;
  19. import java.util.ArrayList;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.function.Function;

  25. import org.hipparchus.exception.LocalizedCoreFormats;
  26. import org.orekit.data.DataSource;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitInternalError;
  29. import org.orekit.errors.OrekitMessages;
  30. import org.orekit.files.ccsds.utils.FileFormat;
  31. import org.orekit.files.ccsds.utils.lexical.LexicalAnalyzerSelector;
  32. import org.orekit.files.ccsds.utils.lexical.MessageParser;
  33. import org.orekit.files.ccsds.utils.lexical.MessageVersionXmlTokenBuilder;
  34. import org.orekit.files.ccsds.utils.lexical.ParseToken;
  35. import org.orekit.files.ccsds.utils.lexical.TokenType;
  36. import org.orekit.files.ccsds.utils.lexical.XmlTokenBuilder;

  37. /** Parser for CCSDS messages.
  38.  * <p>
  39.  * Note than starting with Orekit 11.0, CCSDS message parsers are
  40.  * mutable objects that gather the data being parsed, until the
  41.  * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
  42.  * parseMessage} method has returned. This implies that parsers
  43.  * should <em>not</em> be used in a multi-thread context. The recommended
  44.  * way to use parsers is to either dedicate one parser for each message
  45.  * and drop it afterwards, or to use a single-thread loop.
  46.  * </p>
  47.  * @param <T> type of the file
  48.  * @author Luc Maisonobe
  49.  * @since 11.0
  50.  */
  51. public abstract class AbstractMessageParser<T> implements MessageParser<T> {

  52.     /** Comment key.
  53.      * @since 12.0
  54.      */
  55.     private static final String COMMENT = "COMMENT";

  56.     /** Safety limit for loop over processing states. */
  57.     private static final int MAX_LOOP = 100;

  58.     /** Root element for XML files. */
  59.     private final String root;

  60.     /** Key for format version. */
  61.     private final String formatVersionKey;

  62.     /** Filters for parse tokens. */
  63.     private final Function<ParseToken, List<ParseToken>>[] filters;

  64.     /** Anticipated next processing state. */
  65.     private ProcessingState next;

  66.     /** Current processing state. */
  67.     private ProcessingState current;

  68.     /** Fallback processing state. */
  69.     private ProcessingState fallback;

  70.     /** Format of the file ready to be parsed. */
  71.     private FileFormat format;

  72.     /** Flag for XML end tag. */
  73.     private boolean endTagSeen;

  74.     /** Simple constructor.
  75.      * @param root root element for XML files
  76.      * @param formatVersionKey key for format version
  77.      * @param filters filters to apply to parse tokens
  78.      * @since 12.0
  79.      */
  80.     protected AbstractMessageParser(final String root, final String formatVersionKey,
  81.                                     final Function<ParseToken, List<ParseToken>>[] filters) {
  82.         this.root             = root;
  83.         this.formatVersionKey = formatVersionKey;
  84.         this.filters          = filters.clone();
  85.         this.current          = null;
  86.         setFallback(new ErrorState());
  87.     }

  88.     /** Set fallback processing state.
  89.      * <p>
  90.      * The fallback processing state is used if anticipated state fails
  91.      * to parse the token.
  92.      * </p>
  93.      * @param fallback processing state to use if anticipated state does not work
  94.      */
  95.     public void setFallback(final ProcessingState fallback) {
  96.         this.fallback = fallback;
  97.     }

  98.     /** Reset parser to initial state before parsing.
  99.      * @param fileFormat format of the file ready to be parsed
  100.      * @param initialState initial processing state
  101.      */
  102.     protected void reset(final FileFormat fileFormat, final ProcessingState initialState) {
  103.         format     = fileFormat;
  104.         current    = initialState;
  105.         endTagSeen = false;
  106.         anticipateNext(fallback);
  107.     }

  108.     /** Set the flag for XML end tag.
  109.      * @param endTagSeen if true, the XML end tag has been seen
  110.      */
  111.     public void setEndTagSeen(final boolean endTagSeen) {
  112.         this.endTagSeen = endTagSeen;
  113.     }

  114.     /** Check if XML end tag has been seen.
  115.      * @return true if XML end tag has been seen
  116.      */
  117.     public boolean wasEndTagSeen() {
  118.         return endTagSeen;
  119.     }

  120.     /** Get the current processing state.
  121.      * @return current processing state
  122.      */
  123.     public ProcessingState getCurrent() {
  124.         return current;
  125.     }

  126.     /** {@inheritDoc} */
  127.     @Override
  128.     public FileFormat getFileFormat() {
  129.         return format;
  130.     }

  131.     /** {@inheritDoc} */
  132.     @Override
  133.     public T parseMessage(final DataSource source) {
  134.         try {
  135.             return LexicalAnalyzerSelector.select(source).accept(this);
  136.         } catch (IOException ioe) {
  137.             throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE,
  138.                                       ioe.getLocalizedMessage());
  139.         }
  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public String getFormatVersionKey() {
  144.         return formatVersionKey;
  145.     }

  146.     /** {@inheritDoc} */
  147.     @Override
  148.     public Map<String, XmlTokenBuilder> getSpecialXmlElementsBuilders() {

  149.         final HashMap<String, XmlTokenBuilder> builders = new HashMap<>();

  150.         if (formatVersionKey != null) {
  151.             // special handling of root tag that contains the format version
  152.             builders.put(root, new MessageVersionXmlTokenBuilder());
  153.         }

  154.         return builders;

  155.     }

  156.     /** Anticipate what next processing state should be.
  157.      * @param anticipated anticipated next processing state
  158.      */
  159.     public void anticipateNext(final ProcessingState anticipated) {
  160.         this.next = anticipated;
  161.     }

  162.     /** {@inheritDoc} */
  163.     @Override
  164.     public void process(final ParseToken token) {

  165.         // loop over the filters
  166.         List<ParseToken> filtered = Collections.singletonList(token);
  167.         for (Function<ParseToken, List<ParseToken>> filter : filters) {
  168.             final ArrayList<ParseToken> newFiltered = new ArrayList<>();
  169.             for (final ParseToken original : filtered) {
  170.                 newFiltered.addAll(filter.apply(original));
  171.             }
  172.             filtered = newFiltered;
  173.         }
  174.         if (filtered.isEmpty()) {
  175.             return;
  176.         }

  177.         int remaining = filtered.size();
  178.         for (final ParseToken filteredToken : filtered) {

  179.             if (filteredToken.getType() == TokenType.ENTRY &&
  180.                 !COMMENT.equals(filteredToken.getName()) &&
  181.                 (filteredToken.getRawContent() == null || filteredToken.getRawContent().isEmpty())) {
  182.                 // value is empty, which is forbidden by CCSDS standards
  183.                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, filteredToken.getName());
  184.             }

  185.             // loop over the various states until one really processes the token
  186.             for (int i = 0; i < MAX_LOOP; ++i) {
  187.                 if (current.processToken(filteredToken)) {
  188.                     // filtered token was properly processed
  189.                     if (--remaining == 0) {
  190.                         // we have processed all filtered tokens
  191.                         return;
  192.                     } else {
  193.                         // we need to continue processing the remaining filtered tokens
  194.                         break;
  195.                     }
  196.                 } else {
  197.                     // filtered token was not processed by current processing state, switch to next one
  198.                     current = next;
  199.                     next    = fallback;
  200.                 }
  201.             }

  202.         }

  203.         // this should never happen
  204.         throw new OrekitInternalError(null);

  205.     }

  206. }