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 }