1   /* Copyright 2002-2018 CS Systèmes d'Information
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.time;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.text.ParseException;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  import org.hipparchus.util.FastMath;
30  import org.orekit.data.DataLoader;
31  import org.orekit.data.DataProvidersManager;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitMessages;
34  
35  /** Loader for UTC-TAI extracted from tai-utc.dat file from USNO.
36   * <p>
37   * This class is immutable and hence thread-safe
38   * </p>
39   * @author Luc Maisonobe
40   * @since 7.1
41   */
42  public class TAIUTCDatFilesLoader implements UTCTAIOffsetsLoader {
43  
44      /** Default supported files name pattern. */
45      public static final String DEFAULT_SUPPORTED_NAMES = "^tai-utc\\.dat$";
46  
47      /** Regular expression for supported files names. */
48      private final String supportedNames;
49  
50      /** Build a loader for tai-utc.dat file from USNO.
51       * @param supportedNames regular expression for supported files names
52       */
53      public TAIUTCDatFilesLoader(final String supportedNames) {
54          this.supportedNames = supportedNames;
55      }
56  
57      /** {@inheritDoc} */
58      @Override
59      public List<OffsetModel> loadOffsets() throws OrekitException {
60          final Parser parser = new Parser();
61          DataProvidersManager.getInstance().feed(supportedNames, parser);
62          return parser.getOffsets();
63      }
64  
65      /** Internal class performing the parsing. */
66      private static class Parser implements DataLoader {
67  
68          /** Regular expression for optional blanks. */
69          private static final String BLANKS               = "\\p{Blank}*";
70  
71          /** Regular expression for storage start. */
72          private static final String STORAGE_START        = "(";
73  
74          /** Regular expression for storage end. */
75          private static final String STORAGE_END          = ")";
76  
77          /** Regular expression for alternative. */
78          private static final String ALTERNATIVE          = "|";
79  
80          /** Regular expression matching blanks at start of line. */
81          private static final String LINE_START_REGEXP     = "^" + BLANKS;
82  
83          /** Regular expression matching blanks at end of line. */
84          private static final String LINE_END_REGEXP       = BLANKS + "$";
85  
86          /** Regular expression matching integers. */
87          private static final String INTEGER_REGEXP        = "[-+]?\\p{Digit}+";
88  
89          /** Regular expression matching real numbers. */
90          private static final String REAL_REGEXP           = "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";
91  
92          /** Regular expression matching an integer field to store. */
93          private static final String STORED_INTEGER_FIELD  = BLANKS + STORAGE_START + INTEGER_REGEXP + STORAGE_END;
94  
95          /** Regular expression matching a real field to store. */
96          private static final String STORED_REAL_FIELD     = BLANKS + STORAGE_START + REAL_REGEXP + STORAGE_END;
97  
98          /** Data lines pattern. */
99          private Pattern dataPattern;
100 
101         /** UTC-TAI offsets. */
102         private List<OffsetModel> offsets;
103 
104         /** Simple constructor.
105          */
106         Parser() {
107 
108             // data lines read:
109             // 1965 SEP  1 =JD 2439004.5  TAI-UTC=   3.8401300 S + (MJD - 38761.) X 0.001296 S
110             // 1966 JAN  1 =JD 2439126.5  TAI-UTC=   4.3131700 S + (MJD - 39126.) X 0.002592 S
111             // 1968 FEB  1 =JD 2439887.5  TAI-UTC=   4.2131700 S + (MJD - 39126.) X 0.002592 S
112             // 1972 JAN  1 =JD 2441317.5  TAI-UTC=  10.0       S + (MJD - 41317.) X 0.0      S
113             // 1972 JUL  1 =JD 2441499.5  TAI-UTC=  11.0       S + (MJD - 41317.) X 0.0      S
114             // 1973 JAN  1 =JD 2441683.5  TAI-UTC=  12.0       S + (MJD - 41317.) X 0.0      S
115             // 1974 JAN  1 =JD 2442048.5  TAI-UTC=  13.0       S + (MJD - 41317.) X 0.0      S
116 
117             // month as a three letters upper case abbreviation
118             final StringBuilder builder = new StringBuilder(BLANKS + STORAGE_START);
119             for (final Month month : Month.values()) {
120                 builder.append(month.getUpperCaseAbbreviation());
121                 builder.append(ALTERNATIVE);
122             }
123             builder.delete(builder.length() - 1, builder.length());
124             builder.append(STORAGE_END);
125             final String monthField = builder.toString();
126 
127             dataPattern = Pattern.compile(LINE_START_REGEXP +
128                                           STORED_INTEGER_FIELD + monthField + STORED_INTEGER_FIELD +
129                                           "\\p{Blank}+=JD" + STORED_REAL_FIELD +
130                                           "\\p{Blank}+TAI-UTC=" + STORED_REAL_FIELD +
131                                           "\\p{Blank}+S\\p{Blank}+\\+\\p{Blank}+\\(MJD\\p{Blank}+-" + STORED_REAL_FIELD +
132                                           "\\p{Blank}*\\)\\p{Blank}+X" + STORED_REAL_FIELD +
133                                           "\\p{Blank}*S" + LINE_END_REGEXP);
134 
135             offsets = new ArrayList<OffsetModel>();
136 
137         }
138 
139         /** Get the parsed offsets.
140          * @return parsed offsets
141          */
142         public List<OffsetModel> getOffsets() {
143             return offsets;
144         }
145 
146         /** {@inheritDoc} */
147         public boolean stillAcceptsData() {
148             return offsets.isEmpty();
149         }
150 
151         /** Load UTC-TAI offsets entries read from some file.
152          * <p>The time steps are extracted from some {@code tai-utc.dat} file.
153          * Since entries are stored in a {@link java.util.SortedMap SortedMap},
154          * they are chronologically sorted and only one entry remains for a given date.</p>
155          * @param input data input stream
156          * @param name name of the file (or zip entry)
157          * @exception IOException if data can't be read
158          * @exception ParseException if data can't be parsed
159          * @exception OrekitException if some data is missing
160          * or if some loader specific error occurs
161          */
162         public void loadData(final InputStream input, final String name)
163             throws OrekitException, IOException, ParseException {
164 
165             offsets.clear();
166 
167             // set up a reader for line-oriented file
168             final BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
169 
170             // read all file, ignoring not recognized lines
171             int lineNumber = 0;
172             DateComponents lastDate = null;
173             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
174                 ++lineNumber;
175 
176                 // check matching for data lines
177                 final Matcher matcher = dataPattern.matcher(line);
178                 if (matcher.matches()) {
179 
180                     try {
181                         // build an entry from the extracted fields
182                         final DateComponents dc1 = new DateComponents(Integer.parseInt(matcher.group(1)),
183                                                                       Month.parseMonth(matcher.group(2)),
184                                                                       Integer.parseInt(matcher.group(3)));
185                         final DateComponents dc2 = new DateComponents(DateComponents.JULIAN_EPOCH,
186                                                                       (int) FastMath.ceil(Double.parseDouble(matcher.group(4))));
187                         if (!dc1.equals(dc2)) {
188                             throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
189                                                       name, dc1.getYear(), dc1.getMonth(), dc1.getDay(), dc2.getMJD());
190                         }
191 
192                         if ((lastDate != null) && dc1.compareTo(lastDate) <= 0) {
193                             throw new OrekitException(OrekitMessages.NON_CHRONOLOGICAL_DATES_IN_FILE,
194                                                       name, lineNumber);
195                         }
196                         lastDate = dc1;
197 
198                         final double offset = Double.parseDouble(matcher.group(5));
199                         final double mjdRef = Double.parseDouble(matcher.group(6));
200                         final double slope  = Double.parseDouble(matcher.group(7));
201                         offsets.add(new OffsetModel(dc1, (int) FastMath.rint(mjdRef), offset, slope));
202 
203                     } catch (NumberFormatException nfe) {
204                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
205                                                   lineNumber, name, line);
206                     }
207                 }
208             }
209 
210             if (offsets.isEmpty()) {
211                 throw new OrekitException(OrekitMessages.NO_ENTRIES_IN_IERS_UTC_TAI_HISTORY_FILE, name);
212             }
213 
214         }
215 
216     }
217 
218 }