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;
18  
19  import org.hipparchus.exception.DummyLocalizable;
20  import org.hipparchus.exception.Localizable;
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.util.FastMath;
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitMessages;
25  import org.orekit.frames.Frame;
26  import org.orekit.frames.FramesFactory;
27  import org.orekit.frames.Predefined;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.time.TimeComponents;
30  import org.orekit.time.TimeScale;
31  
32  import java.io.BufferedReader;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.InputStreamReader;
36  import java.lang.reflect.Array;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.NoSuchElementException;
43  import java.util.regex.Matcher;
44  import java.util.regex.Pattern;
45  
46  /** Simple parser for key/value files.
47   * @param Key type of the parameter keys
48   */
49  public class KeyValueFileParser<Key extends Enum<Key>> {
50  
51      /** Error message for unknown frame. */
52      private static final Localizable UNKNOWN_FRAME =
53          new DummyLocalizable("unknown frame {0}");
54  
55      /** Error message for not Earth frame. */
56      private static final Localizable NOT_EARTH_FRAME =
57          new DummyLocalizable("frame {0} is not an Earth frame");
58  
59      /** Enum type. */
60      private final Class<Key> enumType;
61  
62      /** Key/scalar value map. */
63      private final Map<Key, String> scalarMap;
64  
65      /** Key/array value map. */
66      private final Map<Key, List<String>> arrayMap;
67  
68      /** Simple constructor.
69       * @param enumType type of the parameters keys enumerate
70       */
71      public KeyValueFileParser(Class<Key> enumType) {
72          this.enumType  = enumType;
73          this.scalarMap = new HashMap<Key, String>();
74          this.arrayMap  = new HashMap<Key, List<String>>();
75      }
76  
77      /** Parse an input file.
78       * <p>
79       * The input file syntax is a set of {@code key=value} lines or
80       * {@code key[i]=value} lines. Blank lines and lines starting with '#'
81       * (after whitespace trimming) are silently ignored. The equal sign may
82       * be surrounded by space characters. Keys must correspond to the
83       * {@link Key} enumerate constants, given that matching is not case
84       * sensitive and that '_' characters may appear as '.' characters in
85       * the file. This means that the lines:
86       * <pre>
87       *   # this is the semi-major axis
88       *   orbit.circular.a   = 7231582
89       * </pre>
90       * are perfectly right and correspond to key {@code Key#ORBIT_CIRCULAR_A} if
91       * such a constant exists in the enumerate.
92       * </p>
93       * <p>
94       * When the key[i] notation is used, all the values extracted from lines
95       * with the same key will be put in a general array that will be retrieved
96       * using one of the {@code getXxxArray(key)} methods. They will <em>not</em>
97       * be available with the {@code getXxx(key)} methods without the {@code Array}.
98       * </p>
99       * @param input input stream
100      * @exception IOException if input file cannot be read
101      */
102     public void parseInput(final String name, final InputStream input)
103         throws IOException, OrekitException {
104 
105         final Pattern        arrayPattern = Pattern.compile("([\\w\\.]+)\\s*\\[([0-9]+)\\]");
106         final BufferedReader reader       = new BufferedReader(new InputStreamReader(input));
107         int lineNumber = 0;
108         for (String line = reader.readLine(); line != null; line = reader.readLine()) {
109             ++lineNumber;
110             line = line.trim();
111             // we ignore blank lines and line starting with '#'
112             if ((line.length() > 0) && !line.startsWith("#")) {
113                 String[] fields = line.split("\\s*=\\s*");
114                 if (fields.length != 2) {
115                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
116                                               lineNumber, name, line);
117                 }
118                 final Matcher matcher = arrayPattern.matcher(fields[0]);
119                 if (matcher.matches()) {
120                     // this is a key[i]=value line
121                     String canonicalized = matcher.group(1).toUpperCase().replaceAll("\\.", "_");
122                     Key key = Key.valueOf(enumType, canonicalized);
123                     Integer index = Integer.valueOf(matcher.group(2));
124                     List<String> list = arrayMap.get(key);
125                     if (list == null) {
126                         list = new ArrayList<String>();
127                         arrayMap.put(key, list);
128                     }
129                     while (index >= list.size()) {
130                         // insert empty strings for missing elements
131                         list.add("");
132                     }
133                     list.set(index, fields[1]);
134                 } else {
135                     // this is a key=value line
136                     String canonicalized = fields[0].toUpperCase().replaceAll("\\.", "_");
137                     Key key = Key.valueOf(enumType, canonicalized);
138                     scalarMap.put(key, fields[1]);
139                 }
140             }
141         }
142         reader.close();
143 
144     }
145 
146     /** Check if a key is contained in the map.
147      * @param key parameter key
148      * @return true if the key is contained in the map
149      */
150     public boolean containsKey(final Key key) {
151         return scalarMap.containsKey(key) || arrayMap.containsKey(key);
152     }
153 
154     /** Get a raw string value from a parameters map.
155      * @param key parameter key
156      * @return string value corresponding to the key
157      * @exception NoSuchElementException if key is not in the map
158      */
159     public String getString(final Key key) throws NoSuchElementException {
160         final String value = scalarMap.get(key);
161         if (value == null) {
162             throw new NoSuchElementException(key.toString());
163         }
164         return value.trim();
165     }
166 
167     /** Get a raw string values array from a parameters map.
168      * @param key parameter key
169      * @return string values array corresponding to the key
170      * @exception NoSuchElementException if key is not in the map
171      */
172     public String[] getStringArray(final Key key) throws NoSuchElementException {
173         final List<String> list = arrayMap.get(key);
174         if (list == null) {
175             throw new NoSuchElementException(key.toString());
176         }
177         String[] array = new String[list.size()];
178         for (int i = 0; i < array.length; ++i) {
179             array[i] = list.get(i).trim();
180         }
181         return array;
182     }
183 
184     /** Get a raw double value from a parameters map.
185      * @param key parameter key
186      * @return double value corresponding to the key
187      * @exception NoSuchElementException if key is not in the map
188      */
189     public double getDouble(final Key key) throws NoSuchElementException {
190         return Double.parseDouble(getString(key));
191     }
192 
193     /** Get a raw double values array from a parameters map.
194      * @param key parameter key
195      * @return double values array corresponding to the key
196      * @exception NoSuchElementException if key is not in the map
197      */
198     public double[] getDoubleArray(final Key key) throws NoSuchElementException {
199         String[] strings = getStringArray(key);
200         double[] array = new double[strings.length];
201         for (int i = 0; i < array.length; ++i) {
202             array[i] = Double.parseDouble(strings[i]);
203         }
204         return array;
205     }
206 
207     /** Get a raw int value from a parameters map.
208      * @param key parameter key
209      * @return int value corresponding to the key
210      * @exception NoSuchElementException if key is not in the map
211      */
212     public int getInt(final Key key) throws NoSuchElementException {
213         return Integer.parseInt(getString(key));
214     }
215 
216     /** Get a raw int values array from a parameters map.
217      * @param key parameter key
218      * @return int values array corresponding to the key
219      * @exception NoSuchElementException if key is not in the map
220      */
221     public int[] getIntArray(final Key key) throws NoSuchElementException {
222         String[] strings = getStringArray(key);
223         int[] array = new int[strings.length];
224         for (int i = 0; i < array.length; ++i) {
225             array[i] = Integer.parseInt(strings[i]);
226         }
227         return array;
228     }
229 
230     /** Get a raw boolean value from a parameters map.
231      * @param key parameter key
232      * @return boolean value corresponding to the key
233      * @exception NoSuchElementException if key is not in the map
234      */
235     public boolean getBoolean(final Key key) throws NoSuchElementException {
236         return Boolean.parseBoolean(getString(key));
237     }
238 
239     /** Get a raw boolean values array from a parameters map.
240      * @param key parameter key
241      * @return boolean values array corresponding to the key
242      * @exception NoSuchElementException if key is not in the map
243      */
244     public boolean[] getBooleanArray(final Key key) throws NoSuchElementException {
245         String[] strings = getStringArray(key);
246         boolean[] array = new boolean[strings.length];
247         for (int i = 0; i < array.length; ++i) {
248             array[i] = Boolean.parseBoolean(strings[i]);
249         }
250         return array;
251     }
252 
253     /** Get an angle value from a parameters map.
254      * <p>
255      * The angle is considered to be in degrees in the file, it will be returned in radians
256      * </p>
257      * @param key parameter key
258      * @return angular value corresponding to the key, in radians
259      * @exception NoSuchElementException if key is not in the map
260      */
261     public double getAngle(final Key key) throws NoSuchElementException {
262         return FastMath.toRadians(getDouble(key));
263     }
264 
265     /** Get an angle values array from a parameters map.
266      * @param key parameter key
267      * @return angle values array corresponding to the key
268      * @exception NoSuchElementException if key is not in the map
269      */
270     public double[] getAngleArray(final Key key) throws NoSuchElementException {
271         double[] array = getDoubleArray(key);
272         for (int i = 0; i < array.length; ++i) {
273             array[i] = FastMath.toRadians(array[i]);
274         }
275         return array;
276     }
277 
278     /** Get a date value from a parameters map.
279      * @param key parameter key
280      * @param scale time scale in which the date is to be parsed
281      * @return date value corresponding to the key
282      * @exception NoSuchElementException if key is not in the map
283      */
284     public AbsoluteDate getDate(final Key key, TimeScale scale) throws NoSuchElementException {
285         return new AbsoluteDate(getString(key), scale);
286     }
287 
288     /** Get a date values array from a parameters map.
289      * @param key parameter key
290      * @param scale time scale in which the date is to be parsed
291      * @return date values array corresponding to the key
292      * @exception NoSuchElementException if key is not in the map
293      */
294     public AbsoluteDate[] getDateArray(final Key key, TimeScale scale) throws NoSuchElementException {
295         String[] strings = getStringArray(key);
296         AbsoluteDate[] array = new AbsoluteDate[strings.length];
297         for (int i = 0; i < array.length; ++i) {
298             array[i] = new AbsoluteDate(strings[i], scale);
299         }
300         return array;
301     }
302 
303     /** Get a time value from a parameters map.
304      * @param key parameter key
305      * @return time value corresponding to the key
306      * @exception NoSuchElementException if key is not in the map
307      */
308     public TimeComponents getTime(final Key key) throws NoSuchElementException {
309         return TimeComponents.parseTime(getString(key));
310     }
311 
312     /** Get a time values array from a parameters map.
313      * @param key parameter key
314      * @return time values array corresponding to the key
315      * @exception NoSuchElementException if key is not in the map
316      */
317     public TimeComponents[] getTimeArray(final Key key) throws NoSuchElementException {
318         String[] strings = getStringArray(key);
319         TimeComponents[] array = new TimeComponents[strings.length];
320         for (int i = 0; i < array.length; ++i) {
321             array[i] = TimeComponents.parseTime(strings[i]);
322         }
323         return array;
324     }
325 
326     /** Get a vector value from a parameters map.
327      * @param xKey parameter key for abscissa
328      * @param yKey parameter key for ordinate
329      * @param zKey parameter key for height
330      * @return date value corresponding to the key
331      * @exception NoSuchElementException if key is not in the map
332      */
333     public Vector3D getVector(final Key xKey, final Key yKey, final Key zKey)
334         throws NoSuchElementException {
335         return new Vector3D(getDouble(xKey), getDouble(yKey), getDouble(zKey));
336     }
337 
338     /** Get a vector values array from a parameters map.
339      * @param xKey parameter key for abscissa
340      * @param yKey parameter key for ordinate
341      * @param zKey parameter key for height
342      * @return date value corresponding to the key
343      * @exception NoSuchElementException if key is not in the map
344      */
345     public Vector3D[] getVectorArray(final Key xKey, final Key yKey, final Key zKey)
346         throws NoSuchElementException {
347         String[] xStrings = getStringArray(xKey);
348         String[] yStrings = getStringArray(yKey);
349         String[] zStrings = getStringArray(zKey);
350         Vector3D[] array = new Vector3D[xStrings.length];
351         for (int i = 0; i < array.length; ++i) {
352             array[i] = new Vector3D(Double.parseDouble(xStrings[i]),
353                                     Double.parseDouble(yStrings[i]),
354                                     Double.parseDouble(zStrings[i]));
355         }
356         return array;
357     }
358 
359     /** Get a strings list from a parameters map.
360      * @param key parameter key
361      * @param separator elements separator
362      * @return strings list value corresponding to the key
363      * @exception NoSuchElementException if key is not in the map
364      */
365     public List<String> getStringsList(final Key key, final char separator)
366         throws NoSuchElementException {
367         final String value = scalarMap.get(key);
368         if (value == null) {
369             throw new NoSuchElementException(key.toString());
370         }
371         return Arrays.asList(value.trim().split("\\s*" + separator + "\\s*"));
372     }
373 
374     /** Get a strings list array from a parameters map.
375      * @param key parameter key
376      * @param separator elements separator
377      * @return strings list array corresponding to the key
378      * @exception NoSuchElementException if key is not in the map
379      */
380     public List<String>[] getStringsListArray(final Key key, final char separator)
381         throws NoSuchElementException {
382         final String[] strings = getStringArray(key);
383         @SuppressWarnings("unchecked")
384         final List<String>[] array = (List<String>[]) Array.newInstance(List.class, strings.length);
385         for (int i = 0; i < array.length; ++i) {
386             array[i] = Arrays.asList(strings[i].trim().split("\\s*" + separator + "\\s*"));
387         }
388         return array;
389     }
390 
391     /** Get an inertial frame from a parameters map.
392      * @param key parameter key
393      * @return inertial frame corresponding to the key
394      * @exception NoSuchElementException if key is not in the map
395      */
396     public Frame getInertialFrame(final Key key) throws NoSuchElementException, OrekitException {
397 
398         // get the name of the desired frame
399         final String frameName = getString(key);
400 
401         // check the name against predefined frames
402         for (Predefined predefined : Predefined.values()) {
403             if (frameName.equals(predefined.getName())) {
404                 if (FramesFactory.getFrame(predefined).isPseudoInertial()) {
405                     return FramesFactory.getFrame(predefined);
406                 } else {
407                     throw new OrekitException(OrekitMessages.NON_PSEUDO_INERTIAL_FRAME,
408                                               frameName);
409                 }
410             }
411         }
412 
413         // none of the frames match the name
414         throw new OrekitException(UNKNOWN_FRAME, frameName);
415 
416     }
417 
418     /** Get an Earth frame from a parameters map.
419      * <p>
420      * We consider Earth frames are the frames with name starting with "ITRF".
421      * </p>
422      * @param key parameter key
423      * @return Earth frame corresponding to the key
424      * @exception NoSuchElementException if key is not in the map
425      */
426     public Frame getEarthFrame(final Key key)
427             throws NoSuchElementException, OrekitException {
428 
429         // get the name of the desired frame
430         final String frameName = getString(key);
431 
432         // check the name against predefined frames
433         for (Predefined predefined : Predefined.values()) {
434             if (frameName.equals(predefined.getName())) {
435                 if (predefined.toString().startsWith("ITRF") ||
436                     predefined.toString().startsWith("GTOD")) {
437                     return FramesFactory.getFrame(predefined);
438                 } else {
439                     throw new OrekitException(NOT_EARTH_FRAME, frameName);
440                 }
441             }
442         }
443 
444         // none of the frames match the name
445         throw new OrekitException(UNKNOWN_FRAME, frameName);
446 
447     }
448 
449 }