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 *******************************************************************************/ 019 020package com.avpkit.core.io; 021 022import java.io.Closeable; 023import java.io.DataInput; 024import java.io.DataOutput; 025import java.io.InputStream; 026import java.io.OutputStream; 027import java.io.RandomAccessFile; 028import java.nio.channels.ByteChannel; 029import java.nio.channels.ReadableByteChannel; 030import java.nio.channels.WritableByteChannel; 031import java.util.UUID; 032import java.util.concurrent.ConcurrentHashMap; 033import java.util.concurrent.ConcurrentMap; 034 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import com.avpkit.core.IContainer; 039 040/** 041 * Allows AVPKit to read from and write to many different types of Java I/O 042 * objects, plus custom {@link IURLProtocolHandler} objects. 043 * 044 * <p> 045 * Most of the time, IContainer hides this away from you, but in case you're 046 * interested, here's the underlying class doing the IO magic. 047 * </p> 048 * 049 * To use for reading (assuming an InputStream object called inputStream): </p> 050 * 051 * <pre> 052 * IContainer container = IContainer.make(); 053 * container.open(inputStream, null); 054 * </pre> 055 * <p> 056 * or for writing: 057 * </p> 058 * 059 * <pre> 060 * IContainer container = IContainer.make(); 061 * container.open(outputStream, null); 062 * </pre> 063 * <p> 064 * Really. That's it. 065 * </p> 066 * <p> 067 * 068 * All streams that are mapped in this factory share the same name space, even 069 * if registered under different protocols. So, if "exampleone" and "exampletwo" 070 * were both registered as protocols for this factory, then 071 * "exampleone:filename" is the same as "exampletwo:filename" and will map to 072 * the same input and output streams. In reality, they are all mapped to the 073 * {@link #DEFAULT_PROTOCOL} protocol. 074 * 075 * </p> 076 */ 077 078public class AVPKitIO implements IURLProtocolHandlerFactory 079{ 080 private final Logger log = LoggerFactory.getLogger(this.getClass()); 081 /** 082 * The default protocol string that this factory uses ( 083 * {@value #DEFAULT_PROTOCOL}). 084 */ 085 086 public final static String DEFAULT_PROTOCOL = "avpkit"; 087 088 /** 089 * Do we undo mappings on open by default? 090 */ 091 private final static boolean DEFAULT_UNMAP_URL_ON_OPEN = true; 092 093 /** 094 * Do we call {@link Closeable#close()} by default when closing? 095 */ 096 private final static boolean DEFAULT_CLOSE_STREAM_ON_CLOSE = true; 097 098 /** 099 * A thread-safe mapping between URLs and registration information 100 */ 101 private final ConcurrentMap<String, RegistrationInformation> mURLs = new ConcurrentHashMap<String, RegistrationInformation>(); 102 103 /** 104 * The singleton Factory object for this class loader. 105 */ 106 private final static AVPKitIO mFactory = new AVPKitIO(); 107 108 /** 109 * The static constructor just registered the singleton factory under the 110 * DEFAULT_PROTOCOL protocol. 111 */ 112 static 113 { 114 registerFactory(DEFAULT_PROTOCOL); 115 } 116 117 /** 118 * Package level constructor. We don't allow people to create their own 119 * version of this factory 120 */ 121 AVPKitIO() 122 { 123 124 } 125 126 /** 127 * Register a new protocol name for this factory that AVPKit.IO will use for 128 * the given protocol. 129 * 130 * <p> 131 * 132 * A default factory for the protocol {@value #DEFAULT_PROTOCOL} will already 133 * be registered. This just allows you to register the same factory under 134 * different strings if you want. 135 * 136 * </p> 137 * <p> 138 * 139 * <strong>NOTE: Protocol can only contain alpha characters.</strong> 140 * 141 * </p> 142 * 143 * @param protocolPrefix The protocol (e.g. "yourapphandler"). 144 * @return The factory registered 145 */ 146 147 static AVPKitIO registerFactory(String protocolPrefix) 148 { 149 URLProtocolManager manager = URLProtocolManager.getManager(); 150 manager.registerFactory(protocolPrefix, mFactory); 151 return mFactory; 152 } 153 154 /** 155 * Get the singleton factory object for this class. 156 * 157 * @return the factory 158 */ 159 160 static public AVPKitIO getFactory() 161 { 162 return mFactory; 163 } 164 165 /** 166 * Generates a unique name suitable for using in the map methods for the URL 167 * parameter. 168 * 169 * @param src The object you want to generate a unique name for, or null if 170 * you don't have one. 171 * @return A unique name (will be unique across time and space). 172 */ 173 static public String generateUniqueName(Object src) 174 { 175 return generateUniqueName(src, null); 176 } 177 178 /** 179 * Generates a unique name suitable for using in the map methods for the URL 180 * parameter. 181 * 182 * @param src The object you want to generate a unique name for, or null if 183 * you don't have one. 184 * @param extension an option extension to append to the generated URL. 185 * @return A unique name (will be unique across time and space). 186 */ 187 static public String generateUniqueName(Object src, String extension) 188 { 189 StringBuilder builder = new StringBuilder(); 190 builder.append(UUID.randomUUID().toString()); 191 if (src != null) 192 { 193 builder.append("-"); 194 builder.append(src.getClass().getSimpleName()); 195 builder.append("-"); 196 builder.append(Integer.toHexString(src.hashCode())); 197 } 198 if (extension != null) 199 { 200 builder.append(extension); 201 } 202 return builder.toString(); 203 } 204 205 206 /** 207 * Maps a {@link IURLProtocolHandler} to a url that AVPKit can open. 208 * 209 * {@link #unmap(String)} will be called automatically after this URL is 210 * opened. 211 * 212 * @param handler the handler 213 * @return a string that is suitable for passing to {@link IContainer}'s open 214 * methods. 215 */ 216 public static String map(IURLProtocolHandler handler) 217 { 218 return map(generateUniqueName(handler), handler, DEFAULT_UNMAP_URL_ON_OPEN); 219 } 220 221 /** 222 * Maps a {@link IURLProtocolHandler} to a url that AVPKit can open. 223 * 224 * {@link #unmap(String)} will be called automatically after this URL is 225 * opened. 226 * 227 * @param url the unique string to use for the mapping. 228 * @param handler the handler 229 * @return a string that is suitable for passing to {@link IContainer}'s open 230 * methods. 231 */ 232 public static String map(String url, IURLProtocolHandler handler) 233 { 234 return map(url, handler, DEFAULT_UNMAP_URL_ON_OPEN); 235 } 236 237 /** 238 * Maps a {@link DataInput} object to a URL for use by AVPKit. 239 * 240 * @param input the {@link DataInput} 241 * @return a string that can be passed to {@link IContainer}'s open methods. 242 */ 243 public static String map(DataInput input) 244 { 245 return map(generateUniqueName(input), input, null, 246 DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE); 247 } 248 249 /** 250 * Maps a {@link DataInput} object to a URL for use by AVPKit. 251 * 252 * @param url the URL to use. 253 * @param input the {@link DataInput} 254 * @return a string that can be passed to {@link IContainer}'s open methods. 255 */ 256 public static String map(String url, DataInput input) 257 { 258 return map(url, input, null, DEFAULT_UNMAP_URL_ON_OPEN, 259 DEFAULT_CLOSE_STREAM_ON_CLOSE); 260 } 261 262 /** 263 * Maps a {@link DataOutput} object to a URL for use by AVPKit. 264 * 265 * @param output the {@link DataOutput} 266 * @return a string that can be passed to {@link IContainer}'s open methods. 267 */ 268 public static String map(DataOutput output) 269 { 270 return map(generateUniqueName(output), null, output, 271 DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE); 272 } 273 274 /** 275 * Maps a {@link DataOutput} object to a URL for use by AVPKit. 276 * 277 * @param url the URL to use. 278 * @param output the {@link DataOutput} 279 * @return a string that can be passed to {@link IContainer}'s open methods. 280 */ 281 public static String map(String url, DataOutput output) 282 { 283 return map(url, null, output, DEFAULT_UNMAP_URL_ON_OPEN, 284 DEFAULT_CLOSE_STREAM_ON_CLOSE); 285 } 286 287 /** 288 * Maps a {@link RandomAccessFile} object to a URL for use by AVPKit. 289 * 290 * @param file the {@link RandomAccessFile} 291 * @return a string that can be passed to {@link IContainer}'s open methods. 292 */ 293 public static String map(RandomAccessFile file) 294 { 295 return map(generateUniqueName(file), file, file, DEFAULT_UNMAP_URL_ON_OPEN, 296 DEFAULT_CLOSE_STREAM_ON_CLOSE); 297 } 298 299 /** 300 * Maps a {@link RandomAccessFile} object to a URL for use by AVPKit. 301 * 302 * @param url the URL to use. 303 * @param file the {@link RandomAccessFile} 304 * @return a string that can be passed to {@link IContainer}'s open methods. 305 */ 306 public static String map(String url, RandomAccessFile file) 307 { 308 return map(url, file, file, DEFAULT_UNMAP_URL_ON_OPEN, 309 DEFAULT_CLOSE_STREAM_ON_CLOSE); 310 } 311 312 /** 313 * Maps a {@link ReadableByteChannel} to a URL for use by AVPKit. 314 * 315 * @param channel the {@link ReadableByteChannel} 316 * @return a string that can be passed to {@link IContainer}'s open methods. 317 */ 318 public static String map(ReadableByteChannel channel) 319 { 320 return map(generateUniqueName(channel), channel, null, 321 DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE); 322 } 323 324 /** 325 * Maps a {@link ReadableByteChannel} to a URL for use by AVPKit. 326 * 327 * @param url the URL to use. 328 * @param channel the {@link ReadableByteChannel} 329 * @return a string that can be passed to {@link IContainer}'s open methods. 330 */ 331 public static String map(String url, ReadableByteChannel channel) 332 { 333 return map(url, channel, null, DEFAULT_UNMAP_URL_ON_OPEN, 334 DEFAULT_CLOSE_STREAM_ON_CLOSE); 335 } 336 337 /** 338 * Maps a {@link WritableByteChannel} to a URL for use by AVPKit. 339 * 340 * @param channel the {@link WritableByteChannel} 341 * @return a string that can be passed to {@link IContainer}'s open methods. 342 */ 343 public static String map(WritableByteChannel channel) 344 { 345 return map(generateUniqueName(channel), null, channel, 346 DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE); 347 } 348 349 /** 350 * Maps a {@link WritableByteChannel} to a URL for use by AVPKit. 351 * 352 * @param url the URL to use. 353 * @param channel the {@link WritableByteChannel} 354 * @return a string that can be passed to {@link IContainer}'s open methods. 355 */ 356 public static String map(String url, WritableByteChannel channel) 357 { 358 return map(url, null, channel, DEFAULT_UNMAP_URL_ON_OPEN, 359 DEFAULT_CLOSE_STREAM_ON_CLOSE); 360 } 361 362 /** 363 * Maps a {@link ByteChannel} to a URL for use by AVPKit. 364 * 365 * @param channel the {@link ByteChannel} 366 * @return a string that can be passed to {@link IContainer}'s open methods. 367 */ 368 public static String map(ByteChannel channel) 369 { 370 return map(generateUniqueName(channel), channel, channel, 371 DEFAULT_UNMAP_URL_ON_OPEN, DEFAULT_CLOSE_STREAM_ON_CLOSE); 372 } 373 374 /** 375 * Maps a {@link ByteChannel} to a URL for use by AVPKit. 376 * 377 * @param url the URL to use. 378 * @param channel the {@link ByteChannel} 379 * @return a string that can be passed to {@link IContainer}'s open methods. 380 */ 381 public static String map(String url, ByteChannel channel) 382 { 383 return map(url, channel, channel, DEFAULT_UNMAP_URL_ON_OPEN, 384 DEFAULT_CLOSE_STREAM_ON_CLOSE); 385 } 386 387 /** 388 * Maps an {@link InputStream} to a URL for use by AVPKit. 389 * 390 * @param in the stream 391 * 392 * @return Returns a URL that can be passed to AVPKit's {@link IContainer}'s 393 * open method, and will result in IO being performed on the passed in 394 * streams. 395 */ 396 397 public static String map(InputStream in) 398 { 399 return map(generateUniqueName(in), in, null, DEFAULT_UNMAP_URL_ON_OPEN, 400 DEFAULT_CLOSE_STREAM_ON_CLOSE); 401 } 402 403 /** 404 * Maps an {@link InputStream} to a URL for use by AVPKit. 405 * 406 * @param url the URL to use. 407 * @param in the stream 408 * 409 * @return Returns a URL that can be passed to AVPKit's {@link IContainer}'s 410 * open method, and will result in IO being performed on the passed in 411 * streams. 412 */ 413 414 public static String map(String url, InputStream in) 415 { 416 return map(url, in, null, DEFAULT_UNMAP_URL_ON_OPEN, 417 DEFAULT_CLOSE_STREAM_ON_CLOSE); 418 } 419 420 /** 421 * Maps an {@link OutputStream} to a URL for use by AVPKit. 422 * 423 * @param out the stream 424 * 425 * @return Returns a URL that can be passed to AVPKit's {@link IContainer}'s 426 * open method, and will result in IO being performed on the passed in 427 * streams. 428 */ 429 430 public static String map(OutputStream out) 431 { 432 return map(generateUniqueName(out), null, out, DEFAULT_UNMAP_URL_ON_OPEN, 433 DEFAULT_CLOSE_STREAM_ON_CLOSE); 434 } 435 436 /** 437 * Maps an {@link OutputStream} to a URL for use by AVPKit. 438 * 439 * @param url the URL to use. 440 * @param out the stream 441 * 442 * @return Returns a URL that can be passed to AVPKit's {@link IContainer}'s 443 * open method, and will result in IO being performed on the passed in 444 * streams. 445 */ 446 447 public static String map(String url, OutputStream out) 448 { 449 return map(url, null, out, DEFAULT_UNMAP_URL_ON_OPEN, 450 DEFAULT_CLOSE_STREAM_ON_CLOSE); 451 } 452 453 /** 454 * Maps a {@link DataInput} or {@link DataOutput} object to a URL for use by 455 * AVPKit. 456 * 457 * @param url the url 458 * @param in the input to use 459 * @param out the output to use 460 * @param unmapOnOpen should we remove the mapping as soon as the resulting 461 * handler is opened? 462 * @param closeOnClose should we call {@link Closeable#close()} on the 463 * underlying input or output objects when 464 * {@link IURLProtocolHandler#close()} is called (if supported by the 465 * underlying object)? 466 * @return Returns a URL that can be passed to AVPKit's {@link IContainer}'s 467 * open method, and will result in IO being performed on the passed in 468 * streams. 469 */ 470 471 public static String map(String url, DataInput in, DataOutput out, 472 boolean unmapOnOpen, boolean closeOnClose) 473 { 474 return map(url, new DataInputOutputHandler(in, out, closeOnClose)); 475 } 476 477 /** 478 * Maps an {@link ReadableByteChannel} or {@link WritableByteChannel} to a URL 479 * for use by AVPKit. 480 * 481 * @param url the url 482 * @param in the input to use 483 * @param out the output to use 484 * @param unmapOnOpen should we remove the mapping as soon as the resulting 485 * handler is opened? 486 * @param closeOnClose should we call {@link Closeable#close()} on the 487 * underlying input or output objects when 488 * {@link IURLProtocolHandler#close()} is called (if supported by the 489 * underlying object)? 490 * @return Returns a URL that can be passed to AVPKit's {@link IContainer}'s 491 * open method, and will result in IO being performed on the passed in 492 * streams. 493 */ 494 495 public static String map(String url, ReadableByteChannel in, 496 WritableByteChannel out, boolean unmapOnOpen, boolean closeOnClose) 497 { 498 return map(url, new ReadableWritableChannelHandler(in, out, closeOnClose)); 499 } 500 501 /** 502 * Maps an {@link InputStream} or {@link OutputStream} to a URL for use by 503 * AVPKit. 504 * 505 * @param url the url 506 * @param in the input to use 507 * @param out the output to use 508 * @param unmapOnOpen should we remove the mapping as soon as the resulting 509 * handler is opened? 510 * @param closeOnClose should we call {@link Closeable#close()} on the 511 * underlying input or output objects when 512 * {@link IURLProtocolHandler#close()} is called? 513 * 514 * @return Returns a URL that can be passed to AVPKit's {@link IContainer}'s 515 * open method, and will result in IO being performed on the passed in 516 * streams. 517 */ 518 519 public static String map(String url, InputStream in, OutputStream out, 520 boolean unmapOnOpen, boolean closeOnClose) 521 { 522 return map(url, new InputOutputStreamHandler(in, out, closeOnClose)); 523 } 524 525 /** 526 * Maps a {@link IURLProtocolHandler} to a url that AVPKit can open. 527 * 528 * @param url the unique string to use for the mapping. 529 * @param handler the handler 530 * @param unmapUrlOnOpen if true, when AVPKit opens the 531 * {@link IURLProtocolHandler}, {@link #unmap(String)} will be called 532 * automatically. 533 * @return a string that is suitable for passing to {@link IContainer}'s open 534 * methods. 535 */ 536 public static String map(String url, IURLProtocolHandler handler, 537 boolean unmapUrlOnOpen) 538 { 539 if (mFactory.mapIO(url, handler, unmapUrlOnOpen) != null) 540 throw new RuntimeException("url is already mapped: " + url); 541 542 return DEFAULT_PROTOCOL + ":" + URLProtocolManager.getResourceFromURL(url); 543 } 544 545 /** 546 * Undoes a URL to {@link InputStream} or {@link OutputStream} mapping. 547 * Forwards to {@link #getFactory()}.{@link #unmapIO(String)} 548 */ 549 public static IURLProtocolHandler unmap(String url) 550 { 551 return mFactory.unmapIO(url); 552 } 553 554 /** 555 * Maps the given url or file name to the given {@link IURLProtocolHandler} or 556 * so that AVPKit calls to open the URL it will call back to the handler. 557 * 558 * <p> 559 * 560 * If you set unmapOnOpen to false, or you never actually open this mapping, 561 * then you must ensure that you call {@link #unmapIO(String)} at some point 562 * in time to remove the mapping, or we will hold onto references to the 563 * handler you passed in. 564 * 565 * </p> 566 * 567 * @param url A file or URL. If a URL, the protocol will be stripped off and 568 * replaced with {@link #DEFAULT_PROTOCOL} when registering. 569 * @param handler An {@link IURLProtocolHandler} for the url 570 * @param unmapUrlOnOpen If true, the handler will unmap itself after an 571 * {@link IContainer} opens the registered URL. If true, you do not 572 * need to call {@link #unmapIO(String)} for this url. 573 * @return The previous handler for this url, or null if none. 574 * 575 * 576 * @throws IllegalArgumentException if url is null or zero length 577 * @throws IllegalArgumentException if handler is null 578 */ 579 580 public IURLProtocolHandler mapIO(String url, IURLProtocolHandler handler, 581 boolean unmapUrlOnOpen) 582 { 583 { 584 if (url == null || url.length() <= 0) 585 throw new IllegalArgumentException("must pass in non-zero url"); 586 if (handler == null) 587 { 588 throw new IllegalArgumentException("must pass in a non null handler"); 589 } 590 String streamName = URLProtocolManager.getResourceFromURL(url); 591 RegistrationInformation tuple = new RegistrationInformation(streamName, 592 handler, unmapUrlOnOpen); 593 RegistrationInformation oldTuple = mURLs.putIfAbsent(streamName, tuple); 594 return oldTuple == null ? null : oldTuple.getHandler(); 595 } 596 } 597 598 /** 599 * Unmaps a registration between a URL and the underlying i/o objects. 600 * <p> 601 * If URL contains a protocol it is ignored when trying to find the matching 602 * IO stream. 603 * </p> 604 * 605 * @param url The stream name to unmap 606 * @return the {@link IURLProtocolHandler} that had been registered for that 607 * url, or null if none. 608 */ 609 public IURLProtocolHandler unmapIO(String url) 610 { 611 if (url == null || url.length() <= 0) 612 throw new IllegalArgumentException("must pass in non-zero url"); 613 String streamName = URLProtocolManager.getResourceFromURL(url); 614 RegistrationInformation oldTuple = mURLs.remove(streamName); 615 return oldTuple == null ? null : oldTuple.getHandler(); 616 } 617 618 /** 619 * {@inheritDoc} 620 */ 621 622 public IURLProtocolHandler getHandler(String url, int flags) 623 { 624 // Note: We need to remove any protocol markers from the url 625 String streamName = URLProtocolManager.getResourceFromURL(url); 626 627 RegistrationInformation tuple = mURLs.get(streamName); 628 if (tuple != null) 629 { 630 IURLProtocolHandler handler = tuple.getHandler(); 631 if (tuple.isUnmappingOnOpen()) 632 { 633 IURLProtocolHandler oldHandler = unmapIO(tuple.getName()); 634 635 // the unmapIO is an atomic operation 636 if (handler != null && !handler.equals(oldHandler)) 637 { 638 // someone already unmapped this stream 639 log.error("stream {} already unmapped; it was likely already opened", 640 641 tuple.getName()); 642 return null; 643 } 644 } 645 return handler; 646 } 647 return null; 648 } 649 650 /** 651 * A set of information about a registered handler. 652 * 653 * @author aclarke 654 * 655 */ 656 657 private static class RegistrationInformation 658 { 659 private final String mName; 660 private final boolean mIsUnmappingOnOpen; 661 private final IURLProtocolHandler mHandler; 662 663 public RegistrationInformation(String streamName, 664 IURLProtocolHandler handler, boolean unmapUrlOnOpen) 665 { 666 mName = streamName; 667 mHandler = handler; 668 mIsUnmappingOnOpen = unmapUrlOnOpen; 669 } 670 671 /** 672 * The name of this handler registration, without any protocol. 673 * 674 * @return the name 675 */ 676 public String getName() 677 { 678 return mName; 679 } 680 681 /** 682 * Should the handler unmap the stream after it is opened? 683 * 684 * @return the decision 685 */ 686 public boolean isUnmappingOnOpen() 687 { 688 return mIsUnmappingOnOpen; 689 } 690 691 public IURLProtocolHandler getHandler() 692 { 693 return mHandler; 694 } 695 } 696}