1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.files.ccsds.ndm.cdm;
18  
19  import java.io.IOException;
20  import java.time.ZoneOffset;
21  import java.time.ZonedDateTime;
22  
23  import org.orekit.files.ccsds.definitions.TimeConverter;
24  import org.orekit.files.ccsds.section.HeaderKey;
25  import org.orekit.files.ccsds.section.Segment;
26  import org.orekit.files.ccsds.section.XmlStructureKey;
27  import org.orekit.files.ccsds.utils.ContextBinding;
28  import org.orekit.files.ccsds.utils.FileFormat;
29  import org.orekit.files.ccsds.utils.generation.Generator;
30  import org.orekit.files.ccsds.utils.generation.MessageWriter;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.DateComponents;
33  import org.orekit.time.DateTimeComponents;
34  import org.orekit.time.TimeComponents;
35  import org.orekit.time.TimeScale;
36  
37  /** Cdm message writer.
38   * @author Melina Vanel
39   * @since 11.2
40   */
41  public abstract class CdmMessageWriter implements MessageWriter<CdmHeader, CdmSegment, Cdm> {
42  
43      /** Default value for {@link HeaderKey#ORIGINATOR}. */
44      public static final String DEFAULT_ORIGINATOR = "OREKIT";
45  
46      /** Root element for XML files. */
47      private final String root;
48  
49      /** Key for format version. */
50      private final String formatVersionKey;
51  
52      /** Default format version. */
53      private final double defaultVersion;
54  
55      /** Current context binding. */
56      private ContextBinding context;
57  
58      /** Current converter for dates. */
59      private TimeConverter timeConverter;
60  
61      /** Current format version. */
62      private double version;
63  
64      /** Boolean to ensure relative metadata will be written only once. */
65      private boolean isrelativemetadataWritten;
66  
67      /**
68       * Constructor used to create a new NDM writer configured with the necessary parameters
69       * to successfully fill in all required fields that aren't part of a standard object.
70       * <p>
71       * If creation date and originator are not present in header, built-in defaults will be used
72       * </p>
73       * @param root root element for XML files
74       * @param formatVersionKey key for format version
75       * @param defaultVersion default format version
76       * @param context context binding (may be reset for each segment)
77       */
78      public CdmMessageWriter(final String root, final String formatVersionKey,
79                                   final double defaultVersion, final ContextBinding context) {
80  
81          this.root                      = root;
82          this.defaultVersion            = defaultVersion;
83          this.formatVersionKey          = formatVersionKey;
84          this.version                   = defaultVersion;
85          this.isrelativemetadataWritten = false;
86  
87          setContext(context);
88  
89      }
90  
91      /** Reset context binding.
92       * @param context context binding to use
93       */
94      public void setContext(final ContextBinding context) {
95          this.context       = context;
96          this.timeConverter = context.getTimeSystem().getConverter(context);
97      }
98  
99      /** Get the current context.
100      * @return current context
101      */
102     public ContextBinding getContext() {
103         return context;
104     }
105 
106     /** Get the current time converter.
107      * @return current time converter
108      */
109     public TimeConverter getTimeConverter() {
110         return timeConverter;
111     }
112 
113     /** Get the default format version.
114      * @return default format version
115      */
116     public double getDefaultVersion() {
117         return defaultVersion;
118     }
119 
120     /** {@inheritDoc} */
121     @Override
122     public void writeHeader(final Generator generator, final CdmHeader header) throws IOException {
123 
124         final ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
125         final TimeScale     utc = context.getDataContext().getTimeScales().getUTC();
126         final AbsoluteDate date = new AbsoluteDate(zdt.getYear(), zdt.getMonthValue(), zdt.getDayOfMonth(),
127                                                    zdt.getHour(), zdt.getMinute(), zdt.getSecond(),
128                                                    utc);
129 
130         // validate before writing
131         if (header != null) {
132 
133             if (!Double.isNaN(header.getFormatVersion())) {
134                 // save format version for validating segments
135                 version = header.getFormatVersion();
136             }
137 
138             if (header.getCreationDate() == null) {
139                 header.setCreationDate(date);
140             }
141 
142             if (header.getOriginator() == null) {
143                 header.setOriginator(DEFAULT_ORIGINATOR);
144             }
145 
146             header.validate(version);
147 
148         }
149 
150         generator.startMessage(root, formatVersionKey, version);
151 
152         if (generator.getFormat() == FileFormat.XML) {
153             generator.enterSection(XmlStructureKey.header.name());
154         }
155 
156         // comments are optional
157         if (header != null) {
158             generator.writeComments(header.getComments());
159         }
160 
161         // creation date is informational only, but mandatory and always in UTC
162         final DateTimeComponents creationDate = ((header == null) ? date : header.getCreationDate()).getComponents(utc);
163         final DateComponents     dc           = creationDate.getDate();
164         final TimeComponents     tc           = creationDate.getTime();
165         generator.writeEntry(HeaderKey.CREATION_DATE.name(),
166                              generator.dateToString(dc.getYear(), dc.getMonth(), dc.getDay(),
167                                                     tc.getHour(), tc.getMinute(), tc.getSecond()),
168                              null, true);
169 
170         // Use built-in default if mandatory originator not present
171         generator.writeEntry(HeaderKey.ORIGINATOR.name(),
172                              header == null ? DEFAULT_ORIGINATOR : header.getOriginator(),
173                              null, true);
174 
175         if (header != null) {
176             generator.writeEntry(CdmHeaderKey.MESSAGE_FOR.name(), header.getMessageFor(), null, false);
177         }
178 
179         if (header != null) {
180             generator.writeEntry(HeaderKey.MESSAGE_ID.name(), header.getMessageId(), null, false);
181         }
182 
183         if (generator.getFormat() == FileFormat.XML) {
184             generator.exitSection();
185         }
186 
187         // add an empty line for presentation
188         generator.newLine();
189 
190         if (generator.getFormat() == FileFormat.XML) {
191             generator.enterSection(XmlStructureKey.body.name());
192         }
193 
194     }
195 
196     /** {@inheritDoc} */
197     @Override
198     public void writeSegment(final Generator generator, final CdmSegment segment) throws IOException {
199 
200         // validate before writing
201         segment.getMetadata().validate(version);
202         segment.getData().validate(version);
203 
204         // relative metadata should only be written once after the header at the beginning of the body
205         if (!isrelativemetadataWritten) {
206             writeRelativeMetadataContent(generator, version, segment.getMetadata().getRelativeMetadata());
207             isrelativemetadataWritten = true;
208         }
209 
210         if (generator.getFormat() == FileFormat.XML) {
211             generator.enterSection(XmlStructureKey.segment.name());
212         }
213         writeSegmentContent(generator, version, segment);
214         if (generator.getFormat() == FileFormat.XML) {
215             generator.exitSection();
216         }
217 
218     }
219 
220     /** Write RelativeMetadata part only once after header.
221      * @param generator generator to use for producing output
222      * @param formatVersion format version to use
223      * @param relativeMetadata relative metadata to write
224      * @throws IOException if any buffer writing operations fails
225      */
226     public abstract void writeRelativeMetadataContent(Generator generator, double formatVersion,
227                                                       CdmRelativeMetadata relativeMetadata) throws IOException;
228 
229     /** Write one segment content (without XML wrapping).
230      * @param generator generator to use for producing output
231      * @param formatVersion format version to use
232      * @param segment segment to write
233      * @throws IOException if any buffer writing operations fails
234      */
235     public abstract void writeSegmentContent(Generator generator, double formatVersion,
236                                              Segment<CdmMetadata, CdmData> segment) throws IOException;
237 
238     /** {@inheritDoc} */
239     @Override
240     public void writeFooter(final Generator generator) throws IOException {
241         if (generator.getFormat() == FileFormat.XML) {
242             generator.exitSection();
243         }
244         generator.endMessage(root);
245     }
246 
247     /** {@inheritDoc} */
248     @Override
249     public String getRoot() {
250         return root;
251     }
252 
253     /** {@inheritDoc} */
254     @Override
255     public String getFormatVersionKey() {
256         return formatVersionKey;
257     }
258 
259     /** {@inheritDoc} */
260     @Override
261     public double getVersion() {
262         return version;
263     }
264 
265 }