001/*******************************************************************************
002 * Copyright (c) 2024, 2026, Olivier Ayache.  All rights reserved.
003 *
004 * This file is part of AVPKit.
005 *
006 * AVPKit is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU Lesser General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * AVPKit is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with AVPKit.  If not, see <http://www.gnu.org/licenses/>.
018 *******************************************************************************/
019package com.avpkit.core.io;
020
021import java.io.Closeable;
022import java.io.DataInput;
023import java.io.DataInputStream;
024import java.io.DataOutput;
025import java.io.EOFException;
026import java.io.IOException;
027import java.io.RandomAccessFile;
028
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * Implementation of URLProtocolHandler that can read from
034 * {@link DataInput}
035 * objects or write to {@link DataOutput} objects.
036 * 
037 * <p>
038 * 
039 * The {@link IURLProtocolHandler#URL_RDWR} mode is not supported
040 * unless both input and output objects are the same
041 * {@link RandomAccessFile}
042 * 
043 * </p>
044 * <p>
045 * 
046 * {@link IURLProtocolHandler#isStreamed(String, int)} will return
047 * true, unless this class is reading from or writing to
048 * a {@link RandomAccessFile};
049 * 
050 * </p>
051 * 
052 * @author aclarke
053 * 
054 */
055
056public class DataInputOutputHandler implements IURLProtocolHandler
057{
058  private final Logger log = LoggerFactory.getLogger(this.getClass());
059
060  /**
061   * Whether or not this class will default calling
062   * {@link Closeable#close()} on a stream when
063   * {@link #close()} is called on this method.  Can be
064   * overriden in constructors.
065   */
066  public final static boolean CLOSE_STREAM_ON_CLOSE=true;
067  
068  private final DataInput mDataInput;
069  private final DataOutput mDataOutput;
070  private final boolean mCloseStreamOnClose;
071  private Object mOpenStream = null;
072  
073  /**
074   * Create a handler that can only be opened for reading.
075   * @param in an input object.
076   */
077  public DataInputOutputHandler(DataInput in)
078  {
079    this(in, null, CLOSE_STREAM_ON_CLOSE);
080  }
081  
082  /**
083   * Create a handler that can only be opened for writing.
084   * @param out an output object
085   */
086  public DataInputOutputHandler(DataOutput out)
087  {
088    this(null, out, CLOSE_STREAM_ON_CLOSE);
089  }
090
091  /**
092   * Create a handler that can only be opened for reading,
093   * writing and seeking.
094   * @param file a file to use.
095   */
096  public DataInputOutputHandler(RandomAccessFile file)
097  {
098    this(file, file, CLOSE_STREAM_ON_CLOSE);
099  }
100  
101  /**
102   * Creates a new handler.  If you pass in non null arguments
103   * for both in and out, the handler may be opened in either
104   * {@link IURLProtocolHandler#URL_RDONLY_MODE} or
105   * {@link IURLProtocolHandler#URL_WRONLY_MODE}, but not at
106   * the same time.
107   * <p>
108   * {@link IURLProtocolHandler#URL_RDWR} mode is supported
109   * by this handler if both in and out are instances of
110   * {@link RandomAccessFile}.
111   * </p>
112   * @param in The object to read from if asked to.
113   * @param out The object to write from if asked to.
114   * @param closeStreamOnClose Whether or not to automatically
115   *   call {@link Closeable#close()} on the given channel
116   *   when {@link #close()} is called.
117   */
118  public DataInputOutputHandler(
119      DataInput in, DataOutput out,
120      boolean closeStreamOnClose)
121  {
122    if (in == null && out == null)
123      throw new IllegalArgumentException("must pass one non null stream");
124    mDataInput = in;
125    mDataOutput = out;
126    mCloseStreamOnClose = closeStreamOnClose;
127  }
128
129
130  /**
131   * {@inheritDoc}
132   */
133
134  public int close()
135  {
136    int retval = 0;
137    try
138    {
139      if (mOpenStream != null && mCloseStreamOnClose &&
140          mOpenStream instanceof Closeable)
141      {
142        ((Closeable)mOpenStream).close();
143      }
144    }
145    catch (IOException e)
146    {
147      log.error("could not close stream {}: {}", mOpenStream, e);
148      retval = -1;
149    }
150    mOpenStream = null;
151    return retval;
152  }
153
154  /**
155   * {@inheritDoc}
156   */
157
158  public int open(String url, int flags)
159  {
160    if (mOpenStream != null)
161    {
162      log.debug("attempting to open already open handler: {}",
163          mOpenStream);
164      return -1;
165    }
166    switch (flags)
167    {
168      case URL_RDWR:
169        if (mDataInput != null && mDataOutput != null && 
170            mDataInput == mDataOutput &&
171            mDataInput instanceof RandomAccessFile) {
172          mOpenStream = mDataInput;
173        } else {
174          log.debug("do not support read/write mode for Java IO Handlers");
175          return -1;
176        }
177        break;
178      case URL_WRONLY_MODE:
179        mOpenStream = mDataOutput;
180        if (mOpenStream == null)
181        {
182          log.error("No OutputStream specified for writing: {}",url);
183          return -1;
184        }
185        break;
186      case URL_RDONLY_MODE:
187        mOpenStream = mDataInput;
188        if (mOpenStream == null)
189        {
190          log.error("No InputStream specified for reading: {}", url);
191          return -1;
192        }
193        break;
194      default:
195        log.error("Invalid flag passed to open: {}", url);
196        return -1;
197    }
198
199    return 0;
200  }
201
202  /**
203   * {@inheritDoc}
204   */
205
206  public int read(byte[] buf, int size)
207  {
208    int ret = -1;
209    if (mOpenStream == null || !(mOpenStream instanceof DataInput))
210      return -1;
211
212    try
213    {
214      if (mOpenStream instanceof RandomAccessFile) {
215        RandomAccessFile file = (RandomAccessFile) mOpenStream;
216        return file.read(buf, 0, size);
217      } else if (mOpenStream instanceof DataInputStream) {
218        DataInputStream stream = (DataInputStream) mOpenStream;
219        return stream.read(buf, 0, size);
220      } else {
221        DataInput input = (DataInput) mOpenStream;
222        try {
223          input.readFully(buf, 0, size);
224          ret = size;
225        } catch (EOFException e) {
226          // man; we have no idea how many bytes were actually
227          // read now... so we truncate the data
228          // what a sucky interface!
229          ret = -1;
230        }
231        return ret;
232      }
233    }
234    catch (IOException e)
235    {
236      log.error("Got IO exception reading from channel: {}; {}",
237          mOpenStream, e);
238      return -1;
239    }
240  }
241
242  /**
243   * {@inheritDoc}
244   * 
245   * This method is not supported on this class and always return -1;
246   */
247
248  public long seek(long offset, int whence)
249  {
250    if (mOpenStream == null)
251      return -1;
252    if (!(mOpenStream instanceof RandomAccessFile))
253      return -1;
254    final RandomAccessFile file = (RandomAccessFile) mOpenStream;
255    try
256    {
257      final long seek;
258      if (whence == SEEK_SET)
259        seek = offset;
260      else if (whence == SEEK_CUR)
261        seek = file.getFilePointer() + offset;
262      else if (whence == SEEK_END)
263        seek = file.length() + offset;
264      else if (whence == SEEK_SIZE)
265        // odd feature of the protocol handler; this request
266        // just returns the file size without actually seeking
267        return (int) file.length();
268      else
269      {
270        log.error("invalid seek value \"{}\" for file: {}", whence, file);
271        return -1;
272      }
273
274      file.seek(seek);
275      return seek;
276    }
277    catch (IOException e)
278    {
279      log.debug("got io exception \"{}\" while seeking in: {}", e
280          .getMessage(), file);
281      return -1;
282    }
283  }
284
285  /**
286   * {@inheritDoc}
287   */
288
289  public int write(byte[] buf, int size)
290  {
291    if (mOpenStream == null ||
292        !(mOpenStream instanceof DataOutput))
293      return -1;
294
295    try
296    {
297      DataOutput output = (DataOutput) mOpenStream;
298      output.write(buf, 0, size);
299      return size;
300    }
301    catch (IOException e)
302    {
303      log.error("Got error writing to file: {}; {}", mOpenStream, e);
304      return -1;
305    }
306  }
307
308  /**
309   * {@inheritDoc}
310   * Always true, unless we're inputting or outputting to a
311   * {@link RandomAccessFile}. 
312   */
313
314  public boolean isStreamed(String url, int flags)
315  {
316    if (mDataInput != null && mDataInput instanceof RandomAccessFile)
317      return false;
318    if (mDataOutput != null && mDataOutput instanceof RandomAccessFile)
319      return false;
320    return true;
321  }
322  /**
323   * Returns the {@link DataInput} we'd input from if asked.
324   * @return the {@link DataInput}
325   */
326  public DataInput getDataInput()
327  {
328    return mDataInput;
329  }
330  /**
331   * Returns the {@link DataOutput} we'd output to if asked.
332   * @return the {@link DataOutput}
333   */
334  public DataOutput getDataOutput()
335  {
336    return mDataOutput;
337  }
338  /**
339   * Returns the object currently {@link #open(String, int)}.
340   * @return the open object
341   */
342  public Object getOpen()
343  {
344    return mOpenStream;
345  }
346
347  /**
348   * Will this handler call {@link Closeable#close()} automatically
349   * when its {@link #close()} method is called by AVPKit?
350   * 
351   * We will check if the object we're using supports {@link Closeable}.
352   * 
353   * @return the closeStreamOnClose setting
354   */
355  public boolean isCloseStreamOnClose()
356  {
357    return mCloseStreamOnClose;
358  }
359
360}