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