ParseToken.java
/* Copyright 2002-2021 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.files.ccsds.utils.lexical;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.bodies.CelestialBodies;
import org.orekit.bodies.CelestialBody;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.definitions.BodyFacade;
import org.orekit.files.ccsds.definitions.CelestialBodyFrame;
import org.orekit.files.ccsds.definitions.CenterName;
import org.orekit.files.ccsds.definitions.FrameFacade;
import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
import org.orekit.files.ccsds.definitions.SpacecraftBodyFrame;
import org.orekit.files.ccsds.definitions.TimeSystem;
import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
import org.orekit.files.ccsds.utils.ContextBinding;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.units.Unit;
/** Token occurring during CCSDS file parsing.
* <p>
* Parse tokens correspond to:
* <ul>
* <li>bloc or entry start</li>
* <li>entry content</li>
* <li>bloc or entry end</li>
* <li>raw lines</li>
* </ul>
* @see MessageParser
* @author Luc Maisonobe
* @since 11.0
*/
public class ParseToken {
/** Pattern for dash. */
private static final Pattern DASH = Pattern.compile("-");
/** Pattern for spaces. */
private static final Pattern SPACE = Pattern.compile("\\p{Space}+");
/** Pattern for splitting comma-separated lists. */
private static final Pattern SPLIT_AT_COMMAS = Pattern.compile("\\p{Space}*,\\p{Space}*");
/** Pattern for true boolean value. */
private static final Pattern BOOLEAN_TRUE = Pattern.compile("(?:yes)|(?:true)",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
/** Pattern for false boolean value. */
private static final Pattern BOOLEAN_FALSE = Pattern.compile("(?:no)|(?:false)",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
/** Type of the token. */
private TokenType type;
/** Name of the entry. */
private final String name;
/** Entry content. */
private final String content;
/** Units of the entry. */
private final Unit units;
/** Number of the line from which pair is extracted. */
private final int lineNumber;
/** Name of the file. */
private final String fileName;
/** Simple constructor.
* @param type type of the token
* @param name name of the block or entry
* @param content entry content
* @param units units of the entry
* @param lineNumber number of the line in the CCSDS data message
* @param fileName name of the file
*/
public ParseToken(final TokenType type, final String name, final String content, final Unit units,
final int lineNumber, final String fileName) {
this.type = type;
this.name = name;
this.content = content;
this.units = units;
this.lineNumber = lineNumber;
this.fileName = fileName;
}
/** Get the type of the token.
* @return type of the token
*/
public TokenType getType() {
return type;
}
/** Get the name of the block or entry.
* @return name of the block or entry
*/
public String getName() {
return name;
}
/** Get the raw content of the entry.
* @return entry raw content
*/
public String getRawContent() {
return content;
}
/** Get the content of the entry.
* <p>
* Free-text strings are normalized by replacing all occurrences
* of '_' with space, and collapsing several spaces as one space only.
* </p>
* @return entry content
*/
public String getContentAsNormalizedString() {
return SPACE.matcher(content.replace('_', ' ')).replaceAll(" ").trim();
}
/** Get the content of the entry as a list of free-text strings.
* <p>
* Free-text strings are normalized by replacing all occurrences
* of '_' with space, and collapsing several spaces as one space only.
* </p>
* @return content of the entry as a list of free-test strings
*/
public List<String> getContentAsNormalizedList() {
return Arrays.asList(SPLIT_AT_COMMAS.split(getContentAsNormalizedString()));
}
/** Get the content of the entry as normalized and uppercased.
* @return entry normalized and uppercased content
*/
public String getContentAsUppercaseString() {
return getContentAsNormalizedString().toUpperCase(Locale.US);
}
/** Get the content of the entry as a list of normalized and uppercased strings.
* @return content of the entry as a list of normalized and uppercased strings
*/
public List<String> getContentAsUppercaseList() {
return Arrays.asList(SPLIT_AT_COMMAS.split(getContentAsUppercaseString()));
}
/** Get the content of the entry as an enum.
* @param cls enum class
* @param <T> type of the enum
* @return entry content
*/
public <T extends Enum<T>> T getContentAsEnum(final Class<T> cls) {
return toEnum(cls, getRawContent());
}
/** Get the content of the entry as a list of enum.
* @param cls enum class
* @param <T> type of the enum
* @return entry content
*/
public <T extends Enum<T>> List<T> getContentAsEnumList(final Class<T> cls) {
final String[] elements = SPLIT_AT_COMMAS.split(getRawContent());
final List<T> list = new ArrayList<>(elements.length);
for (int i = 0; i < elements.length; ++i) {
list.add(toEnum(cls, elements[i]));
}
return list;
}
/** Get the content of the entry as a double.
* @return content as a double
*/
public double getContentAsDouble() {
try {
return Double.parseDouble(content);
} catch (NumberFormatException nfe) {
throw generateException(nfe);
}
}
/** Get the content of the entry as a vector.
* @return content as a vector
*/
public Vector3D getContentAsVector() {
try {
final String[] fields = SPACE.split(content);
if (fields.length == 3) {
return new Vector3D(Double.parseDouble(fields[0]),
Double.parseDouble(fields[1]),
Double.parseDouble(fields[2]));
}
} catch (NumberFormatException nfe) {
// ignored, error handled below, together with wrong number of fields
}
throw generateException(null);
}
/** Get the content of the entry as a boolean.
* @return content as a boolean
*/
public boolean getContentAsBoolean() {
if (BOOLEAN_TRUE.matcher(content).matches()) {
return true;
} else if (BOOLEAN_FALSE.matcher(content).matches()) {
return false;
} else {
throw generateException(null);
}
}
/** Get the content of the entry as an integer.
* @return content as an integer
*/
public int getContentAsInt() {
try {
return Integer.parseInt(content);
} catch (NumberFormatException nfe) {
throw generateException(nfe);
}
}
/** Get the content of the entry as an uppercase character.
* @return content as an uppercase character
*/
public char getContentAsUppercaseCharacter() {
try {
return getContentAsUppercaseString().charAt(0);
} catch (NumberFormatException nfe) {
throw generateException(nfe);
}
}
/** Get the units.
* @return units of the entry (may be null)
*/
public Unit getUnits() {
return units;
}
/** Get the number of the line in the CCSDS data message.
* @return number of the line in the CCSDS data message
*/
public int getLineNumber() {
return lineNumber;
}
/** Get the name of the file.
* @return name of the file
*/
public String getFileName() {
return fileName;
}
/** Process the content as a normalized string.
* @param consumer consumer of the normalized string
* @return always returns {@code true}
* @see #processAsUppercaseString(StringConsumer)
*/
public boolean processAsNormalizedString(final StringConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsNormalizedString());
}
return true;
}
/** Process the content as a normalized uppercase string.
* @param consumer consumer of the normalized uppercase string
* @return always returns {@code true}
* @see #processAsNormalizedString(StringConsumer)
*/
public boolean processAsUppercaseString(final StringConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsUppercaseString());
}
return true;
}
/** Process the content as an indexed normalized string.
* @param index index
* @param consumer consumer of the indexed normalized string
* @return always returns {@code true}
*/
public boolean processAsIndexedNormalizedString(final int index, final IndexedStringConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(index, getContentAsNormalizedString());
}
return true;
}
/** Process the content as an indexed normalized uppercase string.
* @param index index
* @param consumer consumer of the indexed normalized uppercase string
* @return always returns {@code true}
*/
public boolean processAsIndexedUppercaseString(final int index, final IndexedStringConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(index, getContentAsUppercaseString());
}
return true;
}
/** Process the content as a list of normalized strings.
* @param consumer consumer of the normalized strings list
* @return always returns {@code true}
*/
public boolean processAsNormalizedList(final StringListConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsNormalizedList());
}
return true;
}
/** Process the content as a list of normalized uppercase strings.
* @param consumer consumer of the normalized uppercase strings list
* @return always returns {@code true}
*/
public boolean processAsUppercaseList(final StringListConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsUppercaseList());
}
return true;
}
/** Process the content as an enum.
* @param cls enum class
* @param consumer consumer of the enum
* @param <T> type of the enum
* @return always returns {@code true}
*/
public <T extends Enum<T>> boolean processAsEnum(final Class<T> cls, final EnumConsumer<T> consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsEnum(cls));
}
return true;
}
/** Process the content as a list of enums.
* @param cls enum class
* @param consumer consumer of the enums list
* @param <T> type of the enum
* @return always returns {@code true}
*/
public <T extends Enum<T>> boolean processAsEnumsList(final Class<T> cls, final EnumListConsumer<T> consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsEnumList(cls));
}
return true;
}
/** Process the content as a boolean.
* @param consumer consumer of the boolean
* @return always returns {@code true}
*/
public boolean processAsBoolean(final BooleanConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsBoolean());
}
return true;
}
/** Process the content as an integer.
* @param consumer consumer of the integer
* @return always returns {@code true}
*/
public boolean processAsInteger(final IntConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsInt());
}
return true;
}
/** Process the content as an array of integers.
* @param consumer consumer of the array
* @return always returns {@code true}
*/
public boolean processAsIntegerArray(final IntegerArrayConsumer consumer) {
try {
if (type == TokenType.ENTRY) {
final String[] fields = SPLIT_AT_COMMAS.split(getRawContent());
final int[] integers = new int[fields.length];
for (int i = 0; i < fields.length; ++i) {
integers[i] = Integer.parseInt(fields[i]);
}
consumer.accept(integers);
}
return true;
} catch (NumberFormatException nfe) {
throw generateException(nfe);
}
}
/** Process the content as a normalized character.
* @param consumer consumer of the normalized character
* @return always returns {@code true}
*/
public boolean processAsNormalizedCharacter(final CharConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsUppercaseCharacter());
}
return true;
}
/** Process the content as a double.
* @param standard units of parsed content as specified by CCSDS standard
* @param behavior behavior to adopt for parsed unit
* @param consumer consumer of the double
* @return always returns {@code true}
*/
public boolean processAsDouble(final Unit standard, final ParsedUnitsBehavior behavior,
final DoubleConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(behavior.select(getUnits(), standard).toSI(getContentAsDouble()));
}
return true;
}
/** Process the content as an labeled double.
* @param label label
* @param standard units of parsed content as specified by CCSDS standard
* @param behavior behavior to adopt for parsed unit
* @param consumer consumer of the indexed double
* @return always returns {@code true}
*/
public boolean processAsLabeledDouble(final char label,
final Unit standard, final ParsedUnitsBehavior behavior,
final LabeledDoubleConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(label, behavior.select(getUnits(), standard).toSI(getContentAsDouble()));
}
return true;
}
/** Process the content as an indexed double.
* @param i index
* @param standard units of parsed content as specified by CCSDS standard
* @param behavior behavior to adopt for parsed unit
* @param consumer consumer of the indexed double
* @return always returns {@code true}
*/
public boolean processAsIndexedDouble(final int i,
final Unit standard, final ParsedUnitsBehavior behavior,
final IndexedDoubleConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(i, behavior.select(getUnits(), standard).toSI(getContentAsDouble()));
}
return true;
}
/** Process the content as a doubly-indexed double.
* @param i first index
* @param j second index
* @param standard units of parsed content as specified by CCSDS standard
* @param behavior behavior to adopt for parsed unit
* @param consumer consumer of the doubly-indexed double
* @return always returns {@code true}
*/
public boolean processAsDoublyIndexedDouble(final int i, final int j,
final Unit standard, final ParsedUnitsBehavior behavior,
final DoublyIndexedDoubleConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(i, j, behavior.select(getUnits(), standard).toSI(getContentAsDouble()));
}
return true;
}
/** Process the content as a vector.
* @param consumer consumer of the vector
* @return always returns {@code true} (or throws an exception)
*/
public boolean processAsVector(final VectorConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(getContentAsVector());
}
return true;
}
/** Process the content as a date.
* @param consumer consumer of the date
* @param context context binding
* @return always returns {@code true} (or throws an exception)
*/
public boolean processAsDate(final DateConsumer consumer, final ContextBinding context) {
if (type == TokenType.ENTRY) {
if (context.getTimeSystem() == null) {
throw new OrekitException(OrekitMessages.CCSDS_TIME_SYSTEM_NOT_READ_YET,
getLineNumber(), getFileName());
}
consumer.accept(context.getTimeSystem().getConverter(context).parse(content));
}
return true;
}
/** Process the content as a time system.
* @param consumer consumer of the time system
* @return always returns {@code true} (or throws an exception)
*/
public boolean processAsTimeSystem(final TimeSystemConsumer consumer) {
if (type == TokenType.ENTRY) {
consumer.accept(TimeSystem.parse(getContentAsUppercaseString()));
}
return true;
}
/** Process the content as a frame.
* @param consumer consumer of the frame
* @param context context binding
* @param allowCelestial if true, {@link CelestialBodyFrame} are allowed
* @param allowOrbit if true, {@link OrbitRelativeFrame} are allowed
* @param allowSpacecraft if true, {@link SpacecraftBodyFrame} are allowed
* @return always returns {@code true}
*/
public boolean processAsFrame(final FrameConsumer consumer, final ContextBinding context,
final boolean allowCelestial, final boolean allowOrbit,
final boolean allowSpacecraft) {
if (type == TokenType.ENTRY) {
try {
consumer.accept(FrameFacade.parse(DASH.
matcher(getContentAsUppercaseString()).
replaceAll("").
replace(' ', '_'),
context.getConventions(),
context.isSimpleEOP(), context.getDataContext(),
allowCelestial, allowOrbit, allowSpacecraft));
} catch (OrekitException oe) {
throw generateException(oe);
}
}
return true;
}
/** Process the content as a body center.
* @param consumer consumer of the body center
* @param celestialBodies factory for celestial bodies
* @return always returns {@code true}
*/
public boolean processAsCenter(final CenterConsumer consumer, final CelestialBodies celestialBodies) {
if (type == TokenType.ENTRY) {
final String centerName = getContentAsUppercaseString();
consumer.accept(new BodyFacade(centerName, body(centerName, celestialBodies)));
}
return true;
}
/** Process the content as a body center list.
* @param consumer consumer of the body center list
* @param celestialBodies factory for celestial bodies
* @return always returns {@code true}
*/
public boolean processAsCenterList(final CenterListConsumer consumer, final CelestialBodies celestialBodies) {
if (type == TokenType.ENTRY) {
final List<BodyFacade> facades = new ArrayList<>();
for (final String centerName : SPLIT_AT_COMMAS.split(getContentAsUppercaseString())) {
facades.add(new BodyFacade(centerName, body(centerName, celestialBodies)));
}
consumer.accept(facades);
}
return true;
}
/** Process the content as a list of units.
* @param consumer consumer of the time scale
* @return always returns {@code true} (or throws an exception)
*/
public boolean processAsUnitList(final UnitListConsumer consumer) {
if (type == TokenType.ENTRY) {
final String bracketed = getContentAsNormalizedString();
if (bracketed.charAt(0) != '[' || bracketed.charAt(bracketed.length() - 1) != ']') {
throw generateException(null);
}
final String unbracketed = bracketed.substring(1, bracketed.length() - 1).trim();
try {
consumer.accept(Stream.of(SPLIT_AT_COMMAS.split(unbracketed)).
map(s -> Unit.parse(s)).
collect(Collectors.toList()));
} catch (OrekitException oe) {
// one unit is unknown
throw generateException(oe);
}
}
return true;
}
/** Generate a parse exception for this entry.
* @param cause underlying cause exception (may be null)
* @return exception for this entry
*/
public OrekitException generateException(final Exception cause) {
return new OrekitException(cause, OrekitMessages.UNABLE_TO_PARSE_ELEMENT_IN_FILE,
getName(), getLineNumber(), getFileName());
}
/** Get the body corresponding to a center name.
* @param centerName name of the center
* @param celestialBodies factory for celestial bodies
* @return celestial body corresponding to name, or null
*/
private CelestialBody body(final String centerName, final CelestialBodies celestialBodies) {
// convert some known names
final String canonicalValue;
if (centerName.equals("SOLAR SYSTEM BARYCENTER") || centerName.equals("SSB")) {
canonicalValue = "SOLAR_SYSTEM_BARYCENTER";
} else if (centerName.equals("EARTH MOON BARYCENTER") || centerName.equals("EARTH-MOON BARYCENTER") ||
centerName.equals("EARTH BARYCENTER") || centerName.equals("EMB")) {
canonicalValue = "EARTH_MOON";
} else {
canonicalValue = centerName;
}
try {
return CenterName.valueOf(canonicalValue).getCelestialBody(celestialBodies);
} catch (IllegalArgumentException iae) {
// ignored, we just let body set to null
return null;
}
}
/** Convert a value to an enum.
* @param cls enum class
* @param value value to convert to an enum
* @param <T> type of the enum
* @return enumerate corresponding to the value
*/
private <T extends Enum<T>> T toEnum(final Class<T> cls, final String value) {
// first replace space characters
final String noSpace = value.replace(' ', '_');
try {
// first try without changing case, as some CCSDS enums are mixed case (like RangeUnits for TDM)
return Enum.valueOf(cls, noSpace);
} catch (IllegalArgumentException iae1) {
try {
// second try, using more standard uppercase
return Enum.valueOf(cls, noSpace.toUpperCase(Locale.US));
} catch (IllegalArgumentException iae2) {
// use the first exception for the message
throw generateException(iae1);
}
}
}
/** Interface representing instance methods that consume string values. */
public interface StringConsumer {
/** Consume a string.
* @param value value to consume
*/
void accept(String value);
}
/** Interface representing instance methods that consume indexed string values. */
public interface IndexedStringConsumer {
/** Consume an indexed string.
* @param index index
* @param value value to consume
*/
void accept(int index, String value);
}
/** Interface representing instance methods that consume lists of strings values. */
public interface StringListConsumer {
/** Consume a list of strings.
* @param value value to consume
*/
void accept(List<String> value);
}
/** Interface representing instance methods that consume enum values.
* @param <T> type of the enum
*/
public interface EnumConsumer<T extends Enum<T>> {
/** Consume an enum.
* @param value value to consume
*/
void accept(T value);
}
/** Interface representing instance methods that consume lists of enum values.
* @param <T> type of the enum
*/
public interface EnumListConsumer<T extends Enum<T>> {
/** Consume an enum.
* @param value value to consume
*/
void accept(List<T> value);
}
/** Interface representing instance methods that consume boolean values. */
public interface BooleanConsumer {
/** Consume a boolean.
* @param value value to consume
*/
void accept(boolean value);
}
/** Interface representing instance methods that consume integer values. */
public interface IntConsumer {
/** Consume an integer.
* @param value value to consume
*/
void accept(int value);
}
/** Interface representing instance methods that consume integer array. */
public interface IntegerArrayConsumer {
/** Consume an array of integers.
* @param integers array of integers
*/
void accept(int[] integers);
}
/** Interface representing instance methods that consume character values. */
public interface CharConsumer {
/** Consume a character.
* @param value value to consume
*/
void accept(char value);
}
/** Interface representing instance methods that consume double values. */
public interface DoubleConsumer {
/** Consume a double.
* @param value value to consume
*/
void accept(double value);
}
/** Interface representing instance methods that consume labeled double values. */
public interface LabeledDoubleConsumer {
/** Consume an indexed double.
* @param label label
* @param value value to consume
*/
void accept(char label, double value);
}
/** Interface representing instance methods that consume indexed double values. */
public interface IndexedDoubleConsumer {
/** Consume an indexed double.
* @param i index
* @param value value to consume
*/
void accept(int i, double value);
}
/** Interface representing instance methods that consume doubly-indexed double values. */
public interface DoublyIndexedDoubleConsumer {
/** Consume a doubly indexed double.
* @param i first index
* @param j second index
* @param value value to consume
*/
void accept(int i, int j, double value);
}
/** Interface representing instance methods that consume vector values. */
public interface VectorConsumer {
/** Consume a vector.
* @param value value to consume
*/
void accept(Vector3D value);
}
/** Interface representing instance methods that consume date values. */
public interface DateConsumer {
/** Consume a date.
* @param value value to consume
*/
void accept(AbsoluteDate value);
}
/** Interface representing instance methods that consume time systems values. */
public interface TimeSystemConsumer {
/** Consume a time system.
* @param value value to consume
*/
void accept(TimeSystem value);
}
/** Interface representing instance methods that consume frame values. */
public interface FrameConsumer {
/** Consume a frame.
* @param frameFacade facade in front of several frames types
*/
void accept(FrameFacade frameFacade);
}
/** Interface representing instance methods that consume center values. */
public interface CenterConsumer {
/** Consume a body center.
* @param bodyFacade facade for celestial body name and body
*/
void accept(BodyFacade bodyFacade);
}
/** Interface representing instance methods that consume center lists. */
public interface CenterListConsumer {
/** Consume a body center.
* @param bodyFacades facades for celestial bodies name and bodies
*/
void accept(List<BodyFacade> bodyFacades);
}
/** Interface representing instance methods that consume units lists values. */
public interface UnitListConsumer {
/** Consume a list of units.
* @param value value to consume
*/
void accept(List<Unit> value);
}
}