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 }