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}