1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS Systèmes d'Information (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.files.ccsds.ndm;
18  
19  import org.junit.jupiter.api.Assertions;
20  import org.junit.jupiter.api.BeforeEach;
21  import org.orekit.Utils;
22  import org.orekit.data.DataSource;
23  import org.orekit.files.ccsds.section.Header;
24  import org.orekit.files.ccsds.section.Segment;
25  import org.orekit.files.ccsds.utils.FileFormat;
26  import org.orekit.files.ccsds.utils.generation.Generator;
27  import org.orekit.files.ccsds.utils.generation.KvnGenerator;
28  import org.orekit.files.ccsds.utils.generation.MessageWriter;
29  import org.orekit.files.ccsds.utils.generation.XmlGenerator;
30  import org.orekit.files.ccsds.utils.lexical.MessageParser;
31  import org.orekit.utils.Constants;
32  import org.orekit.utils.TruncatedCcsdsFormatter;
33  
34  import java.io.BufferedReader;
35  import java.io.ByteArrayInputStream;
36  import java.io.CharArrayWriter;
37  import java.io.IOException;
38  import java.io.InputStream;
39  import java.io.InputStreamReader;
40  import java.nio.charset.StandardCharsets;
41  import java.util.Locale;
42  import java.util.regex.Pattern;
43  import java.util.stream.Collectors;
44  
45  public abstract class AbstractWriterTest<H extends Header, S extends Segment<?, ?>, F extends NdmConstituent<H, S>> {
46  
47      @BeforeEach
48      public void setUp() {
49          Utils.setDataRoot("regular-data");
50      }
51  
52      protected abstract MessageParser<F>       getParser();
53      protected abstract MessageWriter<H, S, F> getWriter();
54  
55      protected  void doTest(final String name) {
56          doTest(name, FileFormat.KVN, 60);
57          doTest(name, FileFormat.KVN,  0);
58          doTest(name, FileFormat.XML, 60);
59          doTest(name, FileFormat.XML,  0);
60  
61          doDoubleCcsdsStandardsTest(name, FileFormat.KVN, 60);
62          doDoubleCcsdsStandardsTest(name, FileFormat.KVN,  0);
63          doDoubleCcsdsStandardsTest(name, FileFormat.XML, 60);
64          doDoubleCcsdsStandardsTest(name, FileFormat.XML,  0);
65      }
66  
67      protected  void doTest(final String name, final FileFormat format, final int unitsColumn) {
68          try {
69              final DataSource source1  = new DataSource(name, () -> getClass().getResourceAsStream(name));
70              final F          original = getParser().parseMessage(source1);
71  
72              // write the parsed file back to a characters array
73              final MessageWriter<H, S, F> writer = getWriter();
74              final CharArrayWriter caw = new CharArrayWriter();
75              try (Generator generator = format == FileFormat.KVN ?
76                                         new KvnGenerator(caw, 25, "dummy.kvn", Constants.JULIAN_DAY, unitsColumn) :
77                                         new XmlGenerator(caw, XmlGenerator.DEFAULT_INDENT, "dummy.xml",
78                                                          Constants.JULIAN_DAY, unitsColumn > 0,
79                                                          XmlGenerator.NDM_XML_V3_SCHEMA_LOCATION)) {
80                  writer.writeMessage(generator, original);
81              }
82  
83              // reparse the written file
84              final byte[]      bytes  = caw.toString().getBytes(StandardCharsets.UTF_8);
85              final DataSource source2 = new DataSource(name, () -> new ByteArrayInputStream(bytes));
86              final F          rebuilt = getParser().parseMessage(source2);
87  
88              NdmTestUtils.checkEquals(original, rebuilt);
89  
90              if (format == FileFormat.XML) {
91                  // check schema
92                 try (InputStream       is  = new ByteArrayInputStream(bytes);
93                      InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
94                      BufferedReader    br  = new BufferedReader(isr)) {
95                      Assertions.assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", br.readLine());
96                      Assertions.assertEquals("<" + writer.getRoot() +
97                                              " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
98                                              " xsi:noNamespaceSchemaLocation=\"" + XmlGenerator.NDM_XML_V3_SCHEMA_LOCATION +
99                                              "\" id=\"" + writer.getFormatVersionKey() +
100                                             "\" version=\"" + String.format(Locale.US, "%.1f", writer.getVersion()) +
101                                             "\">",
102                                             br.readLine());
103                 }
104             }
105 
106         } catch (IOException ioe) {
107             Assertions.fail(ioe.getLocalizedMessage());
108         }
109     }
110 
111     protected  void doDoubleCcsdsStandardsTest(final String name, final FileFormat format, final int unitsColumn) {
112         try {
113             final DataSource source1  = new DataSource(name, () -> getClass().getResourceAsStream(name));
114 
115             String inputString = new BufferedReader(new InputStreamReader(source1.getOpener().openStreamOnce()))
116                     .lines().collect(Collectors.joining("\n"));
117 
118             String[] lines = inputString.split("\n");
119             StringBuilder sb = new StringBuilder();
120 
121             Pattern nominal = Pattern.compile("^-?[0-9]+\\.[0-9]+");
122             Pattern scientific = Pattern.compile("^-?[0-9]+\\.[0-9]+E[0-9]+");
123 
124             // Create new file so that every double not a comment is above 16 sig figs
125             for (String line : lines) {
126                 if (!line.contains("COMMENT")) {
127                     String[] segments = line.split(" ");
128                     for (String segment : segments) {
129                         if (nominal.matcher(segment).matches()) {
130                             segment = segment + "1234567891234567";
131                             Assertions.assertTrue( segment.length() - 1  > 16);
132                         } else if (scientific.matcher(segment).matches()) {
133                             String[] splitNum = segment.split("E");
134                             String mantissa = splitNum[0] + "1234567891234567";
135                             Assertions.assertTrue( mantissa.length() - 1  > 16);
136                             segment = mantissa + "E" + splitNum[1];
137                         }
138                         sb.append(segment).append(" ");
139                     }
140                     sb.append("\n");
141                 }
142             }
143 
144             final DataSource altered = new DataSource("altered", () -> {
145                 return new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8));
146             });
147             final F          original = getParser().parseMessage(altered);
148 
149             // write the parsed file back to a characters array
150             final MessageWriter<H, S, F> writer = getWriter();
151             final CharArrayWriter caw = new CharArrayWriter();
152             try (Generator generator = format == FileFormat.KVN ?
153                     new KvnGenerator(caw, 25, "dummy.kvn", Constants.JULIAN_DAY, unitsColumn, new TruncatedCcsdsFormatter()) :
154                     new XmlGenerator(caw, XmlGenerator.DEFAULT_INDENT, "dummy.xml",
155                             Constants.JULIAN_DAY, unitsColumn > 0,
156                             XmlGenerator.NDM_XML_V3_SCHEMA_LOCATION, new TruncatedCcsdsFormatter())) {
157                 writer.writeMessage(generator, original);
158             }
159 
160             // check doubles have correct sig fig
161             String[] shortenedLines = caw.toString().split("\n");
162             for (String line : shortenedLines) {
163                 String[] segments = line.split(" ");
164                 for (String segment : segments) {
165                     if (nominal.matcher(segment).matches()) {
166                         Assertions.assertTrue( segment.replace("-","").replace(".", "").length()  <= 16,
167                                 "Line has too many sig figs: " + line);
168                     } else if (scientific.matcher(segment).matches()) {
169                         String[] splitNum = segment.split("E");
170                         String mantissa = splitNum[0];
171                         Assertions.assertTrue( mantissa.replace("-","").replace(".", "").length()  <= 16,
172                                 "Line has too many sig figs: " + line);
173                     }
174                 }
175             }
176         } catch (IOException ioe) {
177             throw new RuntimeException(ioe.getLocalizedMessage());
178         }
179     }
180 
181 }