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.data;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.text.ParseException;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Set;
29  import java.util.regex.Pattern;
30  
31  import org.orekit.errors.OrekitException;
32  import org.orekit.errors.OrekitMessages;
33  import org.orekit.files.rinex.HatanakaCompressFilter;
34  
35  /** This class manages supported {@link DataProvider data providers}.
36   * <p>
37   * This class is the primary point of access for all data loading features. It
38   * is used for example to load Earth Orientation Parameters used by IERS frames,
39   * to load UTC leap seconds used by time scales, to load planetary ephemerides...
40   *
41   * <p>
42   * It is user-customizable: users can add their own data providers at will. This
43   * allows them for example to use a database or an existing data loading library
44   * in order to embed an Orekit enabled application in a global system with its
45   * own data handling mechanisms. There is no upper limitation on the number of
46   * providers, but often each application will use only a few.
47   * </p>
48   *
49   * <p>
50   * If the list of providers is empty when attempting to {@link #feed(String, DataLoader)
51   * feed} a file loader, the {@link #addDefaultProviders()} method is called
52   * automatically to set up a default configuration. This default configuration
53   * contains one {@link DataProvider data provider} for each component of the
54   * path-like list specified by the java property <code>orekit.data.path</code>.
55   * See the {@link #feed(String, DataLoader) feed} method documentation for further
56   * details. The default providers configuration is <em>not</em> set up if the list
57   * is not empty. If users want to have both the default providers and additional
58   * providers, they must call explicitly the {@link #addDefaultProviders()} method.
59   * </p>
60   *
61   * <p>
62   * The default configuration uses a predefined set of {@link DataFilter data filters}
63   * that already handled gzip-compressed files (recognized by the {@code .gz} suffix),
64   * Unix-compressed files (recognized by the {@code .Z} suffix) and Hatanaka compressed
65   * RINEX files. Users can access the {@link #getFiltersManager() filters manager} to
66   * set up custom filters for handling specific types of filters (decompression,
67   * deciphering...).
68   * </p>
69   *
70   * @author Luc Maisonobe
71   * @see DirectoryCrawler
72   * @see ClasspathCrawler
73   */
74  public class DataProvidersManager {
75  
76      /** Name of the property defining the root directories or zip/jar files path for default configuration. */
77      public static final String OREKIT_DATA_PATH = "orekit.data.path";
78  
79      /** Supported data providers. */
80      private final List<DataProvider> providers;
81  
82      /** Manager for filters.
83       * @since 11.0
84       */
85      private final FiltersManager filtersManager;
86  
87      /** Loaded data. */
88      private final Set<String> loaded;
89  
90      /** Build an instance with default configuration. */
91      public DataProvidersManager() {
92          providers      = new ArrayList<>();
93          filtersManager = new FiltersManager();
94          loaded         = new LinkedHashSet<>();
95          resetFiltersToDefault();
96      }
97  
98      /** Get the manager for filters.
99       * @return filters manager
100      * @since 11.0
101      */
102     public FiltersManager getFiltersManager() {
103         return filtersManager;
104     }
105 
106     /** Reset all filters to default.
107      * <p>
108      * This method {@link FiltersManager#clearFilters() clears} the
109      * {@link #getFiltersManager() filter manager} and then
110      * {@link FiltersManager#addFilter(DataFilter) adds} back the
111      * default filters
112      * </p>
113      * @since 11.0
114      */
115     public void resetFiltersToDefault() {
116 
117         // clear the existing filters
118         filtersManager.clearFilters();
119 
120         // set up predefined filters
121         filtersManager.addFilter(new GzipFilter());
122         filtersManager.addFilter(new UnixCompressFilter());
123         filtersManager.addFilter(new HatanakaCompressFilter());
124 
125     }
126 
127     /** Add the default providers configuration.
128      * <p>
129      * The default configuration contains one {@link DataProvider data provider}
130      * for each component of the path-like list specified by the java property
131      * <code>orekit.data.path</code>.
132      * </p>
133      * <p>
134      * If the property is not set or is null, no data will be available to the library
135      * (for example no pole corrections will be applied and only predefined UTC steps
136      * will be taken into account). No errors will be triggered in this case.
137      * </p>
138      * <p>
139      * If the property is set, it must contains a list of existing directories or zip/jar
140      * archives. One {@link DirectoryCrawler} instance will be set up for each
141      * directory and one {@link ZipJarCrawler} instance (configured to look for the
142      * archive in the filesystem) will be set up for each zip/jar archive. The list
143      * elements in the java property are separated using the standard path separator for
144      * the operating system as returned by {@link System#getProperty(String)
145      * System.getProperty("path.separator")}. This standard path separator is ":" on
146      * Linux and Unix type systems and ";" on Windows types systems.
147      * </p>
148      */
149     public void addDefaultProviders() {
150 
151         // get the path containing all components
152         final String path = System.getProperty(OREKIT_DATA_PATH);
153         if (path != null && !"".equals(path)) {
154 
155             // extract the various components
156             for (final String name : path.split(System.getProperty("path.separator"))) {
157                 if (!"".equals(name)) {
158 
159                     final File file = new File(name);
160 
161                     // check component
162                     if (!file.exists()) {
163                         if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
164                             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, name);
165                         } else {
166                             throw new OrekitException(OrekitMessages.DATA_ROOT_DIRECTORY_DOES_NOT_EXIST, name);
167                         }
168                     }
169 
170                     if (file.isDirectory()) {
171                         addProvider(new DirectoryCrawler(file));
172                     } else if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
173                         addProvider(new ZipJarCrawler(file));
174                     } else {
175                         throw new OrekitException(OrekitMessages.NEITHER_DIRECTORY_NOR_ZIP_OR_JAR, name);
176                     }
177 
178                 }
179             }
180         }
181 
182     }
183 
184     /** Add a data provider to the supported list.
185      * @param provider data provider to add
186      * @see #removeProvider(DataProvider)
187      * @see #clearProviders()
188      * @see #isSupported(DataProvider)
189      * @see #getProviders()
190      */
191     public void addProvider(final DataProvider provider) {
192         providers.add(provider);
193     }
194 
195     /** Remove one provider.
196      * @param provider provider instance to remove
197      * @return instance removed (null if the provider was not already present)
198      * @see #addProvider(DataProvider)
199      * @see #clearProviders()
200      * @see #isSupported(DataProvider)
201      * @see #getProviders()
202      * @since 5.1
203      */
204     public DataProvider removeProvider(final DataProvider provider) {
205         for (final Iterator<DataProvider> iterator = providers.iterator(); iterator.hasNext();) {
206             final DataProvider current = iterator.next();
207             if (current == provider) {
208                 iterator.remove();
209                 return provider;
210             }
211         }
212         return null;
213     }
214 
215     /** Remove all data providers.
216      * @see #addProvider(DataProvider)
217      * @see #removeProvider(DataProvider)
218      * @see #isSupported(DataProvider)
219      * @see #getProviders()
220      */
221     public void clearProviders() {
222         providers.clear();
223     }
224 
225     /** Check if some provider is supported.
226      * @param provider provider to check
227      * @return true if the specified provider instance is already in the supported list
228      * @see #addProvider(DataProvider)
229      * @see #removeProvider(DataProvider)
230      * @see #clearProviders()
231      * @see #getProviders()
232      * @since 5.1
233      */
234     public boolean isSupported(final DataProvider provider) {
235         for (final DataProvider current : providers) {
236             if (current == provider) {
237                 return true;
238             }
239         }
240         return false;
241     }
242 
243     /** Get an unmodifiable view of the list of supported providers.
244      * @return unmodifiable view of the list of supported providers
245      * @see #addProvider(DataProvider)
246      * @see #removeProvider(DataProvider)
247      * @see #clearProviders()
248      * @see #isSupported(DataProvider)
249      */
250     public List<DataProvider> getProviders() {
251         return Collections.unmodifiableList(providers);
252     }
253 
254     /** Get an unmodifiable view of the set of data file names that have been loaded.
255      * <p>
256      * The names returned are exactly the ones that were given to the {@link
257      * DataLoader#loadData(InputStream, String) DataLoader.loadData} method.
258      * </p>
259      * @return unmodifiable view of the set of data file names that have been loaded
260      * @see #feed(String, DataLoader)
261      * @see #clearLoadedDataNames()
262      */
263     public Set<String> getLoadedDataNames() {
264         return Collections.unmodifiableSet(loaded);
265     }
266 
267     /** Clear the set of data file names that have been loaded.
268      * @see #getLoadedDataNames()
269      */
270     public void clearLoadedDataNames() {
271         loaded.clear();
272     }
273 
274     /** Feed a data file loader by browsing all data providers.
275      * <p>
276      * If this method is called with an empty list of providers, a default
277      * providers configuration is set up. This default configuration contains
278      * only one {@link DataProvider data provider}: a {@link DirectoryCrawler}
279      * instance that loads data from files located somewhere in a directory hierarchy.
280      * This default provider is <em>not</em> added if the list is not empty. If users
281      * want to have both the default provider and other providers, they must add it
282      * explicitly.
283      * </p>
284      * <p>
285      * The providers are used in the order in which they were {@link #addProvider(DataProvider)
286      * added}. As soon as one provider is able to feed the data loader, the loop is
287      * stopped. If no provider is able to feed the data loader, then the last error
288      * triggered is thrown.
289      * </p>
290      * @param supportedNames regular expression for file names supported by the visitor
291      * @param loader data loader to use
292      * @return true if some data has been loaded
293      */
294     public boolean feed(final String supportedNames, final DataLoader loader) {
295 
296         final Pattern supported = Pattern.compile(supportedNames);
297 
298         // set up a default configuration if no providers have been set
299         if (providers.isEmpty()) {
300             addDefaultProviders();
301         }
302 
303         // monitor the data that the loader will load
304         final DataLoader monitoredLoader = new MonitoringWrapper(loader);
305 
306         // crawl the data collection
307         OrekitException delayedException = null;
308         for (final DataProvider provider : providers) {
309             try {
310 
311                 // try to feed the visitor using the current provider
312                 if (provider.feed(supported, monitoredLoader, this)) {
313                     return true;
314                 }
315 
316             } catch (OrekitException oe) {
317                 // remember the last error encountered
318                 delayedException = oe;
319             }
320         }
321 
322         if (delayedException != null) {
323             throw delayedException;
324         }
325 
326         return false;
327 
328     }
329 
330     /** Data loading monitoring wrapper class. */
331     private class MonitoringWrapper implements DataLoader {
332 
333         /** Wrapped loader. */
334         private final DataLoader loader;
335 
336         /** Simple constructor.
337          * @param loader loader to monitor
338          */
339         MonitoringWrapper(final DataLoader loader) {
340             this.loader = loader;
341         }
342 
343         /** {@inheritDoc} */
344         public boolean stillAcceptsData() {
345             // delegate to monitored loader
346             return loader.stillAcceptsData();
347         }
348 
349         /** {@inheritDoc} */
350         public void loadData(final InputStream input, final String name)
351             throws IOException, ParseException, OrekitException {
352 
353             // delegate to monitored loader
354             loader.loadData(input, name);
355 
356             // monitor the fact new data has been loaded
357             loaded.add(name);
358 
359         }
360 
361     }
362 
363 }