1 /* Copyright 2002-2022 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