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.sinex;
18  
19  import org.hipparchus.exception.LocalizedCoreFormats;
20  import org.orekit.data.DataSource;
21  import org.orekit.errors.OrekitException;
22  import org.orekit.errors.OrekitMessages;
23  import org.orekit.time.TimeScales;
24  
25  import java.io.BufferedReader;
26  import java.io.IOException;
27  import java.io.Reader;
28  import java.util.Collections;
29  
30  /** Base parser for Solution INdependent EXchange (SINEX) files.
31   * @param <T> type of the SINEX file
32   * @param <P> type of the SINEX files parse info
33   * @author Luc Maisonobe
34   * @since 13.0
35   */
36  public abstract class AbstractSinexParser<T extends AbstractSinex, P extends ParseInfo<T>> {
37  
38      /** Time scales. */
39      private final TimeScales timeScales;
40  
41      /** Simple constructor.
42       * @param timeScales time scales
43       */
44      protected AbstractSinexParser(final TimeScales timeScales) {
45          this.timeScales = timeScales;
46      }
47  
48      /** Parse one or more SINEX files.
49       * @param sources sources providing the data to parse
50       * @return parsed file combining all sources
51       */
52      public T parse(final DataSource... sources) {
53  
54          // placeholders for parsed data
55          final P parseInfo = buildParseInfo();
56  
57          for (final DataSource source : sources) {
58  
59              // start parsing a new file
60              parseInfo.newSource(source.getName());
61              Iterable<LineParser<P>> candidateParsers = Collections.singleton(firstLineParser());
62  
63              try (Reader reader = source.getOpener().openReaderOnce(); BufferedReader br = new BufferedReader(reader)) {
64                  nextLine:
65                  for (parseInfo.setLine(br.readLine()); parseInfo.getLine() != null; parseInfo.setLine(br.readLine())) {
66                      parseInfo.incrementLineNumber();
67  
68                      if (!parseInfo.getLine().isEmpty()) {
69  
70                          // ignore all comment lines
71                          if (parseInfo.getLine().charAt(0) == '*') {
72                              continue;
73                          }
74  
75                          // find a parser for the current line among the available candidates
76                          for (final LineParser<P> candidate : candidateParsers) {
77                              try {
78                                  if (candidate.parseIfRecognized(parseInfo)) {
79                                      // candidate successfully parsed the line
80                                      candidateParsers = candidate.allowedNextParsers(parseInfo);
81                                      continue nextLine;
82                                  }
83                              }
84                              catch (StringIndexOutOfBoundsException | NumberFormatException e) {
85                                  throw new OrekitException(e, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
86                                                            parseInfo.getLineNumber(), parseInfo.getName(),
87                                                            parseInfo.getLine());
88                              }
89                          }
90  
91                          // no candidate could parse this line
92                          throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
93                                                    parseInfo.getLineNumber(), parseInfo.getName(),
94                                                    parseInfo.getLine());
95                      }
96  
97                  }
98  
99              } catch (IOException ioe) {
100                 throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
101             }
102 
103         }
104 
105         return parseInfo.build();
106 
107     }
108 
109     /** Get parser for the first line.
110      * @return parser for the firsty line of the file
111      */
112     protected abstract LineParser<P> firstLineParser();
113 
114     /** Build the container for parsing info.
115      * @return container for parsing info
116      */
117     protected abstract P buildParseInfo();
118 
119     /** Get the time scales.
120      * @return time scales
121      */
122     public TimeScales getTimeScales() {
123         return timeScales;
124     }
125 
126 }