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.io.InputStreamReader;
23 import java.io.Reader;
24 import java.net.URI;
25 import java.nio.ByteBuffer;
26 import java.nio.CharBuffer;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.Files;
29 import java.nio.file.Paths;
30
31 /** Container associating a name with a stream or reader that can be opened <em>lazily</em>.
32 * <p>
33 * This association and the lazy-opening are useful in different cases:
34 * <ul>
35 * <li>when {@link DirectoryCrawler crawling} a directory tree to select data
36 * to be loaded by a {@link DataLoader}, the files that are not meaningful for
37 * the loader can be ignored and not opened at all</li>
38 * <li>when {@link DataFilter data filtering} is used, the raw stream can
39 * be opened by the filter only if the upper level filtered stream is opened</li>
40 * <li>when opening a stream for loading the data it provides, the opening
41 * and closing actions can be grouped in Orekit internal code using a {@code try
42 * with resources} clause so closing is done properly even in case of exception</li>
43 * <li>if some pre-reading of the first few bytes or characters are needed to decide how to
44 * load data (as in {@link org.orekit.files.ccsds.utils.lexical.LexicalAnalyzerSelector}),
45 * then the stream can be opened, buffered and rewound and a fake open method used
46 * to return the already open stream so a {@code try with resources} clause
47 * elsewhere works properly for closing the stream</li>
48 * </ul>
49 * <p>
50 * Beware that the purpose of this class is only to delay this opening (or not open
51 * the stream or reader at all), it is <em>not</em> intended to open the stream several
52 * times and <em>not</em> intended to open both the binary stream and the characters reader.
53 * Some implementations may fail if the {@link #getOpener() opener}'s
54 * {@link Opener#openStreamOnce() openStreamOnce} or {@link Opener#openReaderOnce() openReaderOnce}
55 * methods are called several times or are both called separately. This is particularly
56 * true for network-based streams.
57 * </p>
58 * @see DataFilter
59 * @author Luc Maisonobe
60 * @since 9.2
61 */
62 public class DataSource {
63
64 /** Name of the data (file name, zip entry name...). */
65 private final String name;
66
67 /** Supplier for data stream. */
68 private final Opener opener;
69
70 /** Complete constructor.
71 * @param name data name
72 * @param streamOpener opener for the data stream
73 */
74 public DataSource(final String name, final StreamOpener streamOpener) {
75 this.name = name;
76 this.opener = new BinaryBasedOpener(streamOpener);
77 }
78
79 /** Complete constructor.
80 * @param name data name
81 * @param readerOpener opener for characters reader
82 */
83 public DataSource(final String name, final ReaderOpener readerOpener) {
84 this.name = name;
85 this.opener = new ReaderBasedOpener(readerOpener);
86 }
87
88 /** Build an instance from file name only.
89 * @param fileName name of the file
90 * @since 11.0
91 */
92 public DataSource(final String fileName) {
93 this(Paths.get(fileName).toFile());
94 }
95
96 /** Build an instance from a file on the local file system.
97 * @param file file
98 * @since 11.0
99 */
100 public DataSource(final File file) {
101 this(file.getName(), () -> Files.newInputStream(file.toPath()));
102 }
103
104 /** Build an instance from URI only.
105 * @param uri URI of the file
106 * @since 11.0
107 */
108 public DataSource(final URI uri) {
109 this(Paths.get(uri).toFile());
110 }
111
112 /** Get the name of the data.
113 * @return name of the data
114 */
115 public String getName() {
116 return name;
117 }
118
119 /** Get the data stream opener.
120 * @return data stream opener
121 */
122 public Opener getOpener() {
123 return opener;
124 }
125
126 /** Interface for lazy-opening a binary stream one time. */
127 public interface StreamOpener {
128 /** Open the stream once.
129 * <p>
130 * Beware that this interface is only intended for <em>lazy</em> opening a
131 * stream, i.e. to delay this opening (or not open the stream at all).
132 * It is <em>not</em> intended to open the stream several times. Some
133 * implementations may fail if an attempt to open a stream several
134 * times is made. This is particularly true for network-based streams.
135 * </p>
136 * @return opened stream
137 * @exception IOException if stream cannot be opened
138 */
139 InputStream openOnce() throws IOException;
140
141 }
142
143 /** Interface for lazy-opening a characters stream one time. */
144 public interface ReaderOpener {
145 /** Open the stream once.
146 * <p>
147 * Beware that this interface is only intended for <em>lazy</em> opening a
148 * stream, i.e. to delay this opening (or not open the stream at all).
149 * It is <em>not</em> intended to open the stream several times. Some
150 * implementations may fail if an attempt to open a stream several
151 * times is made. This is particularly true for network-based streams.
152 * </p>
153 * @return opened stream
154 * @exception IOException if stream cannot be opened
155 */
156 Reader openOnce() throws IOException;
157
158 }
159
160 /** Interface for lazy-opening data streams one time. */
161 public interface Opener {
162
163 /** Check if the raw data is binary.
164 * <p>
165 * The raw data may be either binary or characters. In both cases,
166 * either {@link #openStreamOnce()} or {@link #openReaderOnce()} may
167 * be called, but one will be more efficient than the other as one
168 * will supply data as is and the other one will convert raw data
169 * before providing it. If conversion is needed, it will also be done
170 * using {@link StandardCharsets#UTF_8 UTF8 encoding}, which may not
171 * be suitable. This method helps the data consumer to either choose
172 * the more efficient method or avoid wrong encoding conversion.
173 * </p>
174 * @return true if raw data is binary, false if raw data is characters
175 */
176 boolean rawDataIsBinary();
177
178 /** Open a bytes stream once.
179 * <p>
180 * Beware that this interface is only intended for <em>lazy</em> opening a
181 * stream, i.e. to delay this opening (or not open the stream at all).
182 * It is <em>not</em> intended to open the stream several times and not
183 * intended to open both the {@link #openStreamOnce() binary stream} and
184 * the {@link #openReaderOnce() characters stream} separately (but opening
185 * the reader may be implemented by opening the binary stream or vice-versa).
186 * Implementations may fail if an attempt to open a stream several times is
187 * made. This is particularly true for network-based streams.
188 * </p>
189 * @return opened stream or null if there are no data streams at all
190 * @exception IOException if stream cannot be opened
191 */
192 InputStream openStreamOnce() throws IOException;
193
194 /** Open a characters stream reader once.
195 * <p>
196 * Beware that this interface is only intended for <em>lazy</em> opening a
197 * stream, i.e. to delay this opening (or not open the stream at all).
198 * It is <em>not</em> intended to open the stream several times and not
199 * intended to open both the {@link #openStreamOnce() binary stream} and
200 * the {@link #openReaderOnce() characters stream} separately (but opening
201 * the reader may be implemented by opening the binary stream or vice-versa).
202 * Implementations may fail if an attempt to open a stream several times is
203 * made. This is particularly true for network-based streams.
204 * </p>
205 * @return opened reader or null if there are no data streams at all
206 * @exception IOException if stream cannot be opened
207 */
208 Reader openReaderOnce() throws IOException;
209
210 }
211
212 /** Opener based on a binary stream. */
213 private static class BinaryBasedOpener implements Opener {
214
215 /** Opener for the data stream. */
216 private final StreamOpener streamOpener;
217
218 /** Simple constructor.
219 * @param streamOpener opener for the data stream
220 */
221 BinaryBasedOpener(final StreamOpener streamOpener) {
222 this.streamOpener = streamOpener;
223 }
224
225 /** {@inheritDoc} */
226 @Override
227 public boolean rawDataIsBinary() {
228 return true;
229 }
230
231 /** {@inheritDoc} */
232 @Override
233 public InputStream openStreamOnce() throws IOException {
234 return streamOpener.openOnce();
235 }
236
237 /** {@inheritDoc} */
238 @Override
239 public Reader openReaderOnce() throws IOException {
240 // convert bytes to characters
241 final InputStream is = openStreamOnce();
242 return (is == null) ? null : new InputStreamReader(is, StandardCharsets.UTF_8);
243 }
244
245 }
246
247 /** Opener based on a reader. */
248 private static class ReaderBasedOpener implements Opener {
249
250 /** Size of the characters buffer. */
251 private static final int BUFFER_SIZE = 4096;
252
253 /** Opener for characters reader. */
254 private final ReaderOpener readerOpener;
255
256 /** Simple constructor.
257 * @param readerOpener opener for characters reader
258 */
259 ReaderBasedOpener(final ReaderOpener readerOpener) {
260 this.readerOpener = readerOpener;
261 }
262
263 /** {@inheritDoc} */
264 @Override
265 public boolean rawDataIsBinary() {
266 return false;
267 }
268
269 /** {@inheritDoc} */
270 @Override
271 public InputStream openStreamOnce() throws IOException {
272
273 // open the underlying reader
274 final Reader reader = openReaderOnce();
275 if (reader == null) {
276 return null;
277 }
278
279 // set up a stream that convert characters to bytes
280 return new InputStream() {
281
282 private ByteBuffer buffer = null;
283
284 /** {@inheritDoc} */
285 @Override
286 public int read() throws IOException {
287 if (buffer == null || !buffer.hasRemaining()) {
288 // we need to refill the array
289
290 // get characters from the reader
291 final CharBuffer cb = CharBuffer.allocate(BUFFER_SIZE);
292 final int read = reader.read(cb);
293 if (read < 0) {
294 // end of data
295 return read;
296 }
297
298 // convert the characters read into bytes
299 final int last = cb.position();
300 cb.rewind();
301 buffer = StandardCharsets.UTF_8.encode(cb.subSequence(0, last));
302
303 }
304
305 // return next byte
306 return buffer.get();
307
308 }
309
310 };
311 }
312
313 /** {@inheritDoc} */
314 @Override
315 public Reader openReaderOnce() throws IOException {
316 return readerOpener.openOnce();
317 }
318
319 }
320
321 }
322