1 /*
   2  * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.imageio;
  27 
  28 import java.awt.image.BufferedImage;
  29 import java.awt.image.RenderedImage;
  30 import java.io.File;
  31 import java.io.FilePermission;
  32 import java.io.InputStream;
  33 import java.io.IOException;
  34 import java.io.OutputStream;
  35 import java.lang.reflect.Method;
  36 import java.net.URL;
  37 import java.security.AccessController;
  38 import java.util.Arrays;
  39 import java.util.Collections;
  40 import java.util.HashSet;
  41 import java.util.Iterator;
  42 import java.util.NoSuchElementException;
  43 import java.util.Set;
  44 import javax.imageio.spi.IIORegistry;
  45 import javax.imageio.spi.ImageReaderSpi;
  46 import javax.imageio.spi.ImageReaderWriterSpi;
  47 import javax.imageio.spi.ImageWriterSpi;
  48 import javax.imageio.spi.ImageInputStreamSpi;
  49 import javax.imageio.spi.ImageOutputStreamSpi;
  50 import javax.imageio.spi.ImageTranscoderSpi;
  51 import javax.imageio.spi.ServiceRegistry;
  52 import javax.imageio.stream.ImageInputStream;
  53 import javax.imageio.stream.ImageOutputStream;
  54 import sun.awt.AppContext;
  55 import sun.security.action.GetPropertyAction;
  56 
  57 /**
  58  * A class containing static convenience methods for locating
  59  * {@code ImageReader}s and {@code ImageWriter}s, and
  60  * performing simple encoding and decoding.
  61  *
  62  */
  63 public final class ImageIO {
  64 
  65     private static final IIORegistry theRegistry =
  66         IIORegistry.getDefaultInstance();
  67 
  68     /**
  69      * Constructor is private to prevent instantiation.
  70      */
  71     private ImageIO() {}
  72 
  73     /**
  74      * Scans for plug-ins on the application class path,
  75      * loads their service provider classes, and registers a service
  76      * provider instance for each one found with the
  77      * {@code IIORegistry}.
  78      *
  79      * <p>This method is needed because the application class path can
  80      * theoretically change, or additional plug-ins may become available.
  81      * Rather than re-scanning the classpath on every invocation of the
  82      * API, the class path is scanned automatically only on the first
  83      * invocation. Clients can call this method to prompt a re-scan.
  84      * Thus this method need only be invoked by sophisticated applications
  85      * which dynamically make new plug-ins available at runtime.
  86      *
  87      * <p> The {@code getResources} method of the context
  88      * {@code ClassLoader} is used locate JAR files containing
  89      * files named
  90      * {@code META-INF/services/javax.imageio.spi.}<i>classname</i>,
  91      * where <i>classname</i> is one of {@code ImageReaderSpi},
  92      * {@code ImageWriterSpi}, {@code ImageTranscoderSpi},
  93      * {@code ImageInputStreamSpi}, or
  94      * {@code ImageOutputStreamSpi}, along the application class
  95      * path.
  96      *
  97      * <p> The contents of the located files indicate the names of
  98      * actual implementation classes which implement the
  99      * aforementioned service provider interfaces; the default class
 100      * loader is then used to load each of these classes and to
 101      * instantiate an instance of each class, which is then placed
 102      * into the registry for later retrieval.
 103      *
 104      * <p> The exact set of locations searched depends on the
 105      * implementation of the Java runtime environment.
 106      *
 107      * @see ClassLoader#getResources
 108      */
 109     public static void scanForPlugins() {
 110         theRegistry.registerApplicationClasspathSpis();
 111     }
 112 
 113     // ImageInputStreams
 114 
 115     /**
 116      * A class to hold information about caching.  Each
 117      * {@code ThreadGroup} will have its own copy
 118      * via the {@code AppContext} mechanism.
 119      */
 120     static class CacheInfo {
 121         boolean useCache = true;
 122         File cacheDirectory = null;
 123         Boolean hasPermission = null;
 124 
 125         public CacheInfo() {}
 126 
 127         public boolean getUseCache() {
 128             return useCache;
 129         }
 130 
 131         public void setUseCache(boolean useCache) {
 132             this.useCache = useCache;
 133         }
 134 
 135         public File getCacheDirectory() {
 136             return cacheDirectory;
 137         }
 138 
 139         public void setCacheDirectory(File cacheDirectory) {
 140             this.cacheDirectory = cacheDirectory;
 141         }
 142 
 143         public Boolean getHasPermission() {
 144             return hasPermission;
 145         }
 146 
 147         public void setHasPermission(Boolean hasPermission) {
 148             this.hasPermission = hasPermission;
 149         }
 150     }
 151 
 152     /**
 153      * Returns the {@code CacheInfo} object associated with this
 154      * {@code ThreadGroup}.
 155      */
 156     private static synchronized CacheInfo getCacheInfo() {
 157         AppContext context = AppContext.getAppContext();
 158         CacheInfo info = (CacheInfo)context.get(CacheInfo.class);
 159         if (info == null) {
 160             info = new CacheInfo();
 161             context.put(CacheInfo.class, info);
 162         }
 163         return info;
 164     }
 165 
 166     /**
 167      * Returns the default temporary (cache) directory as defined by the
 168      * java.io.tmpdir system property.
 169      */
 170     private static String getTempDir() {
 171         GetPropertyAction a = new GetPropertyAction("java.io.tmpdir");
 172         return AccessController.doPrivileged(a);
 173     }
 174 
 175     /**
 176      * Determines whether the caller has write access to the cache
 177      * directory, stores the result in the {@code CacheInfo} object,
 178      * and returns the decision.  This method helps to prevent mysterious
 179      * SecurityExceptions to be thrown when this convenience class is used
 180      * in an applet, for example.
 181      */
 182     private static boolean hasCachePermission() {
 183         Boolean hasPermission = getCacheInfo().getHasPermission();
 184 
 185         if (hasPermission != null) {
 186             return hasPermission.booleanValue();
 187         } else {
 188             try {
 189                 SecurityManager security = System.getSecurityManager();
 190                 if (security != null) {
 191                     File cachedir = getCacheDirectory();
 192                     String cachepath;
 193 
 194                     if (cachedir != null) {
 195                         cachepath = cachedir.getPath();
 196                     } else {
 197                         cachepath = getTempDir();
 198 
 199                         if (cachepath == null || cachepath.isEmpty()) {
 200                             getCacheInfo().setHasPermission(Boolean.FALSE);
 201                             return false;
 202                         }
 203                     }
 204 
 205                     // we have to check whether we can read, write,
 206                     // and delete cache files.
 207                     // So, compose cache file path and check it.
 208                     String filepath = cachepath;
 209                     if (!filepath.endsWith(File.separator)) {
 210                         filepath += File.separator;
 211                     }
 212                     filepath += "*";
 213 
 214                     security.checkPermission(new FilePermission(filepath, "read, write, delete"));
 215                 }
 216             } catch (SecurityException e) {
 217                 getCacheInfo().setHasPermission(Boolean.FALSE);
 218                 return false;
 219             }
 220 
 221             getCacheInfo().setHasPermission(Boolean.TRUE);
 222             return true;
 223         }
 224     }
 225 
 226     /**
 227      * Sets a flag indicating whether a disk-based cache file should
 228      * be used when creating {@code ImageInputStream}s and
 229      * {@code ImageOutputStream}s.
 230      *
 231      * <p> When reading from a standard {@code InputStream}, it
 232      * may be necessary to save previously read information in a cache
 233      * since the underlying stream does not allow data to be re-read.
 234      * Similarly, when writing to a standard
 235      * {@code OutputStream}, a cache may be used to allow a
 236      * previously written value to be changed before flushing it to
 237      * the final destination.
 238      *
 239      * <p> The cache may reside in main memory or on disk.  Setting
 240      * this flag to {@code false} disallows the use of disk for
 241      * future streams, which may be advantageous when working with
 242      * small images, as the overhead of creating and destroying files
 243      * is removed.
 244      *
 245      * <p> On startup, the value is set to {@code true}.
 246      *
 247      * @param useCache a {@code boolean} indicating whether a
 248      * cache file should be used, in cases where it is optional.
 249      *
 250      * @see #getUseCache
 251      */
 252     public static void setUseCache(boolean useCache) {
 253         getCacheInfo().setUseCache(useCache);
 254     }
 255 
 256     /**
 257      * Returns the current value set by {@code setUseCache}, or
 258      * {@code true} if no explicit setting has been made.
 259      *
 260      * @return true if a disk-based cache may be used for
 261      * {@code ImageInputStream}s and
 262      * {@code ImageOutputStream}s.
 263      *
 264      * @see #setUseCache
 265      */
 266     public static boolean getUseCache() {
 267         return getCacheInfo().getUseCache();
 268     }
 269 
 270     /**
 271      * Sets the directory where cache files are to be created.  A
 272      * value of {@code null} indicates that the system-dependent
 273      * default temporary-file directory is to be used.  If
 274      * {@code getUseCache} returns false, this value is ignored.
 275      *
 276      * @param cacheDirectory a {@code File} specifying a directory.
 277      *
 278      * @see File#createTempFile(String, String, File)
 279      *
 280      * @exception SecurityException if the security manager denies
 281      * access to the directory.
 282      * @exception IllegalArgumentException if {@code cacheDir} is
 283      * non-{@code null} but is not a directory.
 284      *
 285      * @see #getCacheDirectory
 286      */
 287     public static void setCacheDirectory(File cacheDirectory) {
 288         if ((cacheDirectory != null) && !(cacheDirectory.isDirectory())) {
 289             throw new IllegalArgumentException("Not a directory!");
 290         }
 291         getCacheInfo().setCacheDirectory(cacheDirectory);
 292         getCacheInfo().setHasPermission(null);
 293     }
 294 
 295     /**
 296      * Returns the current value set by
 297      * {@code setCacheDirectory}, or {@code null} if no
 298      * explicit setting has been made.
 299      *
 300      * @return a {@code File} indicating the directory where
 301      * cache files will be created, or {@code null} to indicate
 302      * the system-dependent default temporary-file directory.
 303      *
 304      * @see #setCacheDirectory
 305      */
 306     public static File getCacheDirectory() {
 307         return getCacheInfo().getCacheDirectory();
 308     }
 309 
 310     /**
 311      * Returns an {@code ImageInputStream} that will take its
 312      * input from the given {@code Object}.  The set of
 313      * {@code ImageInputStreamSpi}s registered with the
 314      * {@code IIORegistry} class is queried and the first one
 315      * that is able to take input from the supplied object is used to
 316      * create the returned {@code ImageInputStream}.  If no
 317      * suitable {@code ImageInputStreamSpi} exists,
 318      * {@code null} is returned.
 319      *
 320      * <p> The current cache settings from {@code getUseCache} and
 321      * {@code getCacheDirectory} will be used to control caching.
 322      *
 323      * @param input an {@code Object} to be used as an input
 324      * source, such as a {@code File}, readable
 325      * {@code RandomAccessFile}, or {@code InputStream}.
 326      *
 327      * @return an {@code ImageInputStream}, or {@code null}.
 328      *
 329      * @exception IllegalArgumentException if {@code input}
 330      * is {@code null}.
 331      * @exception IOException if a cache file is needed but cannot be
 332      * created.
 333      *
 334      * @see javax.imageio.spi.ImageInputStreamSpi
 335      */
 336     public static ImageInputStream createImageInputStream(Object input)
 337         throws IOException {
 338         if (input == null) {
 339             throw new IllegalArgumentException("input == null!");
 340         }
 341 
 342         Iterator<ImageInputStreamSpi> iter;
 343         // Ensure category is present
 344         try {
 345             iter = theRegistry.getServiceProviders(ImageInputStreamSpi.class,
 346                                                    true);
 347         } catch (IllegalArgumentException e) {
 348             return null;
 349         }
 350 
 351         boolean usecache = getUseCache() && hasCachePermission();
 352 
 353         while (iter.hasNext()) {
 354             ImageInputStreamSpi spi = iter.next();
 355             if (spi.getInputClass().isInstance(input)) {
 356                 try {
 357                     return spi.createInputStreamInstance(input,
 358                                                          usecache,
 359                                                          getCacheDirectory());
 360                 } catch (IOException e) {
 361                     throw new IIOException("Can't create cache file!", e);
 362                 }
 363             }
 364         }
 365 
 366         return null;
 367     }
 368 
 369     // ImageOutputStreams
 370 
 371     /**
 372      * Returns an {@code ImageOutputStream} that will send its
 373      * output to the given {@code Object}.  The set of
 374      * {@code ImageOutputStreamSpi}s registered with the
 375      * {@code IIORegistry} class is queried and the first one
 376      * that is able to send output from the supplied object is used to
 377      * create the returned {@code ImageOutputStream}.  If no
 378      * suitable {@code ImageOutputStreamSpi} exists,
 379      * {@code null} is returned.
 380      *
 381      * <p> The current cache settings from {@code getUseCache} and
 382      * {@code getCacheDirectory} will be used to control caching.
 383      *
 384      * @param output an {@code Object} to be used as an output
 385      * destination, such as a {@code File}, writable
 386      * {@code RandomAccessFile}, or {@code OutputStream}.
 387      *
 388      * @return an {@code ImageOutputStream}, or
 389      * {@code null}.
 390      *
 391      * @exception IllegalArgumentException if {@code output} is
 392      * {@code null}.
 393      * @exception IOException if a cache file is needed but cannot be
 394      * created.
 395      *
 396      * @see javax.imageio.spi.ImageOutputStreamSpi
 397      */
 398     public static ImageOutputStream createImageOutputStream(Object output)
 399         throws IOException {
 400         if (output == null) {
 401             throw new IllegalArgumentException("output == null!");
 402         }
 403 
 404         Iterator<ImageOutputStreamSpi> iter;
 405         // Ensure category is present
 406         try {
 407             iter = theRegistry.getServiceProviders(ImageOutputStreamSpi.class,
 408                                                    true);
 409         } catch (IllegalArgumentException e) {
 410             return null;
 411         }
 412 
 413         boolean usecache = getUseCache() && hasCachePermission();
 414 
 415         while (iter.hasNext()) {
 416             ImageOutputStreamSpi spi = iter.next();
 417             if (spi.getOutputClass().isInstance(output)) {
 418                 try {
 419                     return spi.createOutputStreamInstance(output,
 420                                                           usecache,
 421                                                           getCacheDirectory());
 422                 } catch (IOException e) {
 423                     throw new IIOException("Can't create cache file!", e);
 424                 }
 425             }
 426         }
 427 
 428         return null;
 429     }
 430 
 431     private static enum SpiInfo {
 432         FORMAT_NAMES {
 433             @Override
 434             String[] info(ImageReaderWriterSpi spi) {
 435                 return spi.getFormatNames();
 436             }
 437         },
 438         MIME_TYPES {
 439             @Override
 440             String[] info(ImageReaderWriterSpi spi) {
 441                 return spi.getMIMETypes();
 442             }
 443         },
 444         FILE_SUFFIXES {
 445             @Override
 446             String[] info(ImageReaderWriterSpi spi) {
 447                 return spi.getFileSuffixes();
 448             }
 449         };
 450 
 451         abstract String[] info(ImageReaderWriterSpi spi);
 452     }
 453 
 454     private static <S extends ImageReaderWriterSpi>
 455         String[] getReaderWriterInfo(Class<S> spiClass, SpiInfo spiInfo)
 456     {
 457         // Ensure category is present
 458         Iterator<S> iter;
 459         try {
 460             iter = theRegistry.getServiceProviders(spiClass, true);
 461         } catch (IllegalArgumentException e) {
 462             return new String[0];
 463         }
 464 
 465         HashSet<String> s = new HashSet<String>();
 466         while (iter.hasNext()) {
 467             ImageReaderWriterSpi spi = iter.next();
 468             Collections.addAll(s, spiInfo.info(spi));
 469         }
 470 
 471         return s.toArray(new String[s.size()]);
 472     }
 473 
 474     // Readers
 475 
 476     /**
 477      * Returns an array of {@code String}s listing all of the
 478      * informal format names understood by the current set of registered
 479      * readers.
 480      *
 481      * @return an array of {@code String}s.
 482      */
 483     public static String[] getReaderFormatNames() {
 484         return getReaderWriterInfo(ImageReaderSpi.class,
 485                                    SpiInfo.FORMAT_NAMES);
 486     }
 487 
 488     /**
 489      * Returns an array of {@code String}s listing all of the
 490      * MIME types understood by the current set of registered
 491      * readers.
 492      *
 493      * @return an array of {@code String}s.
 494      */
 495     public static String[] getReaderMIMETypes() {
 496         return getReaderWriterInfo(ImageReaderSpi.class,
 497                                    SpiInfo.MIME_TYPES);
 498     }
 499 
 500     /**
 501      * Returns an array of {@code String}s listing all of the
 502      * file suffixes associated with the formats understood
 503      * by the current set of registered readers.
 504      *
 505      * @return an array of {@code String}s.
 506      * @since 1.6
 507      */
 508     public static String[] getReaderFileSuffixes() {
 509         return getReaderWriterInfo(ImageReaderSpi.class,
 510                                    SpiInfo.FILE_SUFFIXES);
 511     }
 512 
 513     static class ImageReaderIterator implements Iterator<ImageReader> {
 514         // Contains ImageReaderSpis
 515         private Iterator<ImageReaderSpi> iter;
 516 
 517         public ImageReaderIterator(Iterator<ImageReaderSpi> iter) {
 518             this.iter = iter;
 519         }
 520 
 521         public boolean hasNext() {
 522             return iter.hasNext();
 523         }
 524 
 525         public ImageReader next() {
 526             ImageReaderSpi spi = null;
 527             try {
 528                 spi = iter.next();
 529                 return spi.createReaderInstance();
 530             } catch (IOException e) {
 531                 // Deregister the spi in this case, but only as
 532                 // an ImageReaderSpi
 533                 theRegistry.deregisterServiceProvider(spi, ImageReaderSpi.class);
 534             }
 535             return null;
 536         }
 537 
 538         public void remove() {
 539             throw new UnsupportedOperationException();
 540         }
 541     }
 542 
 543     static class CanDecodeInputFilter
 544         implements ServiceRegistry.Filter {
 545 
 546         Object input;
 547 
 548         public CanDecodeInputFilter(Object input) {
 549             this.input = input;
 550         }
 551 
 552         public boolean filter(Object elt) {
 553             try {
 554                 ImageReaderSpi spi = (ImageReaderSpi)elt;
 555                 ImageInputStream stream = null;
 556                 if (input instanceof ImageInputStream) {
 557                     stream = (ImageInputStream)input;
 558                 }
 559 
 560                 // Perform mark/reset as a defensive measure
 561                 // even though plug-ins are supposed to take
 562                 // care of it.
 563                 boolean canDecode = false;
 564                 if (stream != null) {
 565                     stream.mark();
 566                 }
 567                 try {
 568                     canDecode = spi.canDecodeInput(input);
 569                 } finally {
 570                     if (stream != null) {
 571                         stream.reset();
 572                     }
 573                 }
 574 
 575                 return canDecode;
 576             } catch (IOException e) {
 577                 return false;
 578             }
 579         }
 580     }
 581 
 582     static class CanEncodeImageAndFormatFilter
 583         implements ServiceRegistry.Filter {
 584 
 585         ImageTypeSpecifier type;
 586         String formatName;
 587 
 588         public CanEncodeImageAndFormatFilter(ImageTypeSpecifier type,
 589                                              String formatName) {
 590             this.type = type;
 591             this.formatName = formatName;
 592         }
 593 
 594         public boolean filter(Object elt) {
 595             ImageWriterSpi spi = (ImageWriterSpi)elt;
 596             return Arrays.asList(spi.getFormatNames()).contains(formatName) &&
 597                 spi.canEncodeImage(type);
 598         }
 599     }
 600 
 601     static class ContainsFilter
 602         implements ServiceRegistry.Filter {
 603 
 604         Method method;
 605         String name;
 606 
 607         // method returns an array of Strings
 608         public ContainsFilter(Method method,
 609                               String name) {
 610             this.method = method;
 611             this.name = name;
 612         }
 613 
 614         public boolean filter(Object elt) {
 615             try {
 616                 return contains((String[])method.invoke(elt), name);
 617             } catch (Exception e) {
 618                 return false;
 619             }
 620         }
 621     }
 622 
 623     /**
 624      * Returns an {@code Iterator} containing all currently
 625      * registered {@code ImageReader}s that claim to be able to
 626      * decode the supplied {@code Object}, typically an
 627      * {@code ImageInputStream}.
 628      *
 629      * <p> The stream position is left at its prior position upon
 630      * exit from this method.
 631      *
 632      * @param input an {@code ImageInputStream} or other
 633      * {@code Object} containing encoded image data.
 634      *
 635      * @return an {@code Iterator} containing {@code ImageReader}s.
 636      *
 637      * @exception IllegalArgumentException if {@code input} is
 638      * {@code null}.
 639      *
 640      * @see javax.imageio.spi.ImageReaderSpi#canDecodeInput
 641      */
 642     public static Iterator<ImageReader> getImageReaders(Object input) {
 643         if (input == null) {
 644             throw new IllegalArgumentException("input == null!");
 645         }
 646         Iterator<ImageReaderSpi> iter;
 647         // Ensure category is present
 648         try {
 649             iter = theRegistry.getServiceProviders(ImageReaderSpi.class,
 650                                               new CanDecodeInputFilter(input),
 651                                               true);
 652         } catch (IllegalArgumentException e) {
 653             return Collections.emptyIterator();
 654         }
 655 
 656         return new ImageReaderIterator(iter);
 657     }
 658 
 659     private static Method readerFormatNamesMethod;
 660     private static Method readerFileSuffixesMethod;
 661     private static Method readerMIMETypesMethod;
 662     private static Method writerFormatNamesMethod;
 663     private static Method writerFileSuffixesMethod;
 664     private static Method writerMIMETypesMethod;
 665 
 666     static {
 667         try {
 668             readerFormatNamesMethod =
 669                 ImageReaderSpi.class.getMethod("getFormatNames");
 670             readerFileSuffixesMethod =
 671                 ImageReaderSpi.class.getMethod("getFileSuffixes");
 672             readerMIMETypesMethod =
 673                 ImageReaderSpi.class.getMethod("getMIMETypes");
 674 
 675             writerFormatNamesMethod =
 676                 ImageWriterSpi.class.getMethod("getFormatNames");
 677             writerFileSuffixesMethod =
 678                 ImageWriterSpi.class.getMethod("getFileSuffixes");
 679             writerMIMETypesMethod =
 680                 ImageWriterSpi.class.getMethod("getMIMETypes");
 681         } catch (NoSuchMethodException e) {
 682             e.printStackTrace();
 683         }
 684     }
 685 
 686     /**
 687      * Returns an {@code Iterator} containing all currently
 688      * registered {@code ImageReader}s that claim to be able to
 689      * decode the named format.
 690      *
 691      * @param formatName a {@code String} containing the informal
 692      * name of a format (<i>e.g.</i>, "jpeg" or "tiff".
 693      *
 694      * @return an {@code Iterator} containing
 695      * {@code ImageReader}s.
 696      *
 697      * @exception IllegalArgumentException if {@code formatName}
 698      * is {@code null}.
 699      *
 700      * @see javax.imageio.spi.ImageReaderSpi#getFormatNames
 701      */
 702     public static Iterator<ImageReader>
 703         getImageReadersByFormatName(String formatName)
 704     {
 705         if (formatName == null) {
 706             throw new IllegalArgumentException("formatName == null!");
 707         }
 708         Iterator<ImageReaderSpi> iter;
 709         // Ensure category is present
 710         try {
 711             iter = theRegistry.getServiceProviders(ImageReaderSpi.class,
 712                                     new ContainsFilter(readerFormatNamesMethod,
 713                                                        formatName),
 714                                                 true);
 715         } catch (IllegalArgumentException e) {
 716             return Collections.emptyIterator();
 717         }
 718         return new ImageReaderIterator(iter);
 719     }
 720 
 721     /**
 722      * Returns an {@code Iterator} containing all currently
 723      * registered {@code ImageReader}s that claim to be able to
 724      * decode files with the given suffix.
 725      *
 726      * @param fileSuffix a {@code String} containing a file
 727      * suffix (<i>e.g.</i>, "jpg" or "tiff").
 728      *
 729      * @return an {@code Iterator} containing
 730      * {@code ImageReader}s.
 731      *
 732      * @exception IllegalArgumentException if {@code fileSuffix}
 733      * is {@code null}.
 734      *
 735      * @see javax.imageio.spi.ImageReaderSpi#getFileSuffixes
 736      */
 737     public static Iterator<ImageReader>
 738         getImageReadersBySuffix(String fileSuffix)
 739     {
 740         if (fileSuffix == null) {
 741             throw new IllegalArgumentException("fileSuffix == null!");
 742         }
 743         // Ensure category is present
 744         Iterator<ImageReaderSpi> iter;
 745         try {
 746             iter = theRegistry.getServiceProviders(ImageReaderSpi.class,
 747                                    new ContainsFilter(readerFileSuffixesMethod,
 748                                                       fileSuffix),
 749                                               true);
 750         } catch (IllegalArgumentException e) {
 751             return Collections.emptyIterator();
 752         }
 753         return new ImageReaderIterator(iter);
 754     }
 755 
 756     /**
 757      * Returns an {@code Iterator} containing all currently
 758      * registered {@code ImageReader}s that claim to be able to
 759      * decode files with the given MIME type.
 760      *
 761      * @param MIMEType a {@code String} containing a file
 762      * suffix (<i>e.g.</i>, "image/jpeg" or "image/x-bmp").
 763      *
 764      * @return an {@code Iterator} containing
 765      * {@code ImageReader}s.
 766      *
 767      * @exception IllegalArgumentException if {@code MIMEType} is
 768      * {@code null}.
 769      *
 770      * @see javax.imageio.spi.ImageReaderSpi#getMIMETypes
 771      */
 772     public static Iterator<ImageReader>
 773         getImageReadersByMIMEType(String MIMEType)
 774     {
 775         if (MIMEType == null) {
 776             throw new IllegalArgumentException("MIMEType == null!");
 777         }
 778         // Ensure category is present
 779         Iterator<ImageReaderSpi> iter;
 780         try {
 781             iter = theRegistry.getServiceProviders(ImageReaderSpi.class,
 782                                       new ContainsFilter(readerMIMETypesMethod,
 783                                                          MIMEType),
 784                                               true);
 785         } catch (IllegalArgumentException e) {
 786             return Collections.emptyIterator();
 787         }
 788         return new ImageReaderIterator(iter);
 789     }
 790 
 791     // Writers
 792 
 793     /**
 794      * Returns an array of {@code String}s listing all of the
 795      * informal format names understood by the current set of registered
 796      * writers.
 797      *
 798      * @return an array of {@code String}s.
 799      */
 800     public static String[] getWriterFormatNames() {
 801         return getReaderWriterInfo(ImageWriterSpi.class,
 802                                    SpiInfo.FORMAT_NAMES);
 803     }
 804 
 805     /**
 806      * Returns an array of {@code String}s listing all of the
 807      * MIME types understood by the current set of registered
 808      * writers.
 809      *
 810      * @return an array of {@code String}s.
 811      */
 812     public static String[] getWriterMIMETypes() {
 813         return getReaderWriterInfo(ImageWriterSpi.class,
 814                                    SpiInfo.MIME_TYPES);
 815     }
 816 
 817     /**
 818      * Returns an array of {@code String}s listing all of the
 819      * file suffixes associated with the formats understood
 820      * by the current set of registered writers.
 821      *
 822      * @return an array of {@code String}s.
 823      * @since 1.6
 824      */
 825     public static String[] getWriterFileSuffixes() {
 826         return getReaderWriterInfo(ImageWriterSpi.class,
 827                                    SpiInfo.FILE_SUFFIXES);
 828     }
 829 
 830     static class ImageWriterIterator implements Iterator<ImageWriter> {
 831         // Contains ImageWriterSpis
 832         private Iterator<ImageWriterSpi> iter;
 833 
 834         public ImageWriterIterator(Iterator<ImageWriterSpi> iter) {
 835             this.iter = iter;
 836         }
 837 
 838         public boolean hasNext() {
 839             return iter.hasNext();
 840         }
 841 
 842         public ImageWriter next() {
 843             ImageWriterSpi spi = null;
 844             try {
 845                 spi = iter.next();
 846                 return spi.createWriterInstance();
 847             } catch (IOException e) {
 848                 // Deregister the spi in this case, but only as a writerSpi
 849                 theRegistry.deregisterServiceProvider(spi, ImageWriterSpi.class);
 850             }
 851             return null;
 852         }
 853 
 854         public void remove() {
 855             throw new UnsupportedOperationException();
 856         }
 857     }
 858 
 859     private static boolean contains(String[] names, String name) {
 860         for (int i = 0; i < names.length; i++) {
 861             if (name.equalsIgnoreCase(names[i])) {
 862                 return true;
 863             }
 864         }
 865 
 866         return false;
 867     }
 868 
 869     /**
 870      * Returns an {@code Iterator} containing all currently
 871      * registered {@code ImageWriter}s that claim to be able to
 872      * encode the named format.
 873      *
 874      * @param formatName a {@code String} containing the informal
 875      * name of a format (<i>e.g.</i>, "jpeg" or "tiff".
 876      *
 877      * @return an {@code Iterator} containing
 878      * {@code ImageWriter}s.
 879      *
 880      * @exception IllegalArgumentException if {@code formatName} is
 881      * {@code null}.
 882      *
 883      * @see javax.imageio.spi.ImageWriterSpi#getFormatNames
 884      */
 885     public static Iterator<ImageWriter>
 886         getImageWritersByFormatName(String formatName)
 887     {
 888         if (formatName == null) {
 889             throw new IllegalArgumentException("formatName == null!");
 890         }
 891         Iterator<ImageWriterSpi> iter;
 892         // Ensure category is present
 893         try {
 894             iter = theRegistry.getServiceProviders(ImageWriterSpi.class,
 895                                     new ContainsFilter(writerFormatNamesMethod,
 896                                                        formatName),
 897                                             true);
 898         } catch (IllegalArgumentException e) {
 899             return Collections.emptyIterator();
 900         }
 901         return new ImageWriterIterator(iter);
 902     }
 903 
 904     /**
 905      * Returns an {@code Iterator} containing all currently
 906      * registered {@code ImageWriter}s that claim to be able to
 907      * encode files with the given suffix.
 908      *
 909      * @param fileSuffix a {@code String} containing a file
 910      * suffix (<i>e.g.</i>, "jpg" or "tiff").
 911      *
 912      * @return an {@code Iterator} containing {@code ImageWriter}s.
 913      *
 914      * @exception IllegalArgumentException if {@code fileSuffix} is
 915      * {@code null}.
 916      *
 917      * @see javax.imageio.spi.ImageWriterSpi#getFileSuffixes
 918      */
 919     public static Iterator<ImageWriter>
 920         getImageWritersBySuffix(String fileSuffix)
 921     {
 922         if (fileSuffix == null) {
 923             throw new IllegalArgumentException("fileSuffix == null!");
 924         }
 925         Iterator<ImageWriterSpi> iter;
 926         // Ensure category is present
 927         try {
 928             iter = theRegistry.getServiceProviders(ImageWriterSpi.class,
 929                                    new ContainsFilter(writerFileSuffixesMethod,
 930                                                       fileSuffix),
 931                                             true);
 932         } catch (IllegalArgumentException e) {
 933             return Collections.emptyIterator();
 934         }
 935         return new ImageWriterIterator(iter);
 936     }
 937 
 938     /**
 939      * Returns an {@code Iterator} containing all currently
 940      * registered {@code ImageWriter}s that claim to be able to
 941      * encode files with the given MIME type.
 942      *
 943      * @param MIMEType a {@code String} containing a file
 944      * suffix (<i>e.g.</i>, "image/jpeg" or "image/x-bmp").
 945      *
 946      * @return an {@code Iterator} containing {@code ImageWriter}s.
 947      *
 948      * @exception IllegalArgumentException if {@code MIMEType} is
 949      * {@code null}.
 950      *
 951      * @see javax.imageio.spi.ImageWriterSpi#getMIMETypes
 952      */
 953     public static Iterator<ImageWriter>
 954         getImageWritersByMIMEType(String MIMEType)
 955     {
 956         if (MIMEType == null) {
 957             throw new IllegalArgumentException("MIMEType == null!");
 958         }
 959         Iterator<ImageWriterSpi> iter;
 960         // Ensure category is present
 961         try {
 962             iter = theRegistry.getServiceProviders(ImageWriterSpi.class,
 963                                       new ContainsFilter(writerMIMETypesMethod,
 964                                                          MIMEType),
 965                                             true);
 966         } catch (IllegalArgumentException e) {
 967             return Collections.emptyIterator();
 968         }
 969         return new ImageWriterIterator(iter);
 970     }
 971 
 972     /**
 973      * Returns an {@code ImageWriter} corresponding to the given
 974      * {@code ImageReader}, if there is one, or {@code null}
 975      * if the plug-in for this {@code ImageReader} does not
 976      * specify a corresponding {@code ImageWriter}, or if the
 977      * given {@code ImageReader} is not registered.  This
 978      * mechanism may be used to obtain an {@code ImageWriter}
 979      * that will understand the internal structure of non-pixel
 980      * metadata (as encoded by {@code IIOMetadata} objects)
 981      * generated by the {@code ImageReader}.  By obtaining this
 982      * data from the {@code ImageReader} and passing it on to the
 983      * {@code ImageWriter} obtained with this method, a client
 984      * program can read an image, modify it in some way, and write it
 985      * back out preserving all metadata, without having to understand
 986      * anything about the structure of the metadata, or even about
 987      * the image format.  Note that this method returns the
 988      * "preferred" writer, which is the first in the list returned by
 989      * {@code javax.imageio.spi.ImageReaderSpi.getImageWriterSpiNames()}.
 990      *
 991      * @param reader an instance of a registered {@code ImageReader}.
 992      *
 993      * @return an {@code ImageWriter}, or null.
 994      *
 995      * @exception IllegalArgumentException if {@code reader} is
 996      * {@code null}.
 997      *
 998      * @see #getImageReader(ImageWriter)
 999      * @see javax.imageio.spi.ImageReaderSpi#getImageWriterSpiNames()
1000      */
1001     public static ImageWriter getImageWriter(ImageReader reader) {
1002         if (reader == null) {
1003             throw new IllegalArgumentException("reader == null!");
1004         }
1005 
1006         ImageReaderSpi readerSpi = reader.getOriginatingProvider();
1007         if (readerSpi == null) {
1008             Iterator<ImageReaderSpi> readerSpiIter;
1009             // Ensure category is present
1010             try {
1011                 readerSpiIter =
1012                     theRegistry.getServiceProviders(ImageReaderSpi.class,
1013                                                     false);
1014             } catch (IllegalArgumentException e) {
1015                 return null;
1016             }
1017 
1018             while (readerSpiIter.hasNext()) {
1019                 ImageReaderSpi temp = readerSpiIter.next();
1020                 if (temp.isOwnReader(reader)) {
1021                     readerSpi = temp;
1022                     break;
1023                 }
1024             }
1025             if (readerSpi == null) {
1026                 return null;
1027             }
1028         }
1029 
1030         String[] writerNames = readerSpi.getImageWriterSpiNames();
1031         if (writerNames == null) {
1032             return null;
1033         }
1034 
1035         Class<?> writerSpiClass = null;
1036         try {
1037             writerSpiClass = Class.forName(writerNames[0], true,
1038                                            ClassLoader.getSystemClassLoader());
1039         } catch (ClassNotFoundException e) {
1040             return null;
1041         }
1042 
1043         ImageWriterSpi writerSpi = (ImageWriterSpi)
1044             theRegistry.getServiceProviderByClass(writerSpiClass);
1045         if (writerSpi == null) {
1046             return null;
1047         }
1048 
1049         try {
1050             return writerSpi.createWriterInstance();
1051         } catch (IOException e) {
1052             // Deregister the spi in this case, but only as a writerSpi
1053             theRegistry.deregisterServiceProvider(writerSpi,
1054                                                   ImageWriterSpi.class);
1055             return null;
1056         }
1057     }
1058 
1059     /**
1060      * Returns an {@code ImageReader} corresponding to the given
1061      * {@code ImageWriter}, if there is one, or {@code null}
1062      * if the plug-in for this {@code ImageWriter} does not
1063      * specify a corresponding {@code ImageReader}, or if the
1064      * given {@code ImageWriter} is not registered.  This method
1065      * is provided principally for symmetry with
1066      * {@code getImageWriter(ImageReader)}.  Note that this
1067      * method returns the "preferred" reader, which is the first in
1068      * the list returned by
1069      * javax.imageio.spi.ImageWriterSpi.{@code getImageReaderSpiNames()}.
1070      *
1071      * @param writer an instance of a registered {@code ImageWriter}.
1072      *
1073      * @return an {@code ImageReader}, or null.
1074      *
1075      * @exception IllegalArgumentException if {@code writer} is
1076      * {@code null}.
1077      *
1078      * @see #getImageWriter(ImageReader)
1079      * @see javax.imageio.spi.ImageWriterSpi#getImageReaderSpiNames()
1080      */
1081     public static ImageReader getImageReader(ImageWriter writer) {
1082         if (writer == null) {
1083             throw new IllegalArgumentException("writer == null!");
1084         }
1085 
1086         ImageWriterSpi writerSpi = writer.getOriginatingProvider();
1087         if (writerSpi == null) {
1088             Iterator<ImageWriterSpi> writerSpiIter;
1089             // Ensure category is present
1090             try {
1091                 writerSpiIter =
1092                     theRegistry.getServiceProviders(ImageWriterSpi.class,
1093                                                     false);
1094             } catch (IllegalArgumentException e) {
1095                 return null;
1096             }
1097 
1098             while (writerSpiIter.hasNext()) {
1099                 ImageWriterSpi temp = writerSpiIter.next();
1100                 if (temp.isOwnWriter(writer)) {
1101                     writerSpi = temp;
1102                     break;
1103                 }
1104             }
1105             if (writerSpi == null) {
1106                 return null;
1107             }
1108         }
1109 
1110         String[] readerNames = writerSpi.getImageReaderSpiNames();
1111         if (readerNames == null) {
1112             return null;
1113         }
1114 
1115         Class<?> readerSpiClass = null;
1116         try {
1117             readerSpiClass = Class.forName(readerNames[0], true,
1118                                            ClassLoader.getSystemClassLoader());
1119         } catch (ClassNotFoundException e) {
1120             return null;
1121         }
1122 
1123         ImageReaderSpi readerSpi = (ImageReaderSpi)
1124             theRegistry.getServiceProviderByClass(readerSpiClass);
1125         if (readerSpi == null) {
1126             return null;
1127         }
1128 
1129         try {
1130             return readerSpi.createReaderInstance();
1131         } catch (IOException e) {
1132             // Deregister the spi in this case, but only as a readerSpi
1133             theRegistry.deregisterServiceProvider(readerSpi,
1134                                                   ImageReaderSpi.class);
1135             return null;
1136         }
1137     }
1138 
1139     /**
1140      * Returns an {@code Iterator} containing all currently
1141      * registered {@code ImageWriter}s that claim to be able to
1142      * encode images of the given layout (specified using an
1143      * {@code ImageTypeSpecifier}) in the given format.
1144      *
1145      * @param type an {@code ImageTypeSpecifier} indicating the
1146      * layout of the image to be written.
1147      * @param formatName the informal name of the {@code format}.
1148      *
1149      * @return an {@code Iterator} containing {@code ImageWriter}s.
1150      *
1151      * @exception IllegalArgumentException if any parameter is
1152      * {@code null}.
1153      *
1154      * @see javax.imageio.spi.ImageWriterSpi#canEncodeImage(ImageTypeSpecifier)
1155      */
1156     public static Iterator<ImageWriter>
1157         getImageWriters(ImageTypeSpecifier type, String formatName)
1158     {
1159         if (type == null) {
1160             throw new IllegalArgumentException("type == null!");
1161         }
1162         if (formatName == null) {
1163             throw new IllegalArgumentException("formatName == null!");
1164         }
1165 
1166         Iterator<ImageWriterSpi> iter;
1167         // Ensure category is present
1168         try {
1169             iter = theRegistry.getServiceProviders(ImageWriterSpi.class,
1170                                  new CanEncodeImageAndFormatFilter(type,
1171                                                                    formatName),
1172                                             true);
1173         } catch (IllegalArgumentException e) {
1174             return Collections.emptyIterator();
1175         }
1176 
1177         return new ImageWriterIterator(iter);
1178     }
1179 
1180     static class ImageTranscoderIterator
1181         implements Iterator<ImageTranscoder>
1182     {
1183         // Contains ImageTranscoderSpis
1184         public Iterator<ImageTranscoderSpi> iter;
1185 
1186         public ImageTranscoderIterator(Iterator<ImageTranscoderSpi> iter) {
1187             this.iter = iter;
1188         }
1189 
1190         public boolean hasNext() {
1191             return iter.hasNext();
1192         }
1193 
1194         public ImageTranscoder next() {
1195             ImageTranscoderSpi spi = null;
1196             spi = iter.next();
1197             return spi.createTranscoderInstance();
1198         }
1199 
1200         public void remove() {
1201             throw new UnsupportedOperationException();
1202         }
1203     }
1204 
1205     static class TranscoderFilter
1206         implements ServiceRegistry.Filter {
1207 
1208         String readerSpiName;
1209         String writerSpiName;
1210 
1211         public TranscoderFilter(ImageReaderSpi readerSpi,
1212                                 ImageWriterSpi writerSpi) {
1213             this.readerSpiName = readerSpi.getClass().getName();
1214             this.writerSpiName = writerSpi.getClass().getName();
1215         }
1216 
1217         public boolean filter(Object elt) {
1218             ImageTranscoderSpi spi = (ImageTranscoderSpi)elt;
1219             String readerName = spi.getReaderServiceProviderName();
1220             String writerName = spi.getWriterServiceProviderName();
1221             return (readerName.equals(readerSpiName) &&
1222                     writerName.equals(writerSpiName));
1223         }
1224     }
1225 
1226     /**
1227      * Returns an {@code Iterator} containing all currently
1228      * registered {@code ImageTranscoder}s that claim to be
1229      * able to transcode between the metadata of the given
1230      * {@code ImageReader} and {@code ImageWriter}.
1231      *
1232      * @param reader an {@code ImageReader}.
1233      * @param writer an {@code ImageWriter}.
1234      *
1235      * @return an {@code Iterator} containing
1236      * {@code ImageTranscoder}s.
1237      *
1238      * @exception IllegalArgumentException if {@code reader} or
1239      * {@code writer} is {@code null}.
1240      */
1241     public static Iterator<ImageTranscoder>
1242         getImageTranscoders(ImageReader reader, ImageWriter writer)
1243     {
1244         if (reader == null) {
1245             throw new IllegalArgumentException("reader == null!");
1246         }
1247         if (writer == null) {
1248             throw new IllegalArgumentException("writer == null!");
1249         }
1250         ImageReaderSpi readerSpi = reader.getOriginatingProvider();
1251         ImageWriterSpi writerSpi = writer.getOriginatingProvider();
1252         ServiceRegistry.Filter filter =
1253             new TranscoderFilter(readerSpi, writerSpi);
1254 
1255         Iterator<ImageTranscoderSpi> iter;
1256         // Ensure category is present
1257         try {
1258             iter = theRegistry.getServiceProviders(ImageTranscoderSpi.class,
1259                                             filter, true);
1260         } catch (IllegalArgumentException e) {
1261             return Collections.emptyIterator();
1262         }
1263         return new ImageTranscoderIterator(iter);
1264     }
1265 
1266     // All-in-one methods
1267 
1268     /**
1269      * Returns a {@code BufferedImage} as the result of decoding
1270      * a supplied {@code File} with an {@code ImageReader}
1271      * chosen automatically from among those currently registered.
1272      * The {@code File} is wrapped in an
1273      * {@code ImageInputStream}.  If no registered
1274      * {@code ImageReader} claims to be able to read the
1275      * resulting stream, {@code null} is returned.
1276      *
1277      * <p> The current cache settings from {@code getUseCache} and
1278      * {@code getCacheDirectory} will be used to control caching in the
1279      * {@code ImageInputStream} that is created.
1280      *
1281      * <p> Note that there is no {@code read} method that takes a
1282      * filename as a {@code String}; use this method instead after
1283      * creating a {@code File} from the filename.
1284      *
1285      * <p> This method does not attempt to locate
1286      * {@code ImageReader}s that can read directly from a
1287      * {@code File}; that may be accomplished using
1288      * {@code IIORegistry} and {@code ImageReaderSpi}.
1289      *
1290      * @param input a {@code File} to read from.
1291      *
1292      * @return a {@code BufferedImage} containing the decoded
1293      * contents of the input, or {@code null}.
1294      *
1295      * @exception IllegalArgumentException if {@code input} is
1296      * {@code null}.
1297      * @exception IOException if an error occurs during reading.
1298      */
1299     public static BufferedImage read(File input) throws IOException {
1300         if (input == null) {
1301             throw new IllegalArgumentException("input == null!");
1302         }
1303         if (!input.canRead()) {
1304             throw new IIOException("Can't read input file!");
1305         }
1306 
1307         ImageInputStream stream = createImageInputStream(input);
1308         if (stream == null) {
1309             throw new IIOException("Can't create an ImageInputStream!");
1310         }
1311         BufferedImage bi = read(stream);
1312         if (bi == null) {
1313             stream.close();
1314         }
1315         return bi;
1316     }
1317 
1318     /**
1319      * Returns a {@code BufferedImage} as the result of decoding
1320      * a supplied {@code InputStream} with an {@code ImageReader}
1321      * chosen automatically from among those currently registered.
1322      * The {@code InputStream} is wrapped in an
1323      * {@code ImageInputStream}.  If no registered
1324      * {@code ImageReader} claims to be able to read the
1325      * resulting stream, {@code null} is returned.
1326      *
1327      * <p> The current cache settings from {@code getUseCache} and
1328      * {@code getCacheDirectory} will be used to control caching in the
1329      * {@code ImageInputStream} that is created.
1330      *
1331      * <p> This method does not attempt to locate
1332      * {@code ImageReader}s that can read directly from an
1333      * {@code InputStream}; that may be accomplished using
1334      * {@code IIORegistry} and {@code ImageReaderSpi}.
1335      *
1336      * <p> This method <em>does not</em> close the provided
1337      * {@code InputStream} after the read operation has completed;
1338      * it is the responsibility of the caller to close the stream, if desired.
1339      *
1340      * @param input an {@code InputStream} to read from.
1341      *
1342      * @return a {@code BufferedImage} containing the decoded
1343      * contents of the input, or {@code null}.
1344      *
1345      * @exception IllegalArgumentException if {@code input} is
1346      * {@code null}.
1347      * @exception IOException if an error occurs during reading.
1348      */
1349     public static BufferedImage read(InputStream input) throws IOException {
1350         if (input == null) {
1351             throw new IllegalArgumentException("input == null!");
1352         }
1353 
1354         ImageInputStream stream = createImageInputStream(input);
1355         BufferedImage bi = read(stream);
1356         if (bi == null) {
1357             stream.close();
1358         }
1359         return bi;
1360     }
1361 
1362     /**
1363      * Returns a {@code BufferedImage} as the result of decoding
1364      * a supplied {@code URL} with an {@code ImageReader}
1365      * chosen automatically from among those currently registered.  An
1366      * {@code InputStream} is obtained from the {@code URL},
1367      * which is wrapped in an {@code ImageInputStream}.  If no
1368      * registered {@code ImageReader} claims to be able to read
1369      * the resulting stream, {@code null} is returned.
1370      *
1371      * <p> The current cache settings from {@code getUseCache} and
1372      * {@code getCacheDirectory} will be used to control caching in the
1373      * {@code ImageInputStream} that is created.
1374      *
1375      * <p> This method does not attempt to locate
1376      * {@code ImageReader}s that can read directly from a
1377      * {@code URL}; that may be accomplished using
1378      * {@code IIORegistry} and {@code ImageReaderSpi}.
1379      *
1380      * @param input a {@code URL} to read from.
1381      *
1382      * @return a {@code BufferedImage} containing the decoded
1383      * contents of the input, or {@code null}.
1384      *
1385      * @exception IllegalArgumentException if {@code input} is
1386      * {@code null}.
1387      * @exception IOException if an error occurs during reading.
1388      */
1389     public static BufferedImage read(URL input) throws IOException {
1390         if (input == null) {
1391             throw new IllegalArgumentException("input == null!");
1392         }
1393 
1394         InputStream istream = null;
1395         try {
1396             istream = input.openStream();
1397         } catch (IOException e) {
1398             throw new IIOException("Can't get input stream from URL!", e);
1399         }
1400         ImageInputStream stream = createImageInputStream(istream);
1401         BufferedImage bi;
1402         try {
1403             bi = read(stream);
1404             if (bi == null) {
1405                 stream.close();
1406             }
1407         } finally {
1408             istream.close();
1409         }
1410         return bi;
1411     }
1412 
1413     /**
1414      * Returns a {@code BufferedImage} as the result of decoding
1415      * a supplied {@code ImageInputStream} with an
1416      * {@code ImageReader} chosen automatically from among those
1417      * currently registered.  If no registered
1418      * {@code ImageReader} claims to be able to read the stream,
1419      * {@code null} is returned.
1420      *
1421      * <p> Unlike most other methods in this class, this method <em>does</em>
1422      * close the provided {@code ImageInputStream} after the read
1423      * operation has completed, unless {@code null} is returned,
1424      * in which case this method <em>does not</em> close the stream.
1425      *
1426      * @param stream an {@code ImageInputStream} to read from.
1427      *
1428      * @return a {@code BufferedImage} containing the decoded
1429      * contents of the input, or {@code null}.
1430      *
1431      * @exception IllegalArgumentException if {@code stream} is
1432      * {@code null}.
1433      * @exception IOException if an error occurs during reading.
1434      */
1435     public static BufferedImage read(ImageInputStream stream)
1436         throws IOException {
1437         if (stream == null) {
1438             throw new IllegalArgumentException("stream == null!");
1439         }
1440 
1441         Iterator<ImageReader> iter = getImageReaders(stream);
1442         if (!iter.hasNext()) {
1443             return null;
1444         }
1445 
1446         ImageReader reader = iter.next();
1447         ImageReadParam param = reader.getDefaultReadParam();
1448         reader.setInput(stream, true, true);
1449         BufferedImage bi;
1450         try {
1451             bi = reader.read(0, param);
1452         } finally {
1453             reader.dispose();
1454             stream.close();
1455         }
1456         return bi;
1457     }
1458 
1459     /**
1460      * Writes an image using the an arbitrary {@code ImageWriter}
1461      * that supports the given format to an
1462      * {@code ImageOutputStream}.  The image is written to the
1463      * {@code ImageOutputStream} starting at the current stream
1464      * pointer, overwriting existing stream data from that point
1465      * forward, if present.
1466      *
1467      * <p> This method <em>does not</em> close the provided
1468      * {@code ImageOutputStream} after the write operation has completed;
1469      * it is the responsibility of the caller to close the stream, if desired.
1470      *
1471      * @param im a {@code RenderedImage} to be written.
1472      * @param formatName a {@code String} containing the informal
1473      * name of the format.
1474      * @param output an {@code ImageOutputStream} to be written to.
1475      *
1476      * @return {@code false} if no appropriate writer is found.
1477      *
1478      * @exception IllegalArgumentException if any parameter is
1479      * {@code null}.
1480      * @exception IOException if an error occurs during writing.
1481      */
1482     public static boolean write(RenderedImage im,
1483                                 String formatName,
1484                                 ImageOutputStream output) throws IOException {
1485         if (im == null) {
1486             throw new IllegalArgumentException("im == null!");
1487         }
1488         if (formatName == null) {
1489             throw new IllegalArgumentException("formatName == null!");
1490         }
1491         if (output == null) {
1492             throw new IllegalArgumentException("output == null!");
1493         }
1494 
1495         return doWrite(im, getWriter(im, formatName), output);
1496     }
1497 
1498     /**
1499      * Writes an image using an arbitrary {@code ImageWriter}
1500      * that supports the given format to a {@code File}.  If
1501      * there is already a {@code File} present, its contents are
1502      * discarded.
1503      *
1504      * @param im a {@code RenderedImage} to be written.
1505      * @param formatName a {@code String} containing the informal
1506      * name of the format.
1507      * @param output a {@code File} to be written to.
1508      *
1509      * @return {@code false} if no appropriate writer is found.
1510      *
1511      * @exception IllegalArgumentException if any parameter is
1512      * {@code null}.
1513      * @exception IOException if an error occurs during writing.
1514      */
1515     public static boolean write(RenderedImage im,
1516                                 String formatName,
1517                                 File output) throws IOException {
1518         if (output == null) {
1519             throw new IllegalArgumentException("output == null!");
1520         }
1521         ImageOutputStream stream = null;
1522 
1523         ImageWriter writer = getWriter(im, formatName);
1524         if (writer == null) {
1525             /* Do not make changes in the file system if we have
1526              * no appropriate writer.
1527              */
1528             return false;
1529         }
1530 
1531         try {
1532             output.delete();
1533             stream = createImageOutputStream(output);
1534         } catch (IOException e) {
1535             throw new IIOException("Can't create output stream!", e);
1536         }
1537 
1538         try {
1539             return doWrite(im, writer, stream);
1540         } finally {
1541             stream.close();
1542         }
1543     }
1544 
1545     /**
1546      * Writes an image using an arbitrary {@code ImageWriter}
1547      * that supports the given format to an {@code OutputStream}.
1548      *
1549      * <p> This method <em>does not</em> close the provided
1550      * {@code OutputStream} after the write operation has completed;
1551      * it is the responsibility of the caller to close the stream, if desired.
1552      *
1553      * <p> The current cache settings from {@code getUseCache} and
1554      * {@code getCacheDirectory} will be used to control caching.
1555      *
1556      * @param im a {@code RenderedImage} to be written.
1557      * @param formatName a {@code String} containing the informal
1558      * name of the format.
1559      * @param output an {@code OutputStream} to be written to.
1560      *
1561      * @return {@code false} if no appropriate writer is found.
1562      *
1563      * @exception IllegalArgumentException if any parameter is
1564      * {@code null}.
1565      * @exception IOException if an error occurs during writing.
1566      */
1567     public static boolean write(RenderedImage im,
1568                                 String formatName,
1569                                 OutputStream output) throws IOException {
1570         if (output == null) {
1571             throw new IllegalArgumentException("output == null!");
1572         }
1573         ImageOutputStream stream = null;
1574         try {
1575             stream = createImageOutputStream(output);
1576         } catch (IOException e) {
1577             throw new IIOException("Can't create output stream!", e);
1578         }
1579 
1580         try {
1581             return doWrite(im, getWriter(im, formatName), stream);
1582         } finally {
1583             stream.close();
1584         }
1585     }
1586 
1587     /**
1588      * Returns {@code ImageWriter} instance according to given
1589      * rendered image and image format or {@code null} if there
1590      * is no appropriate writer.
1591      */
1592     private static ImageWriter getWriter(RenderedImage im,
1593                                          String formatName) {
1594         ImageTypeSpecifier type =
1595             ImageTypeSpecifier.createFromRenderedImage(im);
1596         Iterator<ImageWriter> iter = getImageWriters(type, formatName);
1597 
1598         if (iter.hasNext()) {
1599             return iter.next();
1600         } else {
1601             return null;
1602         }
1603     }
1604 
1605     /**
1606      * Writes image to output stream  using given image writer.
1607      */
1608     private static boolean doWrite(RenderedImage im, ImageWriter writer,
1609                                  ImageOutputStream output) throws IOException {
1610         if (writer == null) {
1611             return false;
1612         }
1613         writer.setOutput(output);
1614         try {
1615             writer.write(im);
1616         } finally {
1617             writer.dispose();
1618             output.flush();
1619         }
1620         return true;
1621     }
1622 }