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  
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.function.Function;
26  
27  import org.hipparchus.exception.LocalizedCoreFormats;
28  import org.orekit.data.DataSource;
29  import org.orekit.errors.OrekitException;
30  import org.orekit.errors.OrekitInternalError;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.files.ccsds.utils.FileFormat;
33  import org.orekit.files.ccsds.utils.lexical.LexicalAnalyzerSelector;
34  import org.orekit.files.ccsds.utils.lexical.MessageParser;
35  import org.orekit.files.ccsds.utils.lexical.MessageVersionXmlTokenBuilder;
36  import org.orekit.files.ccsds.utils.lexical.ParseToken;
37  import org.orekit.files.ccsds.utils.lexical.TokenType;
38  import org.orekit.files.ccsds.utils.lexical.XmlTokenBuilder;
39  
40  /** Parser for CCSDS messages.
41   * <p>
42   * Note than starting with Orekit 11.0, CCSDS message parsers are
43   * mutable objects that gather the data being parsed, until the
44   * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
45   * parseMessage} method has returned. This implies that parsers
46   * should <em>not</em> be used in a multi-thread context. The recommended
47   * way to use parsers is to either dedicate one parser for each message
48   * and drop it afterwards, or to use a single-thread loop.
49   * </p>
50   * @param <T> type of the file
51   * @author Luc Maisonobe
52   * @since 11.0
53   */
54  public abstract class AbstractMessageParser<T> implements MessageParser<T> {
55  
56      /** Comment key.
57       * @since 12.0
58       */
59      private static final String COMMENT = "COMMENT";
60  
61      /** Safety limit for loop over processing states. */
62      private static final int MAX_LOOP = 100;
63  
64      /** Root element for XML files. */
65      private final String root;
66  
67      /** Key for format version. */
68      private final String formatVersionKey;
69  
70      /** Filters for parse tokens. */
71      private final Function<ParseToken, List<ParseToken>>[] filters;
72  
73      /** Anticipated next processing state. */
74      private ProcessingState next;
75  
76      /** Current processing state. */
77      private ProcessingState current;
78  
79      /** Fallback processing state. */
80      private ProcessingState fallback;
81  
82      /** Format of the file ready to be parsed. */
83      private FileFormat format;
84  
85      /** Flag for XML end tag. */
86      private boolean endTagSeen;
87  
88      /** Simple constructor.
89       * @param root root element for XML files
90       * @param formatVersionKey key for format version
91       * @param filters filters to apply to parse tokens
92       * @since 12.0
93       */
94      protected AbstractMessageParser(final String root, final String formatVersionKey,
95                                      final Function<ParseToken, List<ParseToken>>[] filters) {
96          this.root             = root;
97          this.formatVersionKey = formatVersionKey;
98          this.filters          = filters.clone();
99          this.current          = null;
100         setFallback(new ErrorState());
101     }
102 
103     /** Set fallback processing state.
104      * <p>
105      * The fallback processing state is used if anticipated state fails
106      * to parse the token.
107      * </p>
108      * @param fallback processing state to use if anticipated state does not work
109      */
110     public void setFallback(final ProcessingState fallback) {
111         this.fallback = fallback;
112     }
113 
114     /** Reset parser to initial state before parsing.
115      * @param fileFormat format of the file ready to be parsed
116      * @param initialState initial processing state
117      */
118     protected void reset(final FileFormat fileFormat, final ProcessingState initialState) {
119         format     = fileFormat;
120         current    = initialState;
121         endTagSeen = false;
122         anticipateNext(fallback);
123     }
124 
125     /** Set the flag for XML end tag.
126      * @param endTagSeen if true, the XML end tag has been seen
127      */
128     public void setEndTagSeen(final boolean endTagSeen) {
129         this.endTagSeen = endTagSeen;
130     }
131 
132     /** Check if XML end tag has been seen.
133      * @return true if XML end tag has been seen
134      */
135     public boolean wasEndTagSeen() {
136         return endTagSeen;
137     }
138 
139     /** Get the current processing state.
140      * @return current processing state
141      */
142     public ProcessingState getCurrent() {
143         return current;
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public FileFormat getFileFormat() {
149         return format;
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public T parseMessage(final DataSource source) {
155         try {
156             return LexicalAnalyzerSelector.select(source).accept(this);
157         } catch (IOException ioe) {
158             throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE,
159                                       ioe.getLocalizedMessage());
160         }
161     }
162 
163     /** {@inheritDoc} */
164     @Override
165     public String getFormatVersionKey() {
166         return formatVersionKey;
167     }
168 
169     /** {@inheritDoc} */
170     @Override
171     public Map<String, XmlTokenBuilder> getSpecialXmlElementsBuilders() {
172 
173         final HashMap<String, XmlTokenBuilder> builders = new HashMap<>();
174 
175         if (formatVersionKey != null) {
176             // special handling of root tag that contains the format version
177             builders.put(root, new MessageVersionXmlTokenBuilder());
178         }
179 
180         return builders;
181 
182     }
183 
184     /** Anticipate what next processing state should be.
185      * @param anticipated anticipated next processing state
186      */
187     public void anticipateNext(final ProcessingState anticipated) {
188         this.next = anticipated;
189     }
190 
191     /** {@inheritDoc} */
192     @Override
193     public void process(final ParseToken token) {
194 
195         // loop over the filters
196         List<ParseToken> filtered = Collections.singletonList(token);
197         for (Function<ParseToken, List<ParseToken>> filter : filters) {
198             final ArrayList<ParseToken> newFiltered = new ArrayList<>();
199             for (final ParseToken original : filtered) {
200                 newFiltered.addAll(filter.apply(original));
201             }
202             filtered = newFiltered;
203         }
204         if (filtered.isEmpty()) {
205             return;
206         }
207 
208         int remaining = filtered.size();
209         for (final ParseToken filteredToken : filtered) {
210 
211             if (filteredToken.getType() == TokenType.ENTRY &&
212                 !COMMENT.equals(filteredToken.getName()) &&
213                 (filteredToken.getRawContent() == null || filteredToken.getRawContent().isEmpty())) {
214                 // value is empty, which is forbidden by CCSDS standards
215                 throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, filteredToken.getName());
216             }
217 
218             // loop over the various states until one really processes the token
219             for (int i = 0; i < MAX_LOOP; ++i) {
220                 if (current.processToken(filteredToken)) {
221                     // filtered token was properly processed
222                     if (--remaining == 0) {
223                         // we have processed all filtered tokens
224                         return;
225                     } else {
226                         // we need to continue processing the remaining filtered tokens
227                         break;
228                     }
229                 } else {
230                     // filtered token was not processed by current processing state, switch to next one
231                     current = next;
232                     next    = fallback;
233                 }
234             }
235 
236         }
237 
238         // this should never happen
239         throw new OrekitInternalError(null);
240 
241     }
242 
243 }