1 /* Copyright 2002-2026 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
19 import java.lang.reflect.Array;
20 import java.util.List;
21 import java.util.function.Function;
22
23 import org.orekit.annotation.DefaultDataContext;
24 import org.orekit.data.DataContext;
25 import org.orekit.files.ccsds.definitions.CcsdsFrameMapper;
26 import org.orekit.files.ccsds.definitions.OrekitCcsdsFrameMapper;
27 import org.orekit.files.ccsds.ndm.adm.acm.AcmParser;
28 import org.orekit.files.ccsds.ndm.adm.aem.AemParser;
29 import org.orekit.files.ccsds.ndm.adm.apm.ApmParser;
30 import org.orekit.files.ccsds.ndm.cdm.CdmParser;
31 import org.orekit.files.ccsds.ndm.odm.ocm.OcmParser;
32 import org.orekit.files.ccsds.ndm.odm.oem.OemParser;
33 import org.orekit.files.ccsds.ndm.odm.omm.OmmParser;
34 import org.orekit.files.ccsds.ndm.odm.opm.OpmParser;
35 import org.orekit.files.ccsds.ndm.tdm.IdentityConverter;
36 import org.orekit.files.ccsds.ndm.tdm.RangeUnits;
37 import org.orekit.files.ccsds.ndm.tdm.RangeUnitsConverter;
38 import org.orekit.files.ccsds.ndm.tdm.TdmParser;
39 import org.orekit.files.ccsds.utils.lexical.ParseToken;
40 import org.orekit.frames.Frame;
41 import org.orekit.time.AbsoluteDate;
42 import org.orekit.utils.IERSConventions;
43
44 /** Builder for all {@link NdmConstituent CCSDS Message} files parsers.
45 * <p>
46 * This builder can be used for building all CCSDS Messages parsers types.
47 * It is particularly useful in multi-threaded context as parsers cannot
48 * be shared between threads and thus several independent parsers must be
49 * built in this case.
50 * </p>
51 * @author Luc Maisonobe
52 * @since 11.0
53 */
54 public class ParserBuilder extends AbstractBuilder<ParserBuilder> {
55
56 /** Indicator for simple or accurate EOP interpolation. */
57 private final boolean simpleEOP;
58
59 /** Gravitational coefficient. */
60 private final double mu;
61
62 /** Default mass. */
63 private final double defaultMass;
64
65 /** Default interpolation degree. */
66 private final int defaultInterpolationDegree;
67
68 /** Behavior adopted for units that have been parsed from a CCSDS message. */
69 private final ParsedUnitsBehavior parsedUnitsBehavior;
70
71 /** Filters for parse tokens.
72 * @since 12.0
73 */
74 private final Function<ParseToken, List<ParseToken>>[] filters;
75
76 /**
77 * Maps CCSDS center and frame to a {@link Frame}.
78 *
79 * @since 13.1.5
80 */
81 private final CcsdsFrameMapper frameMapper;
82
83 /**
84 * Simple constructor.
85 * <p>
86 * This constructor creates a builder with
87 * <ul>
88 * <li>{@link #getConventions() IERS conventions} set to {@link IERSConventions#IERS_2010}</li>
89 * <li>{@link #isSimpleEOP() simple EOP} set to {@code true}</li>
90 * <li>{@link #getDataContext() data context} set to {@link DataContext#getDefault() default context}</li>
91 * <li>{@link #getMissionReferenceDate() mission reference date} set to {@code null}</li>
92 * <li>{@link #getMu() gravitational coefficient} set to {@code Double.NaN}</li>
93 * <li>{@link #getEquatorialRadius() central body equatorial radius} set to {@code Double.NaN}</li>
94 * <li>{@link #getFlattening() central body flattening} set to {@code Double.NaN}</li>
95 * <li>{@link #getDefaultMass() default mass} set to {@code Double.NaN}</li>
96 * <li>{@link #getDefaultInterpolationDegree() default interpolation degree} set to {@code 1}</li>
97 * <li>{@link #getParsedUnitsBehavior() parsed unit behavior} set to {@link ParsedUnitsBehavior#CONVERT_COMPATIBLE}</li>
98 * <li>{@link #getRangeUnitsConverter() converter for range units} set to {@link IdentityConverter}</li>
99 * </ul>
100 */
101 @DefaultDataContext
102 public ParserBuilder() {
103 this(DataContext.getDefault());
104 }
105
106 /**
107 * Simple constructor.
108 * <p>
109 * This constructor creates a builder with
110 * <ul>
111 * <li>{@link #getConventions() IERS conventions} set to {@link IERSConventions#IERS_2010}</li>
112 * <li>{@link #isSimpleEOP() simple EOP} set to {@code true}</li>
113 * <li>{@link #getMissionReferenceDate() mission reference date} set to {@code null}</li>
114 * <li>{@link #getMu() gravitational coefficient} set to {@code Double.NaN}</li>
115 * <li>{@link #getEquatorialRadius() central body equatorial radius} set to {@code Double.NaN}</li>
116 * <li>{@link #getFlattening() central body flattening} set to {@code Double.NaN}</li>
117 * <li>{@link #getDefaultMass() default mass} set to {@code Double.NaN}</li>
118 * <li>{@link #getDefaultInterpolationDegree() default interpolation degree} set to {@code 1}</li>
119 * <li>{@link #getParsedUnitsBehavior() parsed unit behavior} set to {@link ParsedUnitsBehavior#CONVERT_COMPATIBLE}</li>
120 * <li>{@link #getRangeUnitsConverter() converter for range units} set to {@link IdentityConverter}</li>
121 * </ul>
122 * @param dataContext data context used to retrieve frames, time scales, etc.
123 */
124 @SuppressWarnings("unchecked")
125 public ParserBuilder(final DataContext dataContext) {
126 this(IERSConventions.IERS_2010, Double.NaN, Double.NaN, dataContext,
127 null, new IdentityConverter(), true, Double.NaN, Double.NaN,
128 1, ParsedUnitsBehavior.CONVERT_COMPATIBLE,
129 (Function<ParseToken, List<ParseToken>>[]) Array.newInstance(Function.class, 0),
130 new OrekitCcsdsFrameMapper());
131 }
132
133 /** Complete constructor.
134 * @param conventions IERS Conventions
135 * @param equatorialRadius central body equatorial radius
136 * @param flattening central body flattening
137 * @param dataContext used to retrieve frames, time scales, etc.
138 * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
139 * @param rangeUnitsConverter converter for {@link RangeUnits#RU Range Units}
140 * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
141 * @param mu gravitational coefficient
142 * @param defaultMass default mass
143 * @param defaultInterpolationDegree default interpolation degree
144 * @param parsedUnitsBehavior behavior to adopt for handling parsed units
145 * @param filters filters to apply to parse tokens
146 * @param frameMapper for creating a {@link Frame}.
147 * @since 13.1.5
148 */
149 private ParserBuilder(final IERSConventions conventions,
150 final double equatorialRadius, final double flattening,
151 final DataContext dataContext, final AbsoluteDate missionReferenceDate,
152 final RangeUnitsConverter rangeUnitsConverter,
153 final boolean simpleEOP, final double mu,
154 final double defaultMass, final int defaultInterpolationDegree,
155 final ParsedUnitsBehavior parsedUnitsBehavior,
156 final Function<ParseToken, List<ParseToken>>[] filters,
157 final CcsdsFrameMapper frameMapper) {
158 super(conventions, equatorialRadius, flattening, dataContext, missionReferenceDate, rangeUnitsConverter);
159 this.simpleEOP = simpleEOP;
160 this.mu = mu;
161 this.defaultMass = defaultMass;
162 this.defaultInterpolationDegree = defaultInterpolationDegree;
163 this.parsedUnitsBehavior = parsedUnitsBehavior;
164 this.filters = filters.clone();
165 this.frameMapper = frameMapper;
166 }
167
168 /** {@inheritDoc} */
169 @Override
170 protected ParserBuilder create(final IERSConventions newConventions,
171 final double newEquatorialRadius, final double newFlattening,
172 final DataContext newDataContext,
173 final AbsoluteDate newMissionReferenceDate, final RangeUnitsConverter newRangeUnitsConverter) {
174 return new ParserBuilder(newConventions, newEquatorialRadius, newFlattening, newDataContext,
175 newMissionReferenceDate, newRangeUnitsConverter, simpleEOP, mu,
176 defaultMass, defaultInterpolationDegree, parsedUnitsBehavior, filters,
177 getFrameMapper());
178 }
179
180 /** Set up flag for ignoring tidal effects when interpolating EOP.
181 * @param newSimpleEOP true if tidal effects are ignored when interpolating EOP
182 * @return a new builder with updated configuration (the instance is not changed)
183 */
184 public ParserBuilder withSimpleEOP(final boolean newSimpleEOP) {
185 return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
186 getMissionReferenceDate(), getRangeUnitsConverter(), newSimpleEOP, getMu(), getDefaultMass(),
187 getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters(),
188 getFrameMapper());
189 }
190
191 /** Check if tidal effects are ignored when interpolating EOP.
192 * @return true if tidal effects are ignored when interpolating EOP
193 */
194 public boolean isSimpleEOP() {
195 return simpleEOP;
196 }
197
198 /** Set up the gravitational coefficient.
199 * @param newMu gravitational coefficient
200 * @return a new builder with updated configuration (the instance is not changed)
201 */
202 public ParserBuilder withMu(final double newMu) {
203 return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
204 getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), newMu, getDefaultMass(),
205 getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters(),
206 getFrameMapper());
207 }
208
209 /** Get the gravitational coefficient.
210 * @return gravitational coefficient
211 */
212 public double getMu() {
213 return mu;
214 }
215
216 /** Set up the default mass.
217 * <p>
218 * The default mass is used only by {@link OpmParser}.
219 * </p>
220 * @param newDefaultMass default mass
221 * @return a new builder with updated configuration (the instance is not changed)
222 */
223 public ParserBuilder withDefaultMass(final double newDefaultMass) {
224 return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
225 getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), getMu(), newDefaultMass,
226 getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters(),
227 getFrameMapper());
228 }
229
230 /** Get the default mass.
231 * @return default mass
232 */
233 public double getDefaultMass() {
234 return defaultMass;
235 }
236
237 /** Set up the default interpolation degree.
238 * <p>
239 * The default interpolation degree is used only by {@link AemParser}
240 * and {@link OemParser}.
241 * </p>
242 * @param newDefaultInterpolationDegree default interpolation degree
243 * @return a new builder with updated configuration (the instance is not changed)
244 */
245 public ParserBuilder withDefaultInterpolationDegree(final int newDefaultInterpolationDegree) {
246 return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
247 getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), getMu(), getDefaultMass(),
248 newDefaultInterpolationDegree, getParsedUnitsBehavior(), getFilters(),
249 getFrameMapper());
250 }
251
252 /** Get the default interpolation degree.
253 * @return default interpolation degree
254 */
255 public int getDefaultInterpolationDegree() {
256 return defaultInterpolationDegree;
257 }
258
259 /** Set up the behavior to adopt for handling parsed units.
260 * @param newParsedUnitsBehavior behavior to adopt for handling parsed units
261 * @return a new builder with updated configuration (the instance is not changed)
262 */
263 public ParserBuilder withParsedUnitsBehavior(final ParsedUnitsBehavior newParsedUnitsBehavior) {
264 return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
265 getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), getMu(), getDefaultMass(),
266 getDefaultInterpolationDegree(), newParsedUnitsBehavior, getFilters(),
267 getFrameMapper());
268 }
269
270 /** Get the behavior to adopt for handling parsed units.
271 * @return behavior to adopt for handling parsed units
272 */
273 public ParsedUnitsBehavior getParsedUnitsBehavior() {
274 return parsedUnitsBehavior;
275 }
276
277 /** Add a filter for parsed tokens.
278 * <p>
279 * This filter allows to change parsed tokens. This method can be called several times,
280 * once for each filter to set up. The filters are always applied in the order they were set.
281 * There are several use cases for this feature.
282 * </p>
283 * <p>
284 * The first use case is to allow parsing malformed CCSDS messages with some known
285 * discrepancies that can be fixed. One real life example (the one that motivated the
286 * development of this feature) is OMM files in XML format that add an empty
287 * OBJECT_ID. This could be fixed by setting a filter as follows:
288 * </p>
289 * <pre>{@code
290 * Omm omm = new ParserBuilder().
291 * withFilter(token -> {
292 * if ("OBJECT_ID".equals(token.getName()) &&
293 * (token.getRawContent() == null || token.getRawContent().isEmpty())) {
294 * // replace null/empty entries with "unknown"
295 * return Collections.singletonList(new ParseToken(token.getType(), token.getName(),
296 * "unknown", token.getUnits(),
297 * token.getLineNumber(), token.getFileName()));
298 * } else {
299 * return Collections.singletonList(token);
300 * }
301 * }).
302 * buildOmmParser().
303 * parseMessage(message);
304 * }</pre>
305 * <p>
306 * A second use case is to remove unwanted data. For example in order to remove all user-defined data
307 * one could use:
308 * </p>
309 * <pre>{@code
310 * Omm omm = new ParserBuilder().
311 * withFilter(token -> {
312 * if (token.getName().startsWith("USER_DEFINED")) {
313 * return Collections.emptyList();
314 * } else {
315 * return Collections.singletonList(token);
316 * }
317 * }).
318 * buildOmmmParser().
319 * parseMessage(message);
320 * }</pre>
321 * <p>
322 * A third use case is to add data not originally present in the file. For example in order
323 * to add a generated ODM V3 message id to an ODM V2 message that lacks it, one could do:
324 * </p>
325 * <pre>{@code
326 * final String myMessageId = ...; // this could be computed from a counter, or a SHA256 digest, or some metadata
327 * Omm omm = new ParserBuilder()
328 * withFilter(token -> {
329 * if ("CCSDS_OMM_VERS".equals(token.getName())) {
330 * // enforce ODM V3
331 * return Collections.singletonList(new ParseToken(token.getType(), token.getName(),
332 * "3.0", token.getUnits(),
333 * token.getLineNumber(), token.getFileName()));
334 * } else {
335 * return Collections.singletonList(token);
336 * }
337 * }).
338 * withFilter(token -> {
339 * if ("ORIGINATOR".equals(token.getName())) {
340 * // add generated message ID after ORIGINATOR entry
341 * return Arrays.asList(token,
342 * new ParseToken(TokenType.ENTRY, "MESSAGE_ID",
343 * myMessageId, null,
344 * -1, token.getFileName()));
345 * } else {
346 * return Collections.singletonList(token);
347 * }
348 * }).
349 * buildOmmmParser().
350 * parseMessage(message);
351 * }</pre>
352 * @param filter token filter to add
353 * @return a new builder with updated configuration (the instance is not changed)
354 * @since 12.0
355 */
356 public ParserBuilder withFilter(final Function<ParseToken, List<ParseToken>> filter) {
357
358 // populate new filters array
359 @SuppressWarnings("unchecked")
360 final Function<ParseToken, List<ParseToken>>[] newFilters =
361 (Function<ParseToken, List<ParseToken>>[]) Array.newInstance(Function.class, filters.length + 1);
362 System.arraycopy(filters, 0, newFilters, 0, filters.length);
363 newFilters[filters.length] = filter;
364
365 return new ParserBuilder(getConventions(), getEquatorialRadius(), getFlattening(), getDataContext(),
366 getMissionReferenceDate(), getRangeUnitsConverter(), isSimpleEOP(), getMu(), getDefaultMass(),
367 getDefaultInterpolationDegree(), getParsedUnitsBehavior(),
368 newFilters, getFrameMapper());
369
370 }
371
372 /** Get the filters to apply to parse tokens.
373 * @return filters to apply to parse tokens
374 * @since 12.0
375 */
376 public Function<ParseToken, List<ParseToken>>[] getFilters() {
377 return filters.clone();
378 }
379
380 /**
381 * Get the mapping between CCSDS NDM center and frame and a {@link Frame}.
382 *
383 * @return the frame mapper.
384 * @since 13.1.5
385 */
386 public CcsdsFrameMapper getFrameMapper() {
387 return frameMapper;
388 }
389
390 /**
391 * Set the mapping between CCSDS NDM center and frame and a {@link Frame}.
392 *
393 * @param newFrameMapper the frame mapper.
394 * @return a new builder with updated configuration (the instance is not
395 * changed)
396 * @since 13.1.5
397 */
398 public ParserBuilder withFrameMapper(final CcsdsFrameMapper newFrameMapper) {
399 return new ParserBuilder(getConventions(), getEquatorialRadius(),
400 getFlattening(), getDataContext(), getMissionReferenceDate(),
401 getRangeUnitsConverter(), isSimpleEOP(), getMu(),
402 getDefaultMass(), getDefaultInterpolationDegree(),
403 getParsedUnitsBehavior(), getFilters(), newFrameMapper);
404 }
405
406 /** Build a parser for {@link org.orekit.files.ccsds.ndm.Ndm Navigation Data Messages}.
407 * @return a new parser
408 */
409 public NdmParser buildNdmParser() {
410 return new NdmParser(this, getFilters());
411 }
412
413 /** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.opm.Opm Orbit Parameters Messages}.
414 * @return a new parser
415 */
416 public OpmParser buildOpmParser() {
417 return new OpmParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
418 getMu(), getDefaultMass(), getParsedUnitsBehavior(), getFilters(),
419 getFrameMapper());
420 }
421
422 /** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.omm.Omm Orbit Mean elements Messages}.
423 * @return a new parser
424 */
425 public OmmParser buildOmmParser() {
426 return new OmmParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
427 getMu(), getDefaultMass(), getParsedUnitsBehavior(), getFilters(),
428 getFrameMapper());
429 }
430
431 /** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.oem.Oem Orbit Ephemeris Messages}.
432 * @return a new parser
433 */
434 public OemParser buildOemParser() {
435 return new OemParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
436 getMu(), getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters(),
437 getFrameMapper());
438 }
439
440 /** Build a parser for {@link org.orekit.files.ccsds.ndm.odm.ocm.Ocm Orbit Comprehensive Messages}.
441 * @return a new parser
442 */
443 public OcmParser buildOcmParser() {
444 return new OcmParser(getConventions(), getEquatorialRadius(), getFlattening(),
445 isSimpleEOP(), getDataContext(), getMu(),
446 getParsedUnitsBehavior(), getFilters(),
447 getFrameMapper());
448 }
449
450 /** Build a parser for {@link org.orekit.files.ccsds.ndm.adm.apm.Apm Attitude Parameters Messages}.
451 * @return a new parser
452 */
453 public ApmParser buildApmParser() {
454 return new ApmParser(getConventions(), isSimpleEOP(), getDataContext(),
455 getMissionReferenceDate(), getParsedUnitsBehavior(), getFilters(),
456 getFrameMapper());
457 }
458
459 /** Build a parser for {@link org.orekit.files.ccsds.ndm.adm.aem.Aem Attitude Ephemeris Messages}.
460 * @return a new parser
461 */
462 public AemParser buildAemParser() {
463 return new AemParser(getConventions(), isSimpleEOP(), getDataContext(), getMissionReferenceDate(),
464 getDefaultInterpolationDegree(), getParsedUnitsBehavior(), getFilters(),
465 getFrameMapper());
466 }
467
468 /** Build a parser for {@link org.orekit.files.ccsds.ndm.adm.acm.Acm Attitude Comprehensive Messages}.
469 * @return a new parser
470 * @since 12.0
471 */
472 public AcmParser buildAcmParser() {
473 return new AcmParser(getConventions(), isSimpleEOP(), getDataContext(),
474 getParsedUnitsBehavior(), getFilters(),
475 getFrameMapper());
476 }
477
478 /** Build a parser for {@link org.orekit.files.ccsds.ndm.tdm.Tdm Tracking Data Messages}.
479 * @return a new parser
480 */
481 public TdmParser buildTdmParser() {
482 return new TdmParser(getConventions(), isSimpleEOP(), getDataContext(),
483 getParsedUnitsBehavior(), getRangeUnitsConverter(), getFilters(),
484 getFrameMapper());
485 }
486
487 /** Build a parser for {@link org.orekit.files.ccsds.ndm.cdm.Cdm Conjunction Data Messages}.
488 * @return a new parser
489 */
490 public CdmParser buildCdmParser() {
491 return new CdmParser(getConventions(), isSimpleEOP(), getDataContext(),
492 getParsedUnitsBehavior(), getFilters(),
493 getFrameMapper());
494 }
495
496 }