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