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