DataSource.java

  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. import java.io.File;
  19. import java.io.FileInputStream;
  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. /** Container associating a name with a stream or reader that can be opened <em>lazily</em>.
  31.  * <p>
  32.  * This association and the lazy-opening are useful in different cases:
  33.  * <ul>
  34.  *   <li>when {@link DirectoryCrawler crawling} a directory tree to select data
  35.  *   to be loaded by a {@link DataLoader}, the files that are not meaningful for
  36.  *   the loader can be ignored and not opened at all</li>
  37.  *   <li>when {@link DataFilter data filtering} is used, the raw stream can
  38.  *   be opened by the filter only if the upper level filtered stream is opened</li>
  39.  *   <li>when opening a stream for loading the data it provides, the opening
  40.  *   and closing actions can be grouped in Orekit internal code using a {@code try
  41.  *   with resources} clause so closing is done properly even in case of exception</li>
  42.  *   <li>if some pre-reading of the first few bytes or characters are needed to decide how to
  43.  *   load data (as in {@link org.orekit.files.ccsds.utils.lexical.LexicalAnalyzerSelector}),
  44.  *   then the stream can be opened, buffered and rewound and a fake open method used
  45.  *   to return the already open stream so a {@code try with resources} clause
  46.  *   elsewhere works properly for closing the stream</li>
  47.  * </ul>
  48.  * <p>
  49.  * Beware that the purpose of this class is only to delay this opening (or not open
  50.  * the stream or reader at all), it is <em>not</em> intended to open the stream several
  51.  * times and <em>not</em> intended to open both the binary stream and the characters reader.
  52.  * Some implementations may fail if the {@link #getOpener() opener}'s
  53.  * {@link Opener#openStreamOnce() openStreamOnce} or {@link Opener#openReaderOnce() openReaderOnce}
  54.  * methods are called several times or are both called separately. This is particularly
  55.  * true for network-based streams.
  56.  * </p>
  57.  * @see DataFilter
  58.  * @author Luc Maisonobe
  59.  * @since 9.2
  60.  */
  61. public class DataSource {

  62.     /** Name of the data (file name, zip entry name...). */
  63.     private final String name;

  64.     /** Supplier for data stream. */
  65.     private final Opener opener;

  66.     /** Complete constructor.
  67.      * @param name data name
  68.      * @param streamOpener opener for the data stream
  69.      */
  70.     public DataSource(final String name, final StreamOpener streamOpener) {
  71.         this.name   = name;
  72.         this.opener = new BinaryBasedOpener(streamOpener);
  73.     }

  74.     /** Complete constructor.
  75.      * @param name data name
  76.      * @param readerOpener opener for characters reader
  77.      */
  78.     public DataSource(final String name, final ReaderOpener readerOpener) {
  79.         this.name   = name;
  80.         this.opener = new ReaderBasedOpener(readerOpener);
  81.     }

  82.     /** Build an instance from file name only.
  83.      * @param fileName name of the file
  84.      * @since 11.0
  85.      */
  86.     public DataSource(final String fileName) {
  87.         this(fileName, () -> Files.newInputStream(Paths.get(fileName)));
  88.     }

  89.     /** Build an instance from a file on the local file system.
  90.      * @param file file
  91.      * @since 11.0
  92.      */
  93.     public DataSource(final File file) {
  94.         this(file.getName(), () -> new FileInputStream(file));
  95.     }

  96.     /** Build an instance from URI only.
  97.      * @param uri URI of the file
  98.      * @since 11.0
  99.      */
  100.     public DataSource(final URI uri) {
  101.         this(Paths.get(uri).toFile());
  102.     }

  103.     /** Get the name of the data.
  104.      * @return name of the data
  105.      */
  106.     public String getName() {
  107.         return name;
  108.     }

  109.     /** Get the data stream opener.
  110.      * @return data stream opener
  111.      */
  112.     public Opener getOpener() {
  113.         return opener;
  114.     }

  115.     /** Interface for lazy-opening a binary stream one time. */
  116.     public interface StreamOpener {
  117.         /** Open the stream once.
  118.          * <p>
  119.          * Beware that this interface is only intended for <em>lazy</em> opening a
  120.          * stream, i.e. to delay this opening (or not open the stream at all).
  121.          * It is <em>not</em> intended to open the stream several times. Some
  122.          * implementations may fail if an attempt to open a stream several
  123.          * times is made. This is particularly true for network-based streams.
  124.          * </p>
  125.          * @return opened stream
  126.          * @exception IOException if stream cannot be opened
  127.          */
  128.         InputStream openOnce() throws IOException;

  129.     }

  130.     /** Interface for lazy-opening a characters stream one time. */
  131.     public interface ReaderOpener {
  132.         /** Open the stream once.
  133.          * <p>
  134.          * Beware that this interface is only intended for <em>lazy</em> opening a
  135.          * stream, i.e. to delay this opening (or not open the stream at all).
  136.          * It is <em>not</em> intended to open the stream several times. Some
  137.          * implementations may fail if an attempt to open a stream several
  138.          * times is made. This is particularly true for network-based streams.
  139.          * </p>
  140.          * @return opened stream
  141.          * @exception IOException if stream cannot be opened
  142.          */
  143.         Reader openOnce() throws IOException;

  144.     }

  145.     /** Interface for lazy-opening data streams one time. */
  146.     public interface Opener {

  147.         /** Check if the raw data is binary.
  148.          * <p>
  149.          * The raw data may be either binary or characters. In both cases,
  150.          * either {@link #openStreamOnce()} or {@link #openReaderOnce()} may
  151.          * be called, but one will be more efficient than the other as one
  152.          * will supply data as is and the other one will convert raw data
  153.          * before providing it. If conversion is needed, it will also be done
  154.          * using {@link StandardCharsets#UTF_8 UTF8 encoding}, which may not
  155.          * be suitable. This method helps the data consumer to either choose
  156.          * the more efficient method or avoid wrong encoding conversion.
  157.          * </p>
  158.          * @return true if raw data is binary, false if raw data is characters
  159.          */
  160.         boolean rawDataIsBinary();

  161.         /** Open a bytes stream once.
  162.          * <p>
  163.          * Beware that this interface is only intended for <em>lazy</em> opening a
  164.          * stream, i.e. to delay this opening (or not open the stream at all).
  165.          * It is <em>not</em> intended to open the stream several times and not
  166.          * intended to open both the {@link #openStreamOnce() binary stream} and
  167.          * the {@link #openReaderOnce() characters stream} separately (but opening
  168.          * the reader may be implemented by opening the binary stream or vice-versa).
  169.          * Implementations may fail if an attempt to open a stream several times is
  170.          * made. This is particularly true for network-based streams.
  171.          * </p>
  172.          * @return opened stream or null if there are no data streams at all
  173.          * @exception IOException if stream cannot be opened
  174.          */
  175.         InputStream openStreamOnce() throws IOException;

  176.         /** Open a characters stream reader once.
  177.          * <p>
  178.          * Beware that this interface is only intended for <em>lazy</em> opening a
  179.          * stream, i.e. to delay this opening (or not open the stream at all).
  180.          * It is <em>not</em> intended to open the stream several times and not
  181.          * intended to open both the {@link #openStreamOnce() binary stream} and
  182.          * the {@link #openReaderOnce() characters stream} separately (but opening
  183.          * the reader may be implemented by opening the binary stream or vice-versa).
  184.          * Implementations may fail if an attempt to open a stream several times is
  185.          * made. This is particularly true for network-based streams.
  186.          * </p>
  187.          * @return opened reader or null if there are no data streams at all
  188.          * @exception IOException if stream cannot be opened
  189.          */
  190.         Reader openReaderOnce() throws IOException;

  191.     }

  192.     /** Opener based on a binary stream. */
  193.     private static class BinaryBasedOpener implements Opener {

  194.         /** Opener for the data stream. */
  195.         private final StreamOpener streamOpener;

  196.         /** Simple constructor.
  197.          * @param streamOpener opener for the data stream
  198.          */
  199.         BinaryBasedOpener(final StreamOpener streamOpener) {
  200.             this.streamOpener = streamOpener;
  201.         }

  202.         /** {@inheritDoc} */
  203.         @Override
  204.         public boolean rawDataIsBinary() {
  205.             return true;
  206.         }

  207.         /** {@inheritDoc} */
  208.         @Override
  209.         public InputStream openStreamOnce() throws IOException {
  210.             return streamOpener.openOnce();
  211.         }

  212.         /** {@inheritDoc} */
  213.         @Override
  214.         public Reader openReaderOnce() throws IOException {
  215.             // convert bytes to characters
  216.             final InputStream is = openStreamOnce();
  217.             return (is == null) ? null : new InputStreamReader(is, StandardCharsets.UTF_8);
  218.         }

  219.     }

  220.     /** Opener based on a reader. */
  221.     private static class ReaderBasedOpener implements Opener {

  222.         /** Size of the characters buffer. */
  223.         private static final int BUFFER_SIZE = 4096;

  224.         /** Opener for characters reader. */
  225.         private final ReaderOpener readerOpener;

  226.         /** Simple constructor.
  227.          * @param readerOpener opener for characters reader
  228.          */
  229.         ReaderBasedOpener(final ReaderOpener readerOpener) {
  230.             this.readerOpener = readerOpener;
  231.         }

  232.         /** {@inheritDoc} */
  233.         @Override
  234.         public boolean rawDataIsBinary() {
  235.             return false;
  236.         }

  237.         /** {@inheritDoc} */
  238.         @Override
  239.         public InputStream openStreamOnce() throws IOException {

  240.             // open the underlying reader
  241.             final Reader reader = openReaderOnce();
  242.             if (reader == null) {
  243.                 return null;
  244.             }

  245.             // set up a stream that convert characters to bytes
  246.             return new InputStream() {

  247.                 private ByteBuffer buffer = null;

  248.                 /** {@inheritDoc} */
  249.                 @Override
  250.                 public int read() throws IOException {
  251.                     if (buffer == null || !buffer.hasRemaining()) {
  252.                         // we need to refill the array

  253.                         // get characters from the reader
  254.                         final CharBuffer cb = CharBuffer.allocate(BUFFER_SIZE);
  255.                         final int read = reader.read(cb);
  256.                         if (read < 0) {
  257.                             // end of data
  258.                             return read;
  259.                         }

  260.                         // convert the characters read into bytes
  261.                         final int last = cb.position();
  262.                         cb.rewind();
  263.                         buffer = StandardCharsets.UTF_8.encode(cb.subSequence(0, last));

  264.                     }

  265.                     // return next byte
  266.                     return buffer.get();

  267.                 }

  268.             };
  269.         }

  270.         /** {@inheritDoc} */
  271.         @Override
  272.         public Reader openReaderOnce() throws IOException {
  273.             return readerOpener.openOnce();
  274.         }

  275.     }

  276. }