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.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.io.Reader;
  23. import java.net.URI;
  24. import java.nio.ByteBuffer;
  25. import java.nio.CharBuffer;
  26. import java.nio.charset.StandardCharsets;
  27. import java.nio.file.Files;
  28. import java.nio.file.Paths;

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

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

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

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

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

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

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

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

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

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

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

  128.     }

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

  143.     }

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

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

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

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

  190.     }

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

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

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

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

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

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

  218.     }

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

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

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

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

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

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

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

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

  246.                 private ByteBuffer buffer = null;

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

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

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

  263.                     }

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

  266.                 }

  267.             };
  268.         }

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

  274.     }

  275. }