CdmParser.java
/* Copyright 2002-2024 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.ndm.cdm;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.orekit.data.DataContext;
import org.orekit.files.ccsds.definitions.TimeSystem;
import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
import org.orekit.files.ccsds.ndm.odm.UserDefined;
import org.orekit.files.ccsds.section.CommentsContainer;
import org.orekit.files.ccsds.section.KvnStructureProcessingState;
import org.orekit.files.ccsds.section.MetadataKey;
import org.orekit.files.ccsds.section.XmlStructureProcessingState;
import org.orekit.files.ccsds.utils.ContextBinding;
import org.orekit.files.ccsds.utils.FileFormat;
import org.orekit.files.ccsds.utils.lexical.ParseToken;
import org.orekit.files.ccsds.utils.lexical.TokenType;
import org.orekit.files.ccsds.utils.parsing.AbstractConstituentParser;
import org.orekit.files.ccsds.utils.parsing.ProcessingState;
import org.orekit.utils.IERSConventions;
/**
* Base class for Conjunction Data Message parsers.
* <p>
* Note than starting with Orekit 11.0, CCSDS message parsers are
* mutable objects that gather the data being parsed, until the
* message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
* parseMessage} method has returned. This implies that parsers
* should <em>not</em> be used in a multi-thread context. The recommended
* way to use parsers is to either dedicate one parser for each message
* and drop it afterwards, or to use a single-thread loop.
* </p>
* @author Melina Vanel
* @since 11.2
*/
public class CdmParser extends AbstractConstituentParser<CdmHeader, Cdm, CdmParser> {
/** Comment key. */
private static String COMMENT = "COMMENT";
/** XML relative metadata key. */
private static String RELATIVEMETADATA = "relativeMetadataData";
/** XML metadata key. */
private static String METADATA = "metadata";
/** File header. */
private CdmHeader header;
/** File segments. */
private List<CdmSegment> segments;
/** CDM metadata being read. */
private CdmMetadata metadata;
/** CDM relative metadata being read. */
private CdmRelativeMetadata relativeMetadata;
/** Context binding valid for current metadata. */
private ContextBinding context;
/** CDM general data comments block being read. */
private CommentsContainer commentsBlock;
/** CDM OD parameters logical block being read. */
private ODParameters odParameters;
/** CDM additional parameters logical block being read. */
private AdditionalParameters addParameters;
/** CDM state vector logical block being read. */
private StateVector stateVector;
/** CDM covariance matrix logical block being read. */
private RTNCovariance covMatrix;
/** CDM XYZ covariance matrix logical block being read. */
private XYZCovariance xyzCovMatrix;
/** CDM Sigma/Eigenvectors covariance logical block being read. */
private SigmaEigenvectorsCovariance sig3eigvec3;
/** CDM additional covariance metadata logical block being read. */
private AdditionalCovarianceMetadata additionalCovMetadata;
/** Processor for global message structure. */
private ProcessingState structureProcessor;
/** Flag to only compute once relative metadata. */
private boolean doRelativeMetadata;
/** Flag to indicate that data block parsing is finished. */
private boolean isDatafinished;
/** CDM user defined logical block being read. */
private UserDefined userDefinedBlock;
/** Complete constructor.
* <p>
* Calling this constructor directly is not recommended. Users should rather use
* {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildCdmParser()
* parserBuilder.buildCdmParser()}.
* </p>
* @param conventions IERS Conventions
* @param simpleEOP if true, tidal effects are ignored when interpolating EOP
* @param dataContext used to retrieve frames, time scales, etc.
* @param parsedUnitsBehavior behavior to adopt for handling parsed units
* @param filters filters to apply to parse tokens
* @since 12.0
*/
public CdmParser(final IERSConventions conventions, final boolean simpleEOP, final DataContext dataContext,
final ParsedUnitsBehavior parsedUnitsBehavior,
final Function<ParseToken, List<ParseToken>>[] filters) {
super(Cdm.ROOT, Cdm.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext, parsedUnitsBehavior, filters);
this.doRelativeMetadata = true;
this.isDatafinished = false;
}
/** {@inheritDoc} */
@Override
public CdmHeader getHeader() {
return header;
}
/** {@inheritDoc} */
@Override
public void reset(final FileFormat fileFormat) {
header = new CdmHeader();
segments = new ArrayList<>();
metadata = null;
relativeMetadata = null;
context = null;
odParameters = null;
addParameters = null;
stateVector = null;
covMatrix = null;
xyzCovMatrix = null;
sig3eigvec3 = null;
additionalCovMetadata = null;
userDefinedBlock = null;
commentsBlock = null;
if (fileFormat == FileFormat.XML) {
structureProcessor = new XmlStructureProcessingState(Cdm.ROOT, this);
reset(fileFormat, structureProcessor);
} else {
structureProcessor = new KvnStructureProcessingState(this);
reset(fileFormat, new CdmHeaderProcessingState(this));
}
}
/** {@inheritDoc} */
@Override
public boolean prepareHeader() {
anticipateNext(new CdmHeaderProcessingState(this));
return true;
}
/** {@inheritDoc} */
@Override
public boolean inHeader() {
anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processMetadataToken);
return true;
}
/** {@inheritDoc} */
@Override
public boolean finalizeHeader() {
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : structureProcessor);
header.validate(header.getFormatVersion());
return true;
}
/** {@inheritDoc} */
@Override
public boolean prepareMetadata() {
if (metadata != null) {
return false;
}
if (doRelativeMetadata) {
// if parser is just after header it is time to create / read relative metadata,
// their are only initialized once and then shared between metadata for object 1 and 2
relativeMetadata = new CdmRelativeMetadata();
relativeMetadata.setTimeSystem(TimeSystem.UTC);
}
metadata = new CdmMetadata(getDataContext());
metadata.setRelativeMetadata(relativeMetadata);
// As no time system is defined in CDM because all dates are given in UTC,
// time system is set here to UTC, we use relative metadata and not metadata
// because setting time system on metadata implies refusingfurthercomments
// witch would be a problem as metadata comments have not been read yet.
context = new ContextBinding(this::getConventions, this::isSimpleEOP,
this::getDataContext, this::getParsedUnitsBehavior,
() -> null, relativeMetadata::getTimeSystem,
() -> 0.0, () -> 1.0);
anticipateNext(this::processMetadataToken);
return true;
}
/** {@inheritDoc} */
@Override
public boolean inMetadata() {
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processGeneralCommentToken);
return true;
}
/** {@inheritDoc} */
@Override
public boolean finalizeMetadata() {
metadata.validate(header.getFormatVersion());
relativeMetadata.validate();
anticipateNext(structureProcessor);
return true;
}
/** {@inheritDoc} */
@Override
public boolean prepareData() {
// stateVector and RTNCovariance blocks are 2 mandatory data blocks
stateVector = new StateVector();
covMatrix = new RTNCovariance();
// initialize comments block for general data comments
commentsBlock = new CommentsContainer();
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processGeneralCommentToken);
return true;
}
/** {@inheritDoc} */
@Override
public boolean inData() {
return true;
}
/** {@inheritDoc} */
@Override
public boolean finalizeData() {
// call at the and of data block for object 1 or 2
if (metadata != null) {
CdmData data = new CdmData(commentsBlock, odParameters, addParameters,
stateVector, covMatrix, additionalCovMetadata);
if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.XYZ) {
data = new CdmData(commentsBlock, odParameters, addParameters,
stateVector, covMatrix, xyzCovMatrix, additionalCovMetadata);
} else if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3) {
data = new CdmData(commentsBlock, odParameters, addParameters,
stateVector, covMatrix, sig3eigvec3, additionalCovMetadata);
}
data.validate(header.getFormatVersion());
segments.add(new CdmSegment(metadata, data));
// Add the user defined block to both Objects data sections
if (userDefinedBlock != null && !userDefinedBlock.getParameters().isEmpty()) {
for (int i = 0; i < segments.size(); i++) {
segments.get(i).getData().setUserDefinedBlock(userDefinedBlock);
}
}
}
metadata = null;
context = null;
odParameters = null;
addParameters = null;
stateVector = null;
covMatrix = null;
xyzCovMatrix = null;
sig3eigvec3 = null;
additionalCovMetadata = null;
userDefinedBlock = null;
commentsBlock = null;
return true;
}
/** {@inheritDoc} */
@Override
public Cdm build() {
// CDM KVN file lack a DATA_STOP keyword, hence we can't call finalizeData()
// automatically before the end of the file
finalizeData();
if (userDefinedBlock != null && userDefinedBlock.getParameters().isEmpty()) {
userDefinedBlock = null;
}
final Cdm file = new Cdm(header, segments, getConventions(), getDataContext());
return file;
}
/** Add a general comment.
* @param comment comment to add
* @return always return true
*/
boolean addGeneralComment(final String comment) {
return commentsBlock.addComment(comment);
}
/** Manage relative metadata section.
* @param starting if true, parser is entering the section
* otherwise it is leaving the section
* @return always return true
*/
boolean manageRelativeMetadataSection(final boolean starting) {
anticipateNext(starting ? this::processMetadataToken : structureProcessor);
return true;
}
/** Manage relative metadata state vector section.
* @param starting if true, parser is entering the section
* otherwise it is leaving the section
* @return always return true
*/
boolean manageRelativeStateVectorSection(final boolean starting) {
anticipateNext(this::processMetadataToken);
return true;
}
/** Manage OD parameters section.
* @param starting if true, parser is entering the section
* otherwise it is leaving the section
* @return always return true
*/
boolean manageODParametersSection(final boolean starting) {
commentsBlock.refuseFurtherComments();
anticipateNext(starting ? this::processODParamToken : structureProcessor);
return true;
}
/** Manage additional parameters section.
* @param starting if true, parser is entering the section
* otherwise it is leaving the section
* @return always return true
*/
boolean manageAdditionalParametersSection(final boolean starting) {
commentsBlock.refuseFurtherComments();
anticipateNext(starting ? this::processAdditionalParametersToken : structureProcessor);
return true;
}
/** Manage state vector section.
* @param starting if true, parser is entering the section
* otherwise it is leaving the section
* @return always return true
*/
boolean manageStateVectorSection(final boolean starting) {
commentsBlock.refuseFurtherComments();
anticipateNext(starting ? this::processStateVectorToken : structureProcessor);
return true;
}
/** Manage general covariance section in XML file.
* @param starting if true, parser is entering the section
* otherwise it is leaving the section
* @return always return true
*/
boolean manageXmlGeneralCovarianceSection(final boolean starting) {
commentsBlock.refuseFurtherComments();
if (starting) {
if (metadata.getAltCovType() == null) {
anticipateNext(this::processCovMatrixToken);
} else {
if (Double.isNaN(covMatrix.getCrr())) {
// First, handle mandatory RTN covariance section
anticipateNext(this::processCovMatrixToken);
} else if ( metadata.getAltCovType() == AltCovarianceType.XYZ && xyzCovMatrix == null ||
metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3 && sig3eigvec3 == null ) {
// Second, add the alternate covariance if provided
anticipateNext(this::processAltCovarianceToken);
} else if (additionalCovMetadata == null) {
// Third, process the additional covariance metadata
anticipateNext(this::processAdditionalCovMetadataToken);
}
}
} else {
anticipateNext(structureProcessor);
}
return true;
}
/** Manage user-defined parameters section.
* @param starting if true, parser is entering the section
* otherwise it is leaving the section
* @return always return true
*/
boolean manageUserDefinedParametersSection(final boolean starting) {
commentsBlock.refuseFurtherComments();
if (starting) {
if (userDefinedBlock == null) {
// this is the first (and unique) user-defined parameters block, we need to allocate the container
userDefinedBlock = new UserDefined();
}
anticipateNext(this::processUserDefinedToken);
} else {
anticipateNext(structureProcessor);
}
return true;
}
/** Process one metadata token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processMetadataToken(final ParseToken token) {
if (isDatafinished && getFileFormat() != FileFormat.XML) {
finalizeData();
isDatafinished = false;
}
if (metadata == null) {
// CDM KVN file lack a META_START keyword, hence we can't call prepareMetadata()
// automatically before the first metadata token arrives
prepareMetadata();
}
inMetadata();
// There can be a COMMENT key at the beginning of relative metadata, but as the relative
// metadata are processed in the same try and catch loop than metadata because Orekit is
// build to read metadata and then data (and not relative metadata), it would be problematic
// to make relative metadata extends comments container(because of the COMMENTS in the middle
// of relativemetadata and metadata section. Indeed, as said in {@link
// #CommentsContainer} COMMENT should only be at the beginning of sections but in this case
// there is a comment at the beginning corresponding to the relative metadata comment
// and 1 in the middle for object 1 metadata and one further for object 2 metadata. That
// is why this special syntax was used and initializes the relative metadata COMMENT once
// at the beginning as relative metadata is not a comment container
if (COMMENT.equals(token.getName()) && doRelativeMetadata ) {
if (token.getType() == TokenType.ENTRY) {
relativeMetadata.addComment(token.getContentAsNormalizedString());
return true;
}
}
doRelativeMetadata = false;
try {
return token.getName() != null &&
CdmRelativeMetadataKey.valueOf(token.getName()).process(token, context, relativeMetadata);
} catch (IllegalArgumentException iaeM) {
try {
return MetadataKey.valueOf(token.getName()).process(token, context, metadata);
} catch (IllegalArgumentException iaeD) {
try {
return CdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
} catch (IllegalArgumentException iaeC) {
// token has not been recognized
return false;
}
}
}
}
/** Process one XML data substructure token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processXmlSubStructureToken(final ParseToken token) {
// As no relativemetadata token exists in the structure processor and as RelativeMetadata keys are
// processed in the same try and catch loop in processMetadatatoken as CdmMetadata keys, if the relativemetadata
// token is read it should be as if the token was equal to metadata to start to initialize relative metadata
// and metadata and to go in the processMetadataToken try and catch loop. The following relativemetadata
// stop should be ignored to stay in the processMetadataToken try and catch loop and the following metadata
// start also ignored to stay in the processMetadataToken try and catch loop. Then arrives the end of metadata
// so we call structure processor with metadata stop. This distinction of cases is useful for relativemetadata
// block followed by metadata block for object 1 and also useful to only close metadata block for object 2.
// The metadata start for object 2 is processed by structureProcessor
if (METADATA.equals(token.getName()) && TokenType.START.equals(token.getType()) ||
RELATIVEMETADATA.equals(token.getName()) && TokenType.STOP.equals(token.getType())) {
anticipateNext(this::processMetadataToken);
return true;
} else if (RELATIVEMETADATA.equals(token.getName()) && TokenType.START.equals(token.getType()) ||
METADATA.equals(token.getName()) && TokenType.STOP.equals(token.getType())) {
final ParseToken replaceToken = new ParseToken(token.getType(), METADATA,
null, token.getUnits(), token.getLineNumber(), token.getFileName());
return structureProcessor.processToken(replaceToken);
} else {
// Relative metadata COMMENT and metadata COMMENT should not be read by XmlSubStructureKey that
// is why 2 cases are distinguished here : the COMMENT for relative metadata and the COMMENT
// for metadata.
if (commentsBlock == null && COMMENT.equals(token.getName())) {
// COMMENT adding for Relative Metadata in XML
if (doRelativeMetadata) {
if (token.getType() == TokenType.ENTRY) {
relativeMetadata.addComment(token.getContentAsNormalizedString());
doRelativeMetadata = false;
return true;
} else {
// if the token Type is still not ENTRY we return true as at the next step
// it will be ENTRY ad we will be able to store the comment (similar treatment
// as OD parameter or Additional parameter or State Vector ... COMMENT treatment.)
return true;
}
}
// COMMENT adding for Metadata in XML
if (!doRelativeMetadata) {
if (token.getType() == TokenType.ENTRY) {
metadata.addComment(token.getContentAsNormalizedString());
return true;
} else {
// same as above
return true;
}
}
}
// to treat XmlSubStructureKey keys ( OD parameters, relative Metadata ...)
try {
return token.getName() != null && !doRelativeMetadata &&
XmlSubStructureKey.valueOf(token.getName()).process(token, this);
} catch (IllegalArgumentException iae) {
// token has not been recognized
return false;
}
}
}
/** Process one comment token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processGeneralCommentToken(final ParseToken token) {
if (commentsBlock == null) {
// CDM KVN file lack a META_STOP keyword, hence we can't call finalizeMetadata()
// automatically before the first data token arrives
finalizeMetadata();
// CDM KVN file lack a DATA_START keyword, hence we can't call prepareData()
// automatically before the first data token arrives
prepareData();
}
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processODParamToken);
if (COMMENT.equals(token.getName()) && commentsBlock.acceptComments()) {
if (token.getType() == TokenType.ENTRY) {
commentsBlock.addComment(token.getContentAsNormalizedString());
}
// in order to be able to differentiate general data comments and next block comment (OD parameters if not empty)
// only 1 line comment is allowed for general data comment.
commentsBlock.refuseFurtherComments();
return true;
} else {
return false;
}
}
/** Process one od parameter data token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processODParamToken(final ParseToken token) {
if (odParameters == null) {
odParameters = new ODParameters();
}
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAdditionalParametersToken);
try {
return token.getName() != null &&
ODParametersKey.valueOf(token.getName()).process(token, context, odParameters);
} catch (IllegalArgumentException iae) {
// token has not been recognized
return false;
}
}
/** Process one additional parameter data token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processAdditionalParametersToken(final ParseToken token) {
if (addParameters == null) {
addParameters = new AdditionalParameters();
}
if (moveCommentsIfEmpty(odParameters, addParameters)) {
// get rid of the empty logical block
odParameters = null;
}
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processStateVectorToken);
try {
return token.getName() != null &&
AdditionalParametersKey.valueOf(token.getName()).process(token, context, addParameters);
} catch (IllegalArgumentException iae) {
// token has not been recognized
return false;
}
}
/** Process one state vector data token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processStateVectorToken(final ParseToken token) {
if (moveCommentsIfEmpty(addParameters, stateVector)) {
// get rid of the empty logical block
addParameters = null;
}
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processCovMatrixToken);
try {
return token.getName() != null &&
StateVectorKey.valueOf(token.getName()).process(token, context, stateVector);
} catch (IllegalArgumentException iae) {
// token has not been recognized
return false;
}
}
/** Process covariance matrix data token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processCovMatrixToken(final ParseToken token) {
if (moveCommentsIfEmpty(stateVector, covMatrix)) {
// get rid of the empty logical block
stateVector = null;
}
if (metadata.getAltCovType() == null) {
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
} else {
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAltCovarianceToken);
}
isDatafinished = true;
try {
return token.getName() != null &&
RTNCovarianceKey.valueOf(token.getName()).process(token, context, covMatrix);
} catch (IllegalArgumentException iae) {
// token has not been recognized
return false;
}
}
/** Process alternate covariance data token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processAltCovarianceToken(final ParseToken token) {
// Covariance is provided in XYZ
if (metadata.getAltCovType() == AltCovarianceType.XYZ && xyzCovMatrix == null) {
xyzCovMatrix = new XYZCovariance(true);
if (moveCommentsIfEmpty(covMatrix, xyzCovMatrix)) {
// get rid of the empty logical block
covMatrix = null;
}
}
// Covariance is provided in CSIG3EIGVEC3 format
if (metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3 && sig3eigvec3 == null) {
sig3eigvec3 = new SigmaEigenvectorsCovariance(true);
if (moveCommentsIfEmpty(covMatrix, sig3eigvec3)) {
// get rid of the empty logical block
covMatrix = null;
}
}
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processAdditionalCovMetadataToken);
try {
if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.XYZ) {
return token.getName() != null &&
XYZCovarianceKey.valueOf(token.getName()).process(token, context, xyzCovMatrix);
} else if (metadata.getAltCovType() != null && metadata.getAltCovType() == AltCovarianceType.CSIG3EIGVEC3) {
return token.getName() != null &&
SigmaEigenvectorsCovarianceKey.valueOf(token.getName()).process(token, context, sig3eigvec3);
} else {
// token has not been recognized
return false;
}
} catch (IllegalArgumentException iae) {
// token has not been recognized
return false;
}
}
/** Process additional covariance metadata token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processAdditionalCovMetadataToken(final ParseToken token) {
// Additional covariance metadata
if ( additionalCovMetadata == null) {
additionalCovMetadata = new AdditionalCovarianceMetadata();
}
if (moveCommentsIfEmpty(xyzCovMatrix, additionalCovMetadata)) {
// get rid of the empty logical block
xyzCovMatrix = null;
} else if (moveCommentsIfEmpty(sig3eigvec3, additionalCovMetadata)) {
// get rid of the empty logical block
sig3eigvec3 = null;
}
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processUserDefinedToken);
try {
return token.getName() != null &&
AdditionalCovarianceMetadataKey.valueOf(token.getName()).process(token, context, additionalCovMetadata);
} catch (IllegalArgumentException iae) {
// token has not been recognized
return false;
}
}
/** Process one user-defined parameter data token.
* @param token token to process
* @return true if token was processed, false otherwise
*/
private boolean processUserDefinedToken(final ParseToken token) {
if (userDefinedBlock == null) {
userDefinedBlock = new UserDefined();
}
if (moveCommentsIfEmpty(additionalCovMetadata, userDefinedBlock)) {
// get rid of the empty logical block
additionalCovMetadata = null;
}
anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
if (COMMENT.equals(token.getName())) {
return token.getType() == TokenType.ENTRY ? userDefinedBlock.addComment(token.getContentAsNormalizedString()) : true;
} else if (token.getName().startsWith(UserDefined.USER_DEFINED_PREFIX)) {
if (token.getType() == TokenType.ENTRY) {
userDefinedBlock.addEntry(token.getName().substring(UserDefined.USER_DEFINED_PREFIX.length()),
token.getContentAsNormalizedString());
}
return true;
} else {
// the token was not processed
return false;
}
}
/** Move comments from one empty logical block to another logical block.
* @param origin origin block
* @param destination destination block
* @return true if origin block was empty
*/
private boolean moveCommentsIfEmpty(final CommentsContainer origin, final CommentsContainer destination) {
if (origin != null && origin.acceptComments()) {
// origin block is empty, move the existing comments
for (final String comment : origin.getComments()) {
destination.addComment(comment);
}
return true;
} else {
return false;
}
}
}