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.IOException;
20  import java.io.InputStream;
21  import java.net.URI;
22  import java.net.URISyntaxException;
23  import java.text.ParseException;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.regex.Pattern;
27  
28  import org.hipparchus.exception.DummyLocalizable;
29  import org.hipparchus.exception.LocalizedCoreFormats;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitMessages;
32  
33  
34  /** Provider for data files stored as resources in the classpath.
35   * <p>
36   * This class handles a list of data files or zip/jar archives located in the
37   * classpath. Since the classpath is not a tree structure the list elements
38   * cannot be whole directories recursively browsed as in {@link
39   * DirectoryCrawler}, they must be data files or zip/jar archives.
40   * </p>
41   * <p>
42   * A typical use case is to put all data files in a single zip or jar archive
43   * and to build an instance of this class with the single name of this zip/jar
44   * archive. Two different instances may be used one for user or project specific
45   * data and another one for system-wide or general data.
46   * </p>
47   * <p>
48   * All {@link FiltersManager#addFilter(DataFilter) registered}
49   * {@link DataFilter filters} are applied.
50   * </p>
51   * <p>
52   * Zip archives entries are supported recursively.
53   * </p>
54   * <p>
55   * This is a simple application of the <code>visitor</code> design pattern for
56   * list browsing.
57   * </p>
58   * @see DataProvidersManager
59   * @author Luc Maisonobe
60   */
61  public class ClasspathCrawler implements DataProvider {
62  
63      /** List elements. */
64      private final List<String> listElements;
65  
66      /** Class loader to use. */
67      private final ClassLoader classLoader;
68  
69      /** Build a data classpath crawler.
70       * <p>
71       * Calling this constructor has the same effect as calling
72       * {@link #ClasspathCrawler(ClassLoader, String...)} with
73       * {@code ClasspathCrawler.class.getClassLoader()} as first
74       * argument.
75       * </p>
76       * @param list list of data file names within the classpath
77       */
78      public ClasspathCrawler(final String... list) {
79          this(ClasspathCrawler.class.getClassLoader(), list);
80      }
81  
82      /** Build a data classpath crawler.
83       * @param classLoader class loader to use to retrieve the resources
84       * @param list list of data file names within the classpath
85       */
86      public ClasspathCrawler(final ClassLoader classLoader, final String... list) {
87  
88          listElements = new ArrayList<>();
89          this.classLoader = classLoader;
90  
91          // check the resources
92          for (final String name : list) {
93              if (!"".equals(name)) {
94  
95                  final String convertedName = name.replace('\\', '/');
96                  try (InputStream stream = classLoader.getResourceAsStream(convertedName)) {
97                      if (stream == null) {
98                          throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_RESOURCE, name);
99                      }
100                     listElements.add(convertedName);
101                 } catch (IOException exc) {
102                     // ignore this error
103                 }
104 
105             }
106         }
107 
108     }
109 
110     /** {@inheritDoc} */
111     public boolean feed(final Pattern supported,
112                         final DataLoader visitor,
113                         final DataProvidersManager manager) {
114 
115         try {
116             OrekitException delayedException = null;
117             boolean loaded = false;
118             for (final String name : listElements) {
119                 try {
120 
121                     if (visitor.stillAcceptsData()) {
122                         if (ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
123 
124                             // browse inside the zip/jar file
125                             final DataProvider zipProvider = new ZipJarCrawler(name);
126                             loaded = zipProvider.feed(supported, visitor, manager) || loaded;
127 
128                         } else {
129 
130                             // match supported name against file name #618
131                             final String fileName = name.substring(name.lastIndexOf('/') + 1);
132                             DataSource data = new DataSource(fileName, () -> classLoader.getResourceAsStream(name));
133                             // apply all registered filters
134                             data = manager.getFiltersManager().applyRelevantFilters(data);
135 
136                             if (supported.matcher(data.getName()).matches()) {
137                                 // visit the current file
138                                 try (InputStream input = data.getOpener().openStreamOnce()) {
139                                     final URI uri = classLoader.getResource(name).toURI();
140                                     visitor.loadData(input, uri.toString());
141                                     loaded = true;
142                                 }
143 
144                             }
145 
146                         }
147                     }
148 
149                 } catch (OrekitException oe) {
150                     // maybe the next path component will be able to provide data
151                     // wait until all components have been tried
152                     delayedException = oe;
153                 } catch (URISyntaxException use) {
154                     // this should bever happen
155                     throw new OrekitException(use, LocalizedCoreFormats.SIMPLE_MESSAGE, use.getMessage());
156                 }
157             }
158 
159             if (!loaded && delayedException != null) {
160                 throw delayedException;
161             }
162 
163             return loaded;
164 
165         } catch (IOException | ParseException ioe) {
166             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
167         }
168 
169     }
170 
171 }