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}