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