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.IOException;
023import java.nio.ByteBuffer;
024import java.nio.channels.Channel;
025import java.nio.channels.ReadableByteChannel;
026import java.nio.channels.SeekableByteChannel;
027import java.nio.channels.WritableByteChannel;
028
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * Implementation of URLProtocolHandler that can read from
034 * {@link ReadableByteChannel}
035 * objects or write to {@link WritableByteChannel} object.
036 * 
037 * <p>
038 * 
039 * The {@link IURLProtocolHandler#URL_RDWR} mode is not supported, and
040 * {@link #isStreamed(String, int)} will always return true.
041 * 
042 * </p>
043 * 
044 * @author aclarke
045 * 
046 */
047
048public class ReadableWritableChannelHandler implements IURLProtocolHandler
049{
050  private final Logger log = LoggerFactory.getLogger(this.getClass());
051
052  /**
053   * Whether or not this class will default calling
054   * {@link Closeable#close()} on a stream when
055   * {@link #close()} is called on this method.  Can be
056   * overriden in constructors.
057   */
058  public final static boolean CLOSE_STREAM_ON_CLOSE=true;
059  
060  private final ReadableByteChannel mReadChannel;
061  private final WritableByteChannel mWriteChannel;
062  private final boolean mCloseStreamOnClose;
063  private Channel mOpenStream = null;
064  
065  /**
066   * Create a handler that can only be opened for reading.
067   * @param channel an input channel.
068   */
069  public ReadableWritableChannelHandler(ReadableByteChannel channel)
070  {
071    this(channel, null, CLOSE_STREAM_ON_CLOSE);
072  }
073  
074  /**
075   * Create a handler that can only be opened for writing.
076   * @param channel an output channel
077   */
078  public ReadableWritableChannelHandler(WritableByteChannel channel)
079  {
080    this(null, channel, CLOSE_STREAM_ON_CLOSE);
081  }
082  
083  /**
084   * Creates a new handler.  If you pass in non null arguments
085   * for both in and out, the handler may be opened in either
086   * {@link IURLProtocolHandler#URL_RDONLY_MODE} or
087   * {@link IURLProtocolHandler#URL_WRONLY_MODE}, but not at
088   * the same time.
089   * <p>
090   * {@link IURLProtocolHandler#URL_RDWR} mode is not supported
091   * by this handler.
092   * </p>
093   * @param in The channel to read from if asked to.
094   * @param out The channel to write from if asked to.
095   * @param closeStreamOnClose Whether or not to automatically
096   *   call {@link Closeable#close()} on the given channel
097   *   when {@link #close()} is called.
098   */
099  public ReadableWritableChannelHandler(
100      ReadableByteChannel in, WritableByteChannel out,
101      boolean closeStreamOnClose)
102  {
103    if (in == null && out == null)
104      throw new IllegalArgumentException("must pass one non null stream");
105    mReadChannel = in;
106    mWriteChannel = out;
107    mCloseStreamOnClose = closeStreamOnClose;
108  }
109
110
111  /**
112   * {@inheritDoc}
113   */
114
115  public int close()
116  {
117    int retval = 0;
118    try
119    {
120      if (mOpenStream != null && mCloseStreamOnClose)
121      {
122        mOpenStream.close();
123      }
124    }
125    catch (IOException e)
126    {
127      log.error("could not close stream {}: {}", mOpenStream, e);
128      retval = -1;
129    }
130    mOpenStream = null;
131    return retval;
132  }
133
134  /**
135   * {@inheritDoc}
136   */
137
138  public int open(String url, int flags)
139  {
140    if (mOpenStream != null)
141    {
142      log.debug("attempting to open already open handler: {}",
143          mOpenStream);
144      return -1;
145    }
146    switch (flags)
147    {
148      case URL_RDWR:
149        log.debug("do not support read/write mode for Java IO Handlers");
150        return -1;
151      case URL_WRONLY_MODE:
152        mOpenStream = mWriteChannel;
153        if (mOpenStream == null)
154        {
155          log.error("No OutputStream specified for writing: {}",url);
156          return -1;
157        }
158        break;
159      case URL_RDONLY_MODE:
160        mOpenStream = mReadChannel;
161        if (mOpenStream == null)
162        {
163          log.error("No InputStream specified for reading: {}", url);
164          return -1;
165        }
166        break;
167      default:
168        log.error("Invalid flag passed to open: {}", url);
169        return -1;
170    }
171
172    return 0;
173  }
174
175  /**
176   * {@inheritDoc}
177   */
178
179  public int read(byte[] buf, int size)
180  {
181    int ret = -1;
182    if (mOpenStream == null || !(mOpenStream instanceof ReadableByteChannel))
183      return -1;
184
185    try
186    {
187      ReadableByteChannel channel = (ReadableByteChannel) mOpenStream;
188      ByteBuffer buffer = ByteBuffer.allocate(size);
189      ret = channel.read(buffer);
190      if (ret > 0) {
191        buffer.flip();
192        buffer.get(buf, 0, ret);
193      }
194      return ret;
195    }
196    catch (IOException e)
197    {
198      log.error("Got IO exception reading from channel: {}; {}",
199          mOpenStream, e);
200      return -1;
201    }
202  }
203
204  /**
205   * {@inheritDoc}
206   * 
207   * This method is not supported on this class and always return -1;
208   */
209
210  public long seek(long offset, int whence)
211  {
212    if (mOpenStream == null || !(mOpenStream instanceof SeekableByteChannel))
213      return -1;
214    try
215    {
216      SeekableByteChannel stream = (SeekableByteChannel) mOpenStream;
217          switch (whence){
218              case SEEK_CUR:
219                  stream.position(stream.position() + offset);
220                  return offset;
221              case SEEK_SET:
222                  stream.position(offset);
223                  return offset;
224              case SEEK_SIZE:
225                  return stream.size();
226              case SEEK_END:
227                  stream.position(stream.size());
228                  return offset;
229              default:
230                  return -1;
231          }
232    }
233    catch (IOException e)
234    {
235      log.error("Got IO exception reading from stream: {}; {}",
236          mOpenStream, e);
237      return -1;
238    }
239  }
240
241  /**
242   * {@inheritDoc}
243   */
244
245  public int write(byte[] buf, int size)
246  {
247    if (mOpenStream == null ||
248        !(mOpenStream instanceof WritableByteChannel))
249      return -1;
250
251    try
252    {
253      WritableByteChannel channel = (WritableByteChannel) mOpenStream;
254      ByteBuffer buffer = ByteBuffer.allocate(size);
255      buffer.put(buf, 0, size);
256      buffer.flip();
257      return channel.write(buffer);
258    }
259    catch (IOException e)
260    {
261      log.error("Got error writing to file: {}; {}", mOpenStream, e);
262      return -1;
263    }
264  }
265
266  /**
267   * {@inheritDoc}
268   * Always true for this class. 
269   */
270
271  public boolean isStreamed(String url, int flags)
272  {
273    return !(mReadChannel instanceof SeekableByteChannel) && !(mWriteChannel instanceof SeekableByteChannel);
274  }
275  /**
276   * Returns the channel we'd input from if asked.
277   * @return the channel
278   */
279  public ReadableByteChannel getReadChannel()
280  {
281    return mReadChannel;
282  }
283  /**
284   * Returns the channel we'd output to if asked.
285   * @return the channel
286   */
287  public WritableByteChannel getWriteChannel()
288  {
289    return mWriteChannel;
290  }
291  /**
292   * Returns the channel currently {@link #open(String, int)}.
293   * @return the openStream
294   */
295  public Channel getOpenChannel()
296  {
297    return mOpenStream;
298  }
299
300  /**
301   * Will this handler call {@link Closeable#close()} automatically
302   * when its {@link #close()} method is called by AVPKit?
303   * 
304   * @return the closeStreamOnClose setting
305   */
306  public boolean isCloseStreamOnClose()
307  {
308    return mCloseStreamOnClose;
309  }
310
311}