1   /* Copyright 2022-2025 Luc Maisonobe
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.sinex;
18  
19  import org.hipparchus.util.FastMath;
20  import org.orekit.time.AbsoluteDate;
21  import org.orekit.time.DateComponents;
22  import org.orekit.time.TimeScale;
23  import org.orekit.time.TimeScales;
24  import org.orekit.utils.Constants;
25  import org.orekit.utils.units.Unit;
26  
27  import java.util.regex.Pattern;
28  
29  /** Transient data used for parsing a SINEX file.
30   * @param <T> type of the SINEX files
31   * @author Luc Maisonobe
32   * @since 13.0
33   */
34  public abstract class ParseInfo<T extends AbstractSinex> {
35  
36      /** 00:000:00000 epoch. */
37      private static final String DEFAULT_EPOCH_TWO_DIGITS = "00:000:00000";
38  
39      /** 0000:000:00000 epoch. */
40      private static final String DEFAULT_EPOCH_FOUR_DIGITS = "0000:000:00000";
41  
42      /** Pattern for delimiting regular expressions. */
43      private static final Pattern SEPARATOR = Pattern.compile(":");
44  
45      /** Time scales. */
46      private final TimeScales timeScales;
47  
48      /** Name of the data source. */
49      private String name;
50  
51      /** Current line. */
52      private String line;
53  
54      /** Current line number of the navigation message. */
55      private int lineNumber;
56  
57      /** SINEX file creation date as extracted for the first line. */
58      private AbsoluteDate creationDate;
59  
60      /** SINEX file creation date as extracted for the first line. */
61      private String creationDateString;
62  
63      /** Start time of the data used in the Sinex solution. */
64      private AbsoluteDate startDate;
65  
66      /** Start time of the data used in the Sinex solution. */
67      private String startDateString;
68  
69      /** End time of the data used in the Sinex solution. */
70      private AbsoluteDate endDate;
71  
72      /** End time of the data used in the Sinex solution. */
73      private String endDateString;
74  
75      /** Time scale. */
76      private TimeScale timeScale;
77  
78      /** Simple constructor.
79       * @param timeScales time scales
80       */
81      protected ParseInfo(final TimeScales timeScales) {
82          this.timeScales = timeScales;
83      }
84  
85      /** Start parsing of a new data source.
86       * @param newName name of the new data source
87       */
88      void newSource(final String newName) {
89          // initialize parsing
90          this.name = newName;
91          this.line = null;
92          this.lineNumber = 0;
93      }
94  
95      /** Get name of data source.
96       * @return name of data source
97       */
98      protected String getName() {
99          return name;
100     }
101 
102     /** Set current line.
103      * @param line current line
104      */
105     void setLine(final String line)
106     {
107         this.line = line;
108     }
109 
110     /** Get current line.
111      * @return current line
112      */
113     String getLine() {
114         return line;
115     }
116 
117     /** Increment line number.
118      */
119     void incrementLineNumber() {
120         ++lineNumber;
121     }
122 
123     /** Get current line number.
124      * @return current line number
125      */
126     int getLineNumber() {
127         return lineNumber;
128     }
129 
130     /** Set creation date.
131      * @param dateString creation date
132      */
133     protected void setCreationDate(final String dateString) {
134         this.creationDateString = dateString;
135         this.creationDate       = stringEpochToAbsoluteDate(creationDateString, false);
136     }
137 
138     /** Get creation date.
139      * @return creation date
140      */
141     protected AbsoluteDate getCreationDate() {
142         return creationDate;
143     }
144 
145     /** Set start date if earlier than previous setting.
146      * @param candidateStartDateString candidate start date
147      */
148     protected void setStartDateIfEarlier(final String candidateStartDateString) {
149         final AbsoluteDate candidateStart = stringEpochToAbsoluteDate(candidateStartDateString, true);
150         if (startDate == null || candidateStart.isBefore(startDate)) {
151             // this is either the first setting
152             // or we are parsing a new data source referring to an earlier date than previous ones
153             this.startDateString = candidateStartDateString;
154             this.startDate = candidateStart;
155         }
156     }
157 
158     /** Get start date.
159      * @return start date
160      */
161     protected AbsoluteDate getStartDate() {
162         return startDate;
163     }
164 
165     /** Set end date if later than previous setting.
166      * @param candidateEndDateString end date
167      */
168     protected void setEndDateIfLater(final String candidateEndDateString) {
169         final AbsoluteDate candidateEnd = stringEpochToAbsoluteDate(candidateEndDateString, true);
170         if (endDate == null || candidateEnd.isAfter(endDate)) {
171             // this is either the first setting
172             // or we are parsing a new data source referring to a later date than previous ones
173             this.endDateString = candidateEndDateString;
174             this.endDate = candidateEnd;
175         }
176     }
177 
178     /** Get end date.
179      * @return end date
180      */
181     protected AbsoluteDate getEndDate() {
182         return endDate;
183     }
184 
185     /** Set time scale.
186      * @param timeScale time scale
187      */
188     protected void setTimeScale(final TimeScale timeScale) {
189 
190         this.timeScale = timeScale;
191 
192         // A time scale has been parsed, update start, end, and creation dates
193         // to take into account the time scale
194         if (startDateString != null) {
195             startDate = stringEpochToAbsoluteDate(startDateString, true);
196         }
197         if (endDateString != null) {
198             endDate = stringEpochToAbsoluteDate(endDateString, false);
199         }
200         if (creationDateString != null) {
201             creationDate = stringEpochToAbsoluteDate(creationDateString, false);
202         }
203 
204     }
205 
206     /** Get time scales.
207      * @return time scales
208      */
209     TimeScales getTimeScales() {
210         return timeScales;
211     }
212 
213     /** Build the parsed file.
214      * @return built parsed file
215      */
216     protected abstract T build();
217 
218     /** Extract a string from current line.
219      * @param start  start index of the string
220      * @param length length of the string
221      * @return parsed string
222      */
223     protected String parseString(final int start, final int length) {
224         return line.substring(start, FastMath.min(line.length(), start + length)).trim();
225     }
226 
227     /** Extract a double from current line.
228      * @param start  start index of the real
229      * @param length length of the real
230      * @return parsed real
231      */
232     protected double parseDouble(final int start, final int length) {
233         return Double.parseDouble(parseString(start, length));
234     }
235 
236     /** Extract an integer from current line.
237      * @param start  start index of the real
238      * @param length length of the real
239      * @return parsed integer
240      */
241     protected int parseInt(final int start, final int length) {
242         return Integer.parseInt(parseString(start, length));
243     }
244 
245     /** Extract a double from current line and convert in SI unit.
246      * @param startUnit    start index of the unit
247      * @param lengthUnit   length of the unit
248      * @param startDouble  start index of the real
249      * @param lengthDouble length of the real
250      * @return parsed double in SI unit
251      */
252     protected double parseDoubleWithUnit(final int startUnit, final int lengthUnit,
253                                          final int startDouble, final int lengthDouble) {
254         final Unit unit = Unit.parse(parseString(startUnit, lengthUnit));
255         return unit.toSI(parseDouble(startDouble, lengthDouble));
256     }
257 
258     /** Transform a String epoch to an AbsoluteDate.
259      * @param stringDate string epoch
260      * @param isStart    true if epoch is a start validity epoch
261      * @return the corresponding AbsoluteDate
262      */
263     protected AbsoluteDate stringEpochToAbsoluteDate(final String stringDate, final boolean isStart) {
264 
265         // Deal with 00:000:00000 epochs
266         if (DEFAULT_EPOCH_TWO_DIGITS.equals(stringDate) || DEFAULT_EPOCH_FOUR_DIGITS.equals(stringDate)) {
267             // If it's a start validity epoch, the file start date shall be used.
268             // For end validity epoch, future infinity is acceptable.
269             return isStart ? startDate : AbsoluteDate.FUTURE_INFINITY;
270         }
271 
272         // Date components
273         final String[] fields = SEPARATOR.split(stringDate);
274 
275         // Read fields
276         final int digitsYear = Integer.parseInt(fields[0]);
277         final int day = Integer.parseInt(fields[1]);
278         final int secInDay = Integer.parseInt(fields[2]);
279 
280         // Data year
281         final int year;
282         if (digitsYear > 50 && digitsYear < 100) {
283             year = 1900 + digitsYear;
284         } else if (digitsYear < 100) {
285             year = 2000 + digitsYear;
286         } else {
287             year = digitsYear;
288         }
289 
290         // Return an absolute date.
291         // Initialize to 1st January of the given year because
292         // sometimes day is equal to 0 in the file.
293         return new AbsoluteDate(new DateComponents(year, 1, 1), timeScale).
294                shiftedBy(Constants.JULIAN_DAY * (day - 1)).
295                shiftedBy(secInDay);
296 
297     }
298 
299 }