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.lexical;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  import java.util.Locale;
23  import java.util.regex.Pattern;
24  import java.util.stream.Collectors;
25  import java.util.stream.Stream;
26  
27  import org.hipparchus.geometry.euclidean.threed.RotationOrder;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.orekit.bodies.CelestialBodies;
30  import org.orekit.bodies.CelestialBody;
31  import org.orekit.errors.OrekitException;
32  import org.orekit.errors.OrekitMessages;
33  import org.orekit.files.ccsds.definitions.BodyFacade;
34  import org.orekit.files.ccsds.definitions.CelestialBodyFrame;
35  import org.orekit.files.ccsds.definitions.CenterName;
36  import org.orekit.files.ccsds.definitions.FrameFacade;
37  import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
38  import org.orekit.files.ccsds.definitions.SpacecraftBodyFrame;
39  import org.orekit.files.ccsds.definitions.TimeSystem;
40  import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
41  import org.orekit.files.ccsds.ndm.cdm.Maneuvrable;
42  import org.orekit.files.ccsds.utils.ContextBinding;
43  import org.orekit.time.AbsoluteDate;
44  import org.orekit.utils.units.Unit;
45  
46  /** Token occurring during CCSDS file parsing.
47   * <p>
48   * Parse tokens correspond to:
49   * <ul>
50   *   <li>bloc or entry start</li>
51   *   <li>entry content</li>
52   *   <li>bloc or entry end</li>
53   *   <li>raw lines</li>
54   * </ul>
55   * @see MessageParser
56   * @author Luc Maisonobe
57   * @since 11.0
58   */
59  public class ParseToken {
60  
61      /** Pattern for dash. */
62      private static final Pattern DASH = Pattern.compile("-");
63  
64      /** Pattern for spaces. */
65      private static final Pattern SPACE = Pattern.compile("\\p{Space}+");
66  
67      /** Pattern for splitting comma-separated lists. */
68      private static final Pattern SPLIT_AT_COMMAS = Pattern.compile("\\p{Space}*,\\p{Space}*");
69  
70      /** Pattern for splitting comma-separated lists with no space in between. */
71      private static final Pattern SPLIT_AT_COMMAS_NO_SPACE = Pattern.compile(",");
72  
73      /** Pattern for true boolean value. */
74      private static final Pattern BOOLEAN_TRUE = Pattern.compile("(?:yes)|(?:true)",
75                                                                  Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
76  
77      /** Pattern for false boolean value. */
78      private static final Pattern BOOLEAN_FALSE = Pattern.compile("(?:no)|(?:false)",
79                                                                   Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
80  
81      /** Type of the token. */
82      private TokenType type;
83  
84      /** Name of the entry. */
85      private final String name;
86  
87      /** Entry content. */
88      private final String content;
89  
90      /** Units of the entry. */
91      private final Unit units;
92  
93      /** Number of the line from which pair is extracted. */
94      private final int lineNumber;
95  
96      /** Name of the file. */
97      private final String fileName;
98  
99      /** Simple constructor.
100      * @param type type of the token
101      * @param name name of the block or entry
102      * @param content entry content
103      * @param units units of the entry
104      * @param lineNumber number of the line in the CCSDS data message
105      * @param fileName name of the file
106      */
107     public ParseToken(final TokenType type, final String name, final String content, final Unit units,
108                       final int lineNumber, final String fileName) {
109         this.type       = type;
110         this.name       = name;
111         this.content    = content;
112         this.units      = units;
113         this.lineNumber = lineNumber;
114         this.fileName   = fileName;
115     }
116 
117     /** Get the type of the token.
118      * @return type of the token
119      */
120     public TokenType getType() {
121         return type;
122     }
123 
124     /** Get the name of the block or entry.
125      * @return name of the block or entry
126      */
127     public String getName() {
128         return name;
129     }
130 
131     /** Get the raw content of the entry.
132      * @return entry raw content
133      */
134     public String getRawContent() {
135         return content;
136     }
137 
138     /** Get the content of the entry.
139      * <p>
140      * Free-text strings are normalized by replacing all occurrences
141      * of '_' with space, and collapsing several spaces as one space only.
142      * </p>
143      * @return entry content
144      */
145     public String getContentAsNormalizedString() {
146         return SPACE.matcher(content.replace('_', ' ')).replaceAll(" ").trim();
147     }
148 
149     /** Get the content of the entry as a list of free-text strings.
150      * @return content of the entry as a list of free-test strings
151      * @since 12.0
152      */
153     public List<String> getContentAsFreeTextList() {
154         return Arrays.asList(SPLIT_AT_COMMAS.split(getRawContent()));
155     }
156 
157     /** Get the content of the entry as a list of normalized strings.
158      * <p>
159      * Normalization is performed by replacing all occurrences
160      * of '_' with space, and collapsing several spaces as one space only.
161      * </p>
162      * @return content of the entry as a list of free-test strings
163      */
164     public List<String> getContentAsNormalizedList() {
165         return Arrays.asList(SPLIT_AT_COMMAS.split(getContentAsNormalizedString()));
166     }
167 
168     /** Get the content of the entry as normalized and uppercased.
169      * @return entry normalized and uppercased content
170      */
171     public String getContentAsUppercaseString() {
172         return getContentAsNormalizedString().toUpperCase(Locale.US);
173     }
174 
175     /** Get the content of the entry as a list of normalized and uppercased strings.
176      * @return content of the entry as a list of normalized and uppercased strings
177      */
178     public List<String> getContentAsUppercaseList() {
179         return Arrays.asList(SPLIT_AT_COMMAS.split(getContentAsUppercaseString()));
180     }
181 
182     /** Get the content of the entry as an enum.
183      * @param cls enum class
184      * @param <T> type of the enum
185      * @return entry content
186      */
187     public <T extends Enum<T>> T getContentAsEnum(final Class<T> cls) {
188         return toEnum(cls, getRawContent());
189     }
190 
191     /** Get the content of the entry as a list of enum.
192      * @param cls enum class
193      * @param <T> type of the enum
194      * @return entry content
195      */
196     public <T extends Enum<T>> List<T> getContentAsEnumList(final Class<T> cls) {
197         final String[] elements = SPLIT_AT_COMMAS.split(getRawContent());
198         final List<T> list = new ArrayList<>(elements.length);
199         for (String element : elements) {
200             list.add(toEnum(cls, element));
201         }
202         return list;
203     }
204 
205     /** Get the content of the entry as a double.
206      * @return content as a double
207      */
208     public double getContentAsDouble() {
209         try {
210             return Double.parseDouble(content);
211         } catch (NumberFormatException nfe) {
212             throw generateException(nfe);
213         }
214     }
215 
216     /** Get the content of the entry as a vector.
217      * @return content as a vector
218      */
219     public Vector3D getContentAsVector() {
220         try {
221             final String[] fields = SPACE.split(content);
222             if (fields.length == 3) {
223                 return new Vector3D(Double.parseDouble(fields[0]),
224                                     Double.parseDouble(fields[1]),
225                                     Double.parseDouble(fields[2]));
226             }
227         } catch (NumberFormatException nfe) {
228             // ignored, error handled below, together with wrong number of fields
229         }
230         throw generateException(null);
231     }
232 
233     /** Get the content of the entry as a boolean.
234      * @return content as a boolean
235      */
236     public boolean getContentAsBoolean() {
237         if (BOOLEAN_TRUE.matcher(content).matches()) {
238             return true;
239         } else if (BOOLEAN_FALSE.matcher(content).matches()) {
240             return false;
241         } else {
242             throw generateException(null);
243         }
244     }
245 
246     /** Get the content of the entry as an integer.
247      * @return content as an integer
248      */
249     public int getContentAsInt() {
250         try {
251             return Integer.parseInt(content);
252         } catch (NumberFormatException nfe) {
253             throw generateException(nfe);
254         }
255     }
256 
257     /** Get the content of the entry as an uppercase character.
258      * @return content as an uppercase character
259      */
260     public char getContentAsUppercaseCharacter() {
261         try {
262             return getContentAsUppercaseString().charAt(0);
263         } catch (NumberFormatException nfe) {
264             throw generateException(nfe);
265         }
266     }
267 
268     /** Get the units.
269      * @return units of the entry (may be null)
270      */
271     public Unit getUnits() {
272         return units;
273     }
274 
275     /** Get the number of the line in the CCSDS data message.
276      * @return number of the line in the CCSDS data message
277      */
278     public int getLineNumber() {
279         return lineNumber;
280     }
281 
282     /** Get the name of the file.
283      * @return name of the file
284      */
285     public String getFileName() {
286         return fileName;
287     }
288 
289     /** Process the content as a normalized string.
290      * @param consumer consumer of the normalized string
291      * @return always returns {@code true}
292      * @see #processAsUppercaseString(StringConsumer)
293      */
294     public boolean processAsNormalizedString(final StringConsumer consumer) {
295         if (type == TokenType.ENTRY) {
296             consumer.accept(getContentAsNormalizedString());
297         }
298         return true;
299     }
300 
301     /** Process the content as a normalized uppercase string.
302      * @param consumer consumer of the normalized uppercase string
303      * @return always returns {@code true}
304      * @see #processAsNormalizedString(StringConsumer)
305      */
306     public boolean processAsUppercaseString(final StringConsumer consumer) {
307         if (type == TokenType.ENTRY) {
308             consumer.accept(getContentAsUppercaseString());
309         }
310         return true;
311     }
312 
313     /** Process the content as an indexed normalized string.
314      * @param index index
315      * @param consumer consumer of the indexed normalized string
316      * @return always returns {@code true}
317      */
318     public boolean processAsIndexedNormalizedString(final int index, final IndexedStringConsumer consumer) {
319         if (type == TokenType.ENTRY) {
320             consumer.accept(index, getContentAsNormalizedString());
321         }
322         return true;
323     }
324 
325     /** Process the content as an indexed normalized uppercase string.
326      * @param index index
327      * @param consumer consumer of the indexed normalized uppercase string
328      * @return always returns {@code true}
329      */
330     public boolean processAsIndexedUppercaseString(final int index, final IndexedStringConsumer consumer) {
331         if (type == TokenType.ENTRY) {
332             consumer.accept(index, getContentAsUppercaseString());
333         }
334         return true;
335     }
336 
337     /** Process the content as a list of free-text strings.
338      * @param consumer consumer of the free-text strings list
339      * @return always returns {@code true}
340      * @since 12.0
341      */
342     public boolean processAsFreeTextList(final StringListConsumer consumer) {
343         if (type == TokenType.ENTRY) {
344             consumer.accept(getContentAsFreeTextList());
345         }
346         return true;
347     }
348 
349     /** Process the content as a list of normalized strings.
350      * @param consumer consumer of the normalized strings list
351      * @return always returns {@code true}
352      */
353     public boolean processAsNormalizedList(final StringListConsumer consumer) {
354         if (type == TokenType.ENTRY) {
355             consumer.accept(getContentAsNormalizedList());
356         }
357         return true;
358     }
359 
360     /** Process the content as a list of normalized uppercase strings.
361      * @param consumer consumer of the normalized uppercase strings list
362      * @return always returns {@code true}
363      */
364     public boolean processAsUppercaseList(final StringListConsumer consumer) {
365         if (type == TokenType.ENTRY) {
366             consumer.accept(getContentAsUppercaseList());
367         }
368         return true;
369     }
370 
371     /** Process the content as an enum.
372      * @param cls enum class
373      * @param consumer consumer of the enum
374      * @param <T> type of the enum
375      * @return always returns {@code true}
376      */
377     public <T extends Enum<T>> boolean processAsEnum(final Class<T> cls, final EnumConsumer<T> consumer) {
378         if (type == TokenType.ENTRY) {
379             consumer.accept(getContentAsEnum(cls));
380         }
381         return true;
382     }
383 
384     /** Process the content as a list of enums.
385      * @param cls enum class
386      * @param consumer consumer of the enums list
387      * @param <T> type of the enum
388      * @return always returns {@code true}
389      */
390     public <T extends Enum<T>> boolean processAsEnumsList(final Class<T> cls, final EnumListConsumer<T> consumer) {
391         if (type == TokenType.ENTRY) {
392             consumer.accept(getContentAsEnumList(cls));
393         }
394         return true;
395     }
396 
397     /** Process the content as a boolean.
398      * @param consumer consumer of the boolean
399      * @return always returns {@code true}
400      */
401     public boolean processAsBoolean(final BooleanConsumer consumer) {
402         if (type == TokenType.ENTRY) {
403             consumer.accept(getContentAsBoolean());
404         }
405         return true;
406     }
407 
408     /** Process the content as an integer.
409      * @param consumer consumer of the integer
410      * @return always returns {@code true}
411      */
412     public boolean processAsInteger(final IntConsumer consumer) {
413         if (type == TokenType.ENTRY) {
414             consumer.accept(getContentAsInt());
415         }
416         return true;
417     }
418 
419     /** Process the content as an indexed integer.
420      * @param index index
421      * @param consumer consumer of the integer
422      * @return always returns {@code true}
423      * @since 12.0
424      */
425     public boolean processAsIndexedInteger(final int index, final IndexedIntConsumer consumer) {
426         if (type == TokenType.ENTRY) {
427             consumer.accept(index, getContentAsInt());
428         }
429         return true;
430     }
431 
432     /** Process the content as an array of integers. No spaces between commas are allowed.
433      * @param consumer consumer of the array
434      * @return always returns {@code true}
435      */
436     public boolean processAsIntegerArrayNoSpace(final IntegerArrayConsumer consumer) {
437         try {
438             if (type == TokenType.ENTRY) {
439                 // Do not allow spaces
440                 final String[] fields = SPLIT_AT_COMMAS_NO_SPACE.split(getRawContent());
441                 final int[] integers = new int[fields.length];
442                 for (int i = 0; i < fields.length; ++i) {
443                     integers[i] = Integer.parseInt(fields[i]);
444                 }
445                 consumer.accept(integers);
446             }
447             return true;
448         } catch (NumberFormatException nfe) {
449             throw generateException(nfe);
450         }
451     }
452 
453     /** Process the content as an array of integers. Spaces are replaced by commas.
454      * @param consumer consumer of the array
455      * @return always returns {@code true}
456      */
457     public boolean processAsIntegerArray(final IntegerArrayConsumer consumer) {
458         try {
459             if (type == TokenType.ENTRY) {
460                 final String[] fields = SPACE.split(getRawContent());
461                 final int[] integers = new int[fields.length];
462                 for (int i = 0; i < fields.length; ++i) {
463                     integers[i] = Integer.parseInt(fields[i]);
464                 }
465                 consumer.accept(integers);
466             }
467             return true;
468         } catch (NumberFormatException nfe) {
469             throw generateException(nfe);
470         }
471     }
472 
473     /** Process the content as a normalized character.
474      * @param consumer consumer of the normalized character
475      * @return always returns {@code true}
476      */
477     public boolean processAsNormalizedCharacter(final CharConsumer consumer) {
478         if (type == TokenType.ENTRY) {
479             consumer.accept(getContentAsUppercaseCharacter());
480         }
481         return true;
482     }
483 
484     /** Process the content as a double.
485      * @param standard units of parsed content as specified by CCSDS standard
486      * @param behavior behavior to adopt for parsed unit
487      * @param consumer consumer of the double
488      * @return always returns {@code true}
489      */
490     public boolean processAsDouble(final Unit standard, final ParsedUnitsBehavior behavior,
491                                    final DoubleConsumer consumer) {
492         if (type == TokenType.ENTRY) {
493             consumer.accept(behavior.select(getUnits(), standard).toSI(getContentAsDouble()));
494         }
495         return true;
496     }
497 
498     /** Process the content as a labeled double.
499      * @param label label
500      * @param standard units of parsed content as specified by CCSDS standard
501      * @param behavior behavior to adopt for parsed unit
502      * @param consumer consumer of the indexed double
503      * @return always returns {@code true}
504      */
505     public boolean processAsLabeledDouble(final char label,
506                                           final Unit standard, final ParsedUnitsBehavior behavior,
507                                           final LabeledDoubleConsumer consumer) {
508         if (type == TokenType.ENTRY) {
509             consumer.accept(label, behavior.select(getUnits(), standard).toSI(getContentAsDouble()));
510         }
511         return true;
512     }
513 
514     /** Process the content as an indexed double.
515      * @param i index
516      * @param standard units of parsed content as specified by CCSDS standard
517      * @param behavior behavior to adopt for parsed unit
518      * @param consumer consumer of the indexed double
519      * @return always returns {@code true}
520      */
521     public boolean processAsIndexedDouble(final int i,
522                                           final Unit standard, final ParsedUnitsBehavior behavior,
523                                           final IndexedDoubleConsumer consumer) {
524         if (type == TokenType.ENTRY) {
525             consumer.accept(i, behavior.select(getUnits(), standard).toSI(getContentAsDouble()));
526         }
527         return true;
528     }
529 
530     /** Process the content as a doubly-indexed double.
531      * @param i first index
532      * @param j second index
533      * @param standard units of parsed content as specified by CCSDS standard
534      * @param behavior behavior to adopt for parsed unit
535      * @param consumer consumer of the doubly-indexed double
536      * @return always returns {@code true}
537      */
538     public boolean processAsDoublyIndexedDouble(final int i, final int j,
539                                                 final Unit standard, final ParsedUnitsBehavior behavior,
540                                                 final DoublyIndexedDoubleConsumer consumer) {
541         if (type == TokenType.ENTRY) {
542             consumer.accept(i, j, behavior.select(getUnits(), standard).toSI(getContentAsDouble()));
543         }
544         return true;
545     }
546 
547     /** Process the content as an array of doubles.
548      * @param standard units of parsed content as specified by CCSDS standard
549      * @param behavior behavior to adopt for parsed unit
550      * @param consumer consumer of the array
551      * @return always returns {@code true}
552      * @since 12.0
553      */
554     public boolean processAsDoubleArray(final Unit standard, final ParsedUnitsBehavior behavior,
555                                         final DoubleArrayConsumer consumer) {
556         try {
557             if (type == TokenType.ENTRY) {
558                 final String[] fields = SPACE.split(getRawContent());
559                 final double[] doubles = new double[fields.length];
560                 for (int i = 0; i < fields.length; ++i) {
561                     doubles[i] = behavior.select(getUnits(), standard).toSI(Double.parseDouble(fields[i]));
562                 }
563                 consumer.accept(doubles);
564             }
565             return true;
566         } catch (NumberFormatException nfe) {
567             throw generateException(nfe);
568         }
569     }
570 
571     /** Process the content as an indexed double array.
572      * @param index index
573      * @param standard units of parsed content as specified by CCSDS standard
574      * @param behavior behavior to adopt for parsed unit
575      * @param consumer consumer of the indexed double array
576      * @return always returns {@code true}
577      * @since 12.0
578      */
579     public boolean processAsIndexedDoubleArray(final int index,
580                                                final Unit standard, final ParsedUnitsBehavior behavior,
581                                                final IndexedDoubleArrayConsumer consumer) {
582         if (type == TokenType.ENTRY) {
583             final String[] fields = SPACE.split(content);
584             final double[] values = new double[fields.length];
585             for (int i = 0; i < fields.length; ++i) {
586                 values[i] = behavior.select(getUnits(), standard).toSI(Double.parseDouble(fields[i]));
587             }
588             consumer.accept(index, values);
589         }
590         return true;
591     }
592 
593     /** Process the content as a vector.
594      * @param standard units of parsed content as specified by CCSDS standard
595      * @param behavior behavior to adopt for parsed unit
596      * @param consumer consumer of the vector
597      * @return always returns {@code true} (or throws an exception)
598      */
599     public boolean processAsVector(final Unit standard, final ParsedUnitsBehavior behavior,
600                                    final VectorConsumer consumer) {
601         if (type == TokenType.ENTRY) {
602             final double scale = behavior.select(getUnits(), standard).getScale();
603             consumer.accept(getContentAsVector().scalarMultiply(scale));
604         }
605         return true;
606     }
607 
608     /** Process the content as a date.
609      * @param consumer consumer of the date
610      * @param context context binding
611      * @return always returns {@code true} (or throws an exception)
612      */
613     public boolean processAsDate(final DateConsumer consumer, final ContextBinding context) {
614         if (type == TokenType.ENTRY) {
615             if (context.getTimeSystem() == null) {
616                 throw new OrekitException(OrekitMessages.CCSDS_TIME_SYSTEM_NOT_READ_YET,
617                                           getLineNumber(), getFileName());
618             }
619             consumer.accept(context.getTimeSystem().getConverter(context).parse(content));
620         }
621         return true;
622     }
623 
624     /** Process the content as a time system.
625      * @param consumer consumer of the time system
626      * @return always returns {@code true} (or throws an exception)
627      */
628     public boolean processAsTimeSystem(final TimeSystemConsumer consumer) {
629         if (type == TokenType.ENTRY) {
630             consumer.accept(TimeSystem.parse(getContentAsUppercaseString()));
631         }
632         return true;
633     }
634 
635     /** Process the content as a frame.
636      * @param consumer consumer of the frame
637      * @param context context binding
638      * @param allowCelestial if true, {@link CelestialBodyFrame} are allowed
639      * @param allowOrbit if true, {@link OrbitRelativeFrame} are allowed
640      * @param allowSpacecraft if true, {@link SpacecraftBodyFrame} are allowed
641      * @return always returns {@code true}
642      */
643     public boolean processAsFrame(final FrameConsumer consumer, final ContextBinding context,
644                                   final boolean allowCelestial, final boolean allowOrbit,
645                                   final boolean allowSpacecraft) {
646         if (type == TokenType.ENTRY) {
647             try {
648                 consumer.accept(FrameFacade.parse(DASH.
649                                                   matcher(getContentAsUppercaseString()).
650                                                   replaceAll("").
651                                                   replace(' ', '_'),
652                                                   context.getConventions(),
653                                                   context.isSimpleEOP(), context.getDataContext(),
654                                                   allowCelestial, allowOrbit, allowSpacecraft));
655             } catch (OrekitException oe) {
656                 throw generateException(oe);
657             }
658         }
659         return true;
660     }
661 
662     /** Process the content as a body center.
663      * @param consumer consumer of the body center
664      * @param celestialBodies factory for celestial bodies
665      * @return always returns {@code true}
666      */
667     public boolean processAsCenter(final CenterConsumer consumer, final CelestialBodies celestialBodies) {
668         if (type == TokenType.ENTRY) {
669             final String centerName = getContentAsUppercaseString();
670             consumer.accept(new BodyFacade(centerName, body(centerName, celestialBodies)));
671         }
672         return true;
673     }
674 
675     /** Process the content as a body center list.
676      * @param consumer consumer of the body center list
677      * @param celestialBodies factory for celestial bodies
678      * @return always returns {@code true}
679      */
680     public boolean processAsCenterList(final CenterListConsumer consumer, final CelestialBodies celestialBodies) {
681         if (type == TokenType.ENTRY) {
682             final List<BodyFacade> facades = new ArrayList<>();
683             for (final String centerName : SPLIT_AT_COMMAS.split(getContentAsUppercaseString())) {
684                 facades.add(new BodyFacade(centerName, body(centerName, celestialBodies)));
685             }
686             consumer.accept(facades);
687         }
688         return true;
689     }
690 
691     /** Process the content as a rotation sequence.
692      * @param consumer consumer of the rotation sequence
693      * @return always returns {@code true}
694      * @since 12.0
695      */
696     public boolean processAsRotationOrder(final RotationOrderConsumer consumer) {
697         if (type == TokenType.ENTRY) {
698             try {
699                 consumer.accept(RotationOrder.valueOf(getContentAsUppercaseString().
700                                                       replace('1', 'X').
701                                                       replace('2', 'Y').
702                                                       replace('3', 'Z')));
703             } catch (IllegalArgumentException iae) {
704                 throw new OrekitException(OrekitMessages.CCSDS_INVALID_ROTATION_SEQUENCE,
705                                           getContentAsUppercaseString(), getLineNumber(), getFileName());
706             }
707         }
708         return true;
709     }
710 
711     /** Process the content as a list of units.
712      * @param consumer consumer of the time scale
713      * @return always returns {@code true} (or throws an exception)
714      */
715     public boolean processAsUnitList(final UnitListConsumer consumer) {
716         if (type == TokenType.ENTRY) {
717             final String bracketed = getContentAsNormalizedString();
718             if (bracketed.charAt(0) != '[' || bracketed.charAt(bracketed.length() - 1) != ']') {
719                 throw generateException(null);
720             }
721             final String unbracketed = bracketed.substring(1, bracketed.length() - 1).trim();
722             try {
723                 consumer.accept(Stream.of(SPLIT_AT_COMMAS.split(unbracketed)).
724                                 map(Unit::parse).
725                                 collect(Collectors.toList()));
726             } catch (OrekitException oe) {
727                 // one unit is unknown
728                 throw generateException(oe);
729             }
730         }
731         return true;
732     }
733 
734      /** Process the content as free text string.
735      * @param consumer consumer of the string
736      * @return always returns {@code true}
737      */
738     public boolean processAsFreeTextString(final StringConsumer consumer) {
739         if (type == TokenType.ENTRY) {
740             consumer.accept(getRawContent());
741         }
742         return true;
743     }
744 
745     /** Process the content of the Maneuvrable enum.
746      * @param consumer consumer of the enum
747      * @return always returns {@code true}
748      */
749     public boolean processAsManeuvrableEnum(final ManeuvrableConsumer consumer) {
750         if (type == TokenType.ENTRY) {
751             consumer.accept(Maneuvrable.getEnum(getRawContent()));
752         }
753         return true;
754     }
755 
756     /** Generate a parse exception for this entry.
757      * @param cause underlying cause exception (may be null)
758      * @return exception for this entry
759      */
760     public OrekitException generateException(final Exception cause) {
761         return new OrekitException(cause, OrekitMessages.UNABLE_TO_PARSE_ELEMENT_IN_FILE,
762                                    getName(), getLineNumber(), getFileName());
763     }
764 
765     /** Get the body corresponding to a center name.
766      * @param centerName name of the center
767      * @param celestialBodies factory for celestial bodies
768      * @return celestial body corresponding to name, or null
769      */
770     private CelestialBody body(final String centerName, final CelestialBodies celestialBodies) {
771 
772         // convert some known names
773         final String canonicalValue;
774         if (centerName.equals("SOLAR SYSTEM BARYCENTER") || centerName.equals("SSB")) {
775             canonicalValue = "SOLAR_SYSTEM_BARYCENTER";
776         } else if (centerName.equals("EARTH MOON BARYCENTER") || centerName.equals("EARTH-MOON BARYCENTER") ||
777                         centerName.equals("EARTH BARYCENTER") || centerName.equals("EMB")) {
778             canonicalValue = "EARTH_MOON";
779         } else {
780             canonicalValue = centerName;
781         }
782 
783         try {
784             return CenterName.valueOf(canonicalValue).getCelestialBody(celestialBodies);
785         } catch (IllegalArgumentException iae) {
786             // ignored, we just let body set to null
787             return null;
788         }
789 
790     }
791 
792     /** Convert a value to an enum.
793      * @param cls enum class
794      * @param value value to convert to an enum
795      * @param <T> type of the enum
796      * @return enumerate corresponding to the value
797      */
798     private <T extends Enum<T>> T toEnum(final Class<T> cls, final String value) {
799         // first replace space characters
800         final String noSpace = value.replace(' ', '_');
801         try {
802             // first try without changing case, as some CCSDS enums are mixed case (like RangeUnits for TDM)
803             return Enum.valueOf(cls, noSpace);
804         } catch (IllegalArgumentException iae1) {
805             try {
806                 // second try, using more standard uppercase
807                 return Enum.valueOf(cls, noSpace.toUpperCase(Locale.US));
808             } catch (IllegalArgumentException iae2) {
809                 // use the first exception for the message
810                 throw generateException(iae1);
811             }
812         }
813     }
814 
815     /** Interface representing instance methods that consume string values. */
816     public interface StringConsumer {
817         /** Consume a string.
818          * @param value value to consume
819          */
820         void accept(String value);
821     }
822 
823     /** Interface representing instance methods that consume indexed string values. */
824     public interface IndexedStringConsumer {
825         /** Consume an indexed string.
826          * @param index index
827          * @param value value to consume
828          */
829         void accept(int index, String value);
830     }
831 
832     /** Interface representing instance methods that consume lists of strings values. */
833     public interface StringListConsumer {
834         /** Consume a list of strings.
835          * @param value value to consume
836          */
837         void accept(List<String> value);
838     }
839 
840     /** Interface representing instance methods that consume enum values.
841      * @param <T> type of the enum
842      */
843     public interface EnumConsumer<T extends Enum<T>> {
844         /** Consume an enum.
845          * @param value value to consume
846          */
847         void accept(T value);
848     }
849 
850     /** Interface representing instance methods that consume lists of enum values.
851      * @param <T> type of the enum
852      */
853     public interface EnumListConsumer<T extends Enum<T>> {
854         /** Consume an enum.
855          * @param value value to consume
856          */
857         void accept(List<T> value);
858     }
859 
860     /** Interface representing instance methods that consume boolean values. */
861     public interface BooleanConsumer {
862         /** Consume a boolean.
863          * @param value value to consume
864          */
865         void accept(boolean value);
866     }
867 
868     /** Interface representing instance methods that consume integer values. */
869     public interface IntConsumer {
870         /** Consume an integer.
871          * @param value value to consume
872          */
873         void accept(int value);
874     }
875 
876     /** Interface representing instance methods that consume indexed integer values.
877      * @since 12.0
878      */
879     public interface IndexedIntConsumer {
880         /** Consume an integer.
881          * @param index index
882          * @param value value to consume
883          */
884         void accept(int index, int value);
885     }
886 
887     /** Interface representing instance methods that consume integer array. */
888     public interface IntegerArrayConsumer {
889         /** Consume an array of integers.
890          * @param integers array of integers
891          */
892         void accept(int[] integers);
893     }
894 
895     /** Interface representing instance methods that consume character values. */
896     public interface CharConsumer {
897         /** Consume a character.
898          * @param value value to consume
899          */
900         void accept(char value);
901     }
902 
903     /** Interface representing instance methods that consume double values. */
904     public interface DoubleConsumer {
905         /** Consume a double.
906          * @param value value to consume
907          */
908         void accept(double value);
909     }
910 
911     /** Interface representing instance methods that consume labeled double values. */
912     public interface LabeledDoubleConsumer {
913         /** Consume an indexed double.
914          * @param label label
915          * @param value value to consume
916          */
917         void accept(char label, double value);
918     }
919 
920     /** Interface representing instance methods that consume indexed double values. */
921     public interface IndexedDoubleConsumer {
922         /** Consume an indexed double.
923          * @param i index
924          * @param value value to consume
925          */
926         void accept(int i, double value);
927     }
928 
929     /** Interface representing instance methods that consume doubly-indexed double values. */
930     public interface DoublyIndexedDoubleConsumer {
931         /** Consume a doubly indexed double.
932          * @param i first index
933          * @param j second index
934          * @param value value to consume
935          */
936         void accept(int i, int j, double value);
937     }
938 
939     /** Interface representing instance methods that consume double array. */
940     public interface DoubleArrayConsumer {
941         /** Consume an array of doubles.
942          * @param doubles array of doubles
943          */
944         void accept(double[] doubles);
945     }
946 
947     /** Interface representing instance methods that consume indexed double array values.
948      * @since 12.0
949      */
950     public interface IndexedDoubleArrayConsumer {
951         /** Consume an indexed double array.
952          * @param index index
953          * @param value array value to consume
954          */
955         void accept(int index, double[] value);
956     }
957 
958     /** Interface representing instance methods that consume vector values. */
959     public interface VectorConsumer {
960         /** Consume a vector.
961          * @param value value to consume
962          */
963         void accept(Vector3D value);
964     }
965 
966     /** Interface representing instance methods that consume date values. */
967     public interface DateConsumer {
968         /** Consume a date.
969          * @param value value to consume
970          */
971         void accept(AbsoluteDate value);
972     }
973 
974     /** Interface representing instance methods that consume time systems values. */
975     public interface TimeSystemConsumer {
976         /** Consume a time system.
977          * @param value value to consume
978          */
979         void accept(TimeSystem value);
980     }
981 
982     /** Interface representing instance methods that consume frame values. */
983     public interface FrameConsumer {
984         /** Consume a frame.
985          * @param frameFacade facade in front of several frames types
986          */
987         void accept(FrameFacade frameFacade);
988     }
989 
990     /** Interface representing instance methods that consume center values. */
991     public interface CenterConsumer {
992         /** Consume a body center.
993          * @param bodyFacade facade for celestial body name and body
994          */
995         void accept(BodyFacade bodyFacade);
996     }
997 
998     /** Interface representing instance methods that consume center lists. */
999     public interface CenterListConsumer {
1000         /** Consume a body center.
1001          * @param bodyFacades facades for celestial bodies name and bodies
1002          */
1003         void accept(List<BodyFacade> bodyFacades);
1004     }
1005 
1006     /** Interface representing instance methods that consume otation order values.
1007      * @since 12.0
1008      */
1009     public interface RotationOrderConsumer {
1010         /** Consume a data.
1011          * @param value value to consume
1012          */
1013         void accept(RotationOrder value);
1014     }
1015 
1016     /** Interface representing instance methods that consume units lists values. */
1017     public interface UnitListConsumer {
1018         /** Consume a list of units.
1019          * @param value value to consume
1020          */
1021         void accept(List<Unit> value);
1022     }
1023 
1024     /** Interface representing instance methods that consume Maneuvrable values. */
1025     public interface ManeuvrableConsumer {
1026         /** Consume a Maneuvrable.
1027          * @param value value to consume
1028          */
1029         void accept(Maneuvrable value);
1030     }
1031 }