Content of file ReadOp.java.html

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>ReadOp.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">Mistral Operations</a> &gt; <a href="../index.html" class="el_bundle">image-core</a> &gt; <a href="index.source.html" class="el_package">it.tidalwave.image.op</a> &gt; <span class="el_source">ReadOp.java</span></div><h1>ReadOp.java</h1><pre class="source lang-java linenums">/*
 * *********************************************************************************************************************
 *
 * Mistral: open source imaging engine
 * http://tidalwave.it/projects/mistral
 *
 * Copyright (C) 2003 - 2023 by Tidalwave s.a.s. (http://tidalwave.it)
 *
 * *********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * *********************************************************************************************************************
 *
 * git clone https://bitbucket.org/tidalwave/mistral-src
 * git clone https://github.com/tidalwave-it/mistral-src
 *
 * *********************************************************************************************************************
 */
package it.tidalwave.image.op;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import java.net.URL;
import java.awt.image.BufferedImage;
import it.tidalwave.util.Parameters;
import it.tidalwave.image.EditableImage;
import it.tidalwave.image.java2d.ImplementationFactoryJ2D;
import it.tidalwave.image.java2d.Java2DUtils;
import it.tidalwave.image.metadata.Directory;
import it.tidalwave.image.metadata.EXIF;
import it.tidalwave.image.metadata.IPTC;
import it.tidalwave.image.metadata.MakerNote;
import it.tidalwave.image.metadata.TIFF;
import it.tidalwave.image.metadata.XMP;
import it.tidalwave.image.metadata.loader.DirectoryLoader;
import it.tidalwave.image.metadata.loader.DrewMetadataLoader;
import it.tidalwave.image.metadata.loader.JpegDrewMetadataLoader;
import it.tidalwave.image.metadata.loader.MetadataLoader;
import it.tidalwave.image.metadata.loader.RAWMetadataLoader;
import it.tidalwave.image.metadata.loader.TIFFMetadataLoader;
import it.tidalwave.image.op.impl.FileChannelImageInputStream;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import static it.tidalwave.util.FunctionalCheckedExceptionWrappers.*;

/***********************************************************************************************************************
 *
 * @author Fabrizio Giudici
 *
 **********************************************************************************************************************/
<span class="pc" id="L82">@ToString(of = {&quot;input&quot;, &quot;imageIndex&quot;, &quot;thumbnailIndex&quot;}) @Slf4j</span>
public class ReadOp extends Operation
  {
    /*******************************************************************************************************************
     *
     * A marker interface for allowable options for {@link ReadOp} constructor.
     *
     ******************************************************************************************************************/
    public static interface Options
      {
      }

    /*******************************************************************************************************************
     *
     * A container of plugin names that should not be used to load an image.
     *
     ******************************************************************************************************************/
<span class="fc" id="L99">    @ToString</span>
    public static class PluginBlackList implements Options
      {
<span class="fc" id="L102">        public static final PluginBlackList DEFAULT = new PluginBlackList(</span>
                // WRONG! These are the good ones! But keep for compability until you test everything.
                &quot;com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader&quot;,
                &quot;com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageWriter&quot;,
                //
                // Considered harmful on Mac OS X. For instance, can only deal with Mac OS X endianness.
                //
                &quot;com.sun.imageio.plugins.tiff.TIFFImageReader&quot;
        );

        private final Set&lt;String&gt; plugins;

        public PluginBlackList (@Nonnull final String... plugins)
<span class="fc" id="L115">          {</span>
<span class="fc" id="L116">            this.plugins = new HashSet&lt;&gt;(Arrays.asList(plugins));</span>
<span class="fc" id="L117">          }</span>

        public boolean contains (@Nonnull final String pluginName)
          {
<span class="fc" id="L121">            return plugins.contains(pluginName);</span>
          }
      }

<span class="fc" id="L125">    @Getter @Nonnull</span>
    private final Object input;

<span class="fc" id="L128">    @Getter @Nonnull</span>
    private final PluginBlackList pluginBlackList;

<span class="nc" id="L131">    @Getter @Nonnull</span>
    private final Type type;

<span class="fc" id="L134">    @Getter @Nonnegative</span>
    private final int imageIndex;

<span class="nc" id="L137">    @Getter @Nonnegative</span>
    private final int thumbnailIndex;

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    private abstract static class Reader
      {
        public static EditableImage read (@Nonnull final Object input,
                                          @Nonnull final Reader reader,
                                          @Nonnull final PluginBlackList pluginBlackList)
                throws IOException
          {
<span class="fc" id="L151">            return reader.run(input, pluginBlackList);</span>
          }

        @Nonnull
        private EditableImage run (@Nonnull final Object input,
                                   @Nonnull final PluginBlackList pluginBlackList)
                throws IOException
          {
<span class="fc" id="L159">            Objects.requireNonNull(input, &quot;input&quot;);</span>

<span class="pc bpc" id="L161" title="1 of 2 branches missed.">            if (input instanceof Path)</span>
              {
<span class="fc" id="L163">                final var imageReader = createImageReader(((Path)input), pluginBlackList);</span>
<span class="fc" id="L164">                final var editableImage = read(imageReader);</span>
<span class="fc" id="L165">                setProperties(editableImage, imageReader);</span>
<span class="fc" id="L166">                imageReader.dispose();</span>
<span class="fc" id="L167">                return editableImage;</span>
              }

<span class="nc bnc" id="L170" title="All 2 branches missed.">            else if (input instanceof File)</span>
              {
<span class="nc" id="L172">                final var imageReader = createImageReader(((File)input).toPath(), pluginBlackList);</span>
<span class="nc" id="L173">                final var editableImage = read(imageReader);</span>
<span class="nc" id="L174">                setProperties(editableImage, imageReader);</span>
<span class="nc" id="L175">                imageReader.dispose();</span>
<span class="nc" id="L176">                return editableImage;</span>
              }

<span class="nc bnc" id="L179" title="All 2 branches missed.">            else if (input instanceof URL)</span>
              {
<span class="nc" id="L181">                final var imageReader = createImageReader((URL)input, pluginBlackList);</span>
<span class="nc" id="L182">                final var editableImage = read(imageReader);</span>
<span class="nc" id="L183">                setProperties(editableImage, imageReader);</span>
<span class="nc" id="L184">                imageReader.dispose();</span>
<span class="nc" id="L185">                return editableImage;</span>
              }

<span class="nc bnc" id="L188" title="All 2 branches missed.">            else if (input instanceof InputStream)</span>
              {
<span class="nc" id="L190">                final var imageReader = createImageReader((InputStream)input, pluginBlackList);</span>
<span class="nc" id="L191">                final var editableImage = read(imageReader);</span>
<span class="nc" id="L192">                setProperties(editableImage, imageReader);</span>
<span class="nc" id="L193">                imageReader.dispose();</span>
<span class="nc" id="L194">                return editableImage;</span>
              }

<span class="nc bnc" id="L197" title="All 2 branches missed.">            else if (input instanceof byte[])</span>
              {
<span class="nc" id="L199">                final var imageReader = createImageReader(new ByteArrayInputStream((byte[])input), pluginBlackList);</span>
<span class="nc" id="L200">                final var editableImage = read(imageReader);</span>
<span class="nc" id="L201">                setProperties(editableImage, imageReader);</span>
<span class="nc" id="L202">                imageReader.dispose();</span>
<span class="nc" id="L203">                return editableImage;</span>
              }

<span class="nc bnc" id="L206" title="All 2 branches missed.">            else if (input instanceof ImageReader)</span>
              {
<span class="nc" id="L208">                final var editableImage = read((ImageReader)input);</span>
<span class="nc" id="L209">                setProperties(editableImage, (ImageReader)input);</span>
<span class="nc" id="L210">                return editableImage;</span>
                // don't dispose the ImageReader in this case
              }

            else
              {
<span class="nc" id="L216">                throw new IllegalArgumentException(&quot;Bad input type: &quot; + input.getClass());</span>
              }
          }

        protected abstract EditableImage read (@Nonnull ImageReader imageReader)
                throws IOException;

        private void setProperties (@Nonnull final EditableImage image, @Nonnull final ImageReader imageReader)
                throws IOException
          {
<span class="fc" id="L226">            image.setAttribute(EditableImage.PROP_FORMAT, imageReader.getFormatName());</span>
<span class="fc" id="L227">            image.setAttribute(EditableImage.PROP_MIME_TYPE, imageReader.getOriginatingProvider().getMIMETypes()[0]);</span>
<span class="fc" id="L228">          }</span>
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
<span class="fc" id="L235">    public static enum Type implements Options</span>
      {
        /***************************************************************************************************************
         *
         *
         **************************************************************************************************************/
<span class="fc" id="L241">        IMAGE</span>
                  {
                    @Override @Nonnull
                    protected EditableImage read (@Nonnull final ReadOp readOp)
                            throws IOException
                      {
<span class="fc" id="L247">                        final var input = readOp.getInput();</span>
<span class="fc" id="L248">                        final var imageIndex = readOp.getImageIndex();</span>
<span class="fc" id="L249">                        log.info(&quot;read({}, {})&quot;, input, imageIndex);</span>

<span class="fc" id="L251">                        return Reader.read(input, new Reader()</span>
<span class="fc" id="L252">                          {</span>
                            @Override
                            protected EditableImage read (final ImageReader imageReader)
                                    throws IOException
                              {
<span class="fc" id="L257">                                final var time = Instant.now();</span>
<span class="fc" id="L258">                                final var image = imageReader.read(imageIndex);</span>
<span class="fc" id="L259">                                final var editableImage = create(image);</span>
<span class="fc" id="L260">                                loadMetadata(editableImage, imageReader, imageIndex);</span>
<span class="fc" id="L261">                                Java2DUtils.logImage(log, &quot;&gt;&gt;&gt;&gt; Loaded image: &quot;, image);</span>
<span class="fc" id="L262">                                editableImage.getInnerProperty(AccessorOp.class).setLatestOperationDuration(Duration.between(Instant.now(), time));</span>
<span class="fc" id="L263">                                return editableImage;</span>
                              }
<span class="fc" id="L265">                          }, readOp.getPluginBlackList());</span>
                      }
                  },

        /***************************************************************************************************************
         *
         *
         **************************************************************************************************************/
<span class="fc" id="L273">        THUMBNAIL</span>
                  {
                    @Override @Nonnull
                    protected EditableImage read (@Nonnull final ReadOp readOp)
                            throws IOException
                      {
<span class="nc" id="L279">                        final var input = readOp.getInput();</span>
<span class="nc" id="L280">                        final var imageIndex = readOp.getImageIndex();</span>
<span class="nc" id="L281">                        final var thumbnailIndex = readOp.getThumbnailIndex();</span>
<span class="nc" id="L282">                        log.info(&quot;read({}, {}, {})&quot;, input, imageIndex, thumbnailIndex);</span>

<span class="nc" id="L284">                        return Reader.read(input, new Reader()</span>
<span class="nc" id="L285">                          {</span>
                            @Nonnull
                            @Override
                            protected EditableImage read (@Nonnull final ImageReader imageReader)
                                    throws IOException
                              {
<span class="nc" id="L291">                                final var time = Instant.now();</span>
<span class="nc" id="L292">                                return create(imageReader.readThumbnail(imageIndex, thumbnailIndex),</span>
<span class="nc" id="L293">                                              Duration.between(Instant.now(), time));</span>
                              }
<span class="nc" id="L295">                          }, readOp.getPluginBlackList());</span>
                      }
                  },

        /***************************************************************************************************************
         *
         *
         **************************************************************************************************************/
<span class="fc" id="L303">        METADATA</span>
                  {
                    @Override @Nonnull
                    protected EditableImage read (@Nonnull final ReadOp readOp)
                            throws IOException
                      {
<span class="fc" id="L309">                        final var input = readOp.getInput();</span>
<span class="fc" id="L310">                        final var imageIndex = readOp.getImageIndex();</span>
<span class="fc" id="L311">                        log.info(&quot;read({}, {})&quot;, input, imageIndex);</span>

<span class="fc" id="L313">                        return Reader.read(input, new Reader()</span>
<span class="fc" id="L314">                          {</span>
                            @Nonnull
                            @Override
                            protected EditableImage read (@Nonnull final ImageReader imageReader)
                              {
<span class="fc" id="L319">                                final var editableImage = new EditableImage(null);</span>
<span class="fc" id="L320">                                loadMetadata(editableImage, imageReader, imageIndex);</span>
<span class="fc" id="L321">                                return editableImage;</span>
                              }
<span class="fc" id="L323">                          }, readOp.getPluginBlackList());</span>
                      }
                  };

        /***************************************************************************************************************
         *
         *
         **************************************************************************************************************/
        @Nonnull
        protected abstract EditableImage read (@Nonnull ReadOp readOp)
                throws IOException;

        /***************************************************************************************************************
         *
         *
         **************************************************************************************************************/
        @Nonnull
        private static EditableImage create (@Nonnull final BufferedImage image)
          {
<span class="fc" id="L342">            return new EditableImage(ImplementationFactoryJ2D.getDefault().createImageModel(image));</span>
          }

        /***************************************************************************************************************
         *
         *
         **************************************************************************************************************/
        @Nonnull
        private static EditableImage create (@Nonnull final BufferedImage image,
                                             @Nonnull final Duration latestOperationDuration)
          {
<span class="nc" id="L353">            final var editableImage = create(image);</span>
<span class="nc" id="L354">            editableImage.getInnerProperty(AccessorOp.class).setLatestOperationDuration(latestOperationDuration);</span>
<span class="nc" id="L355">            return editableImage;</span>
          }
      }

    /*******************************************************************************************************************
     *
     * @param  input          the input (an ImageReader or a Path)
     *
     ******************************************************************************************************************/
    public ReadOp (@Nonnull final Object input)
      {
<span class="fc" id="L366">        this(input, 0, 0);</span>
<span class="fc" id="L367">      }</span>

    /*******************************************************************************************************************
     *
     * @param  input          the input (an ImageReader or a Path)
     * @param  type           the type of read
     *
     ******************************************************************************************************************/
    public ReadOp (@Nonnull final Object input, @Nonnull final Options... options)
      {
<span class="fc" id="L377">        this(input, 0, 0, options);</span>
<span class="fc" id="L378">      }</span>

    /*******************************************************************************************************************
     *
     * @param  input          the input (an ImageReader or a Path)
     * @param  type           the type of read
     * @param  imageIndex     the index of the image to read
     *
     ******************************************************************************************************************/
    public ReadOp (@Nonnull final Object input, @Nonnegative final int imageIndex, @Nonnull final Options... options)
      {
<span class="nc" id="L389">        this(input, imageIndex, 0, options);</span>
<span class="nc" id="L390">      }</span>

    /*******************************************************************************************************************
     *
     * @param  input          the input (an ImageReader or a Path)
     * @param  type           the type of read
     * @param  imageIndex     the index of the image to read
     * @param  thumbnailIndex the index of the thumbnail to read
     *
     ******************************************************************************************************************/
    public ReadOp (@Nonnull final Object input,
                   @Nonnegative final int imageIndex,
                   @Nonnegative final int thumbnailIndex,
                   @Nonnull final Options... options)
<span class="fc" id="L404">      {</span>
<span class="fc" id="L405">        this.input = input;</span>
<span class="fc" id="L406">        this.type = Parameters.find(Type.class, Type.IMAGE, options);</span>
<span class="fc" id="L407">        this.pluginBlackList = Parameters.find(PluginBlackList.class, PluginBlackList.DEFAULT, options);</span>
<span class="fc" id="L408">        this.imageIndex = imageIndex;</span>
<span class="fc" id="L409">        this.thumbnailIndex = thumbnailIndex;</span>
<span class="fc" id="L410">        log.trace(&quot;ReadOp({}, {}, {}, {})&quot;, input, imageIndex, thumbnailIndex, options);</span>
<span class="fc" id="L411">      }</span>

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    public EditableImage execute()
            throws IOException
      {
<span class="fc" id="L421">        return type.read(this);</span>
      }

    /*******************************************************************************************************************
     *
     * Creates an ImageReader for the given Path. Using a Path as argument is
     * important for photos that are stored in multiple files (e.g. Canon .CRW format).
     * This method supports files GZIP compression (but multiple file formats such
     * as .CRW aren't supported in this case).
     *
     * @throws IOException  if it is not possible
     *
     ******************************************************************************************************************/
    @Nonnull
    public static ImageReader createImageReader (@Nonnull final Path file,
                                                 @Nonnull final PluginBlackList pluginBlackList)
            throws IOException
      {
<span class="fc" id="L439">        log.trace(&quot;createImageReader({}, {})&quot;, file, pluginBlackList);</span>

<span class="fc bfc" id="L441" title="All 2 branches covered.">        if (!Files.exists(file))</span>
          {
<span class="fc" id="L443">            throw new FileNotFoundException(file.toAbsolutePath().toString());</span>
          }

<span class="pc bpc" id="L446" title="1 of 2 branches missed.">        if (!Files.isReadable(file))</span>
          {
<span class="nc" id="L448">            throw new IOException(&quot;Cannot read &quot; + file.toAbsolutePath());</span>
          }

<span class="fc" id="L451">        var fileName = file.getFileName().toString();</span>
<span class="fc" id="L452">        var suffix = &quot;&quot;;</span>
<span class="fc" id="L453">        final var gzipCompression = fileName.toLowerCase().endsWith(&quot;.gz&quot;);</span>

<span class="pc bpc" id="L455" title="1 of 2 branches missed.">        if (gzipCompression)</span>
          {
<span class="nc" id="L457">            fileName = fileName.substring(0, fileName.length() - 3);</span>
          }

<span class="fc" id="L460">        final var i = fileName.lastIndexOf('.');</span>

<span class="pc bpc" id="L462" title="1 of 2 branches missed.">        if (i &gt; 0)</span>
          {
<span class="fc" id="L464">            suffix = fileName.substring(i + 1);</span>
          }

<span class="fc" id="L467">        log.trace(&quot;&gt;&gt;&gt;&gt; suffix is {}&quot;, suffix);</span>
<span class="fc" id="L468">        ImageInputStream imageInputStream = null;</span>

        //
        // For reasons stated in the javadoc comment of this method, it's better
        // to create the ImageInputStream by passing a Path.
        //
<span class="pc bpc" id="L474" title="1 of 2 branches missed.">        if (!gzipCompression)</span>
          {
<span class="fc" id="L476">            imageInputStream = new FileChannelImageInputStream(file.toFile());</span>
//                imageInputStream = ImageIO.createImageInputStream(file);
          }
        //
        // This will not work with multiple-file formats such as Canon .CRW.
        //
        else
          {
<span class="nc" id="L484">            final InputStream inputStream = new GZIPInputStream(Files.newInputStream(file));</span>
<span class="nc" id="L485">            imageInputStream = ImageIO.createImageInputStream(inputStream);</span>
          }

<span class="fc" id="L488">        return createImageReader(imageInputStream, gzipCompression, suffix, pluginBlackList);</span>
      }

    /*******************************************************************************************************************
     *
     * Returns a valid &lt;code&gt;ImageReader&lt;/code&gt; for the given URL, or throw an
     * &lt;code&gt;IOException&lt;/code&gt; if it's not possible.
     *
     * @param   url          the URL
     * @throws IOException  if it is not possible
     *
     ******************************************************************************************************************/
    @Nonnull
    public static ImageReader createImageReader (@Nonnull final URL url, @Nonnull final PluginBlackList pluginBlackList)
            throws IOException
      {
<span class="nc" id="L504">        log.trace(&quot;createImageReader({})&quot;, url);</span>

<span class="nc" id="L506">        final var fileName = url.getPath();</span>
<span class="nc" id="L507">        var suffix = &quot;&quot;;</span>
<span class="nc" id="L508">        final var gzipCompression = fileName.toLowerCase().endsWith(&quot;.gz&quot;);</span>

<span class="nc" id="L510">        final var i = fileName.lastIndexOf('.');</span>

<span class="nc bnc" id="L512" title="All 2 branches missed.">        if (i &gt; 0)</span>
          {
<span class="nc" id="L514">            suffix = fileName.substring(i + 1);</span>
          }

<span class="nc" id="L517">        log.trace(&quot;&gt;&gt;&gt;&gt; suffix is {}&quot;, suffix);</span>
<span class="nc" id="L518">        ImageInputStream imageInputStream = null;</span>
        //
        // This will not work with multiple-file formats such as Canon .CRW.
        //
<span class="nc bnc" id="L522" title="All 2 branches missed.">        final var inputStream = gzipCompression ? new GZIPInputStream(url.openStream()) : url.openStream();</span>
<span class="nc" id="L523">        imageInputStream = ImageIO.createImageInputStream(inputStream);</span>
<span class="nc" id="L524">        return createImageReader(imageInputStream, gzipCompression, suffix, pluginBlackList);</span>
      }

    /*******************************************************************************************************************
     *
     * Returns a valid &lt;code&gt;ImageReader&lt;/code&gt; for the given stream, or throw 
     * an &lt;code&gt;IOException&lt;/code&gt; if it's not possible.
     *
     * @param   inputStream       the input stream
     * @throws IOException       if it is not possible
     *
     ******************************************************************************************************************/
    @Nonnull
    private static ImageReader createImageReader (@Nonnull final InputStream inputStream,
                                                  @Nonnull final PluginBlackList pluginBlackList)
            throws IOException
      {
<span class="nc" id="L541">        log.info(&quot;createImageReader({})&quot;, inputStream);</span>
<span class="nc" id="L542">        final var imageInputStream = ImageIO.createImageInputStream(inputStream);</span>
<span class="nc" id="L543">        final var iterator = ImageIO.getImageReaders(imageInputStream);</span>
<span class="nc" id="L544">        return createImageReader(imageInputStream, iterator, pluginBlackList);</span>
      }

    /*******************************************************************************************************************
     *
     * Returns a valid &lt;code&gt;ImageReader&lt;/code&gt; for the given stream, or throw 
     * an &lt;code&gt;IOException&lt;/code&gt; if it's not possible.
     *
     * @param   imageInputStream  the input stream
     * @param   gzipCompression   if the stream is compressed
     * @param   suffix            the file format suffix (e.g. jpg, tiff,...)
     * @throws IOException       if it is not possible
     *
     ******************************************************************************************************************/
    private static ImageReader createImageReader (@Nonnull final ImageInputStream imageInputStream,
                                                  final boolean gzipCompression,
                                                  @Nonnull final String suffix,
                                                  @Nonnull final PluginBlackList pluginBlackList)
            throws IOException
      {
<span class="fc" id="L564">        log.info(&quot;createImageReader({}, {}, {})&quot;, imageInputStream, gzipCompression, suffix);</span>
//        logger.finest(&quot;&gt;&gt;&gt;&gt; Suffixes: &quot; + Arrays.asList(ImageIO.getReaderFileSuffixes()));
<span class="fc" id="L566">        final var iterator = ImageIO.getImageReaders(imageInputStream);</span>
<span class="fc" id="L567">        return createImageReader(imageInputStream, iterator, pluginBlackList);</span>
      }

    /*******************************************************************************************************************
     *
     * Returns a valid &lt;code&gt;ImageReader&lt;/code&gt; for the given stream, or throw 
     * an &lt;code&gt;IOException&lt;/code&gt; if it's not possible.
     *
     * @param   imageInputStream  the input stream
     * @throws IOException       if it is not possible
     *
     ******************************************************************************************************************/
    @Nonnull
    private static ImageReader createImageReader (@Nonnull final ImageInputStream imageInputStream,
                                                  @Nonnull final Iterator&lt;? extends ImageReader&gt; iterator,
                                                  @Nonnull final PluginBlackList pluginBlackList)
            throws IOException
      {
<span class="fc" id="L585">        log.info(&quot;createImageReader({}, {})&quot;, imageInputStream, iterator);</span>

        // See http://bluemarine.tidalwave.it/issues/browse/MST-137
<span class="fc" id="L588">        final List&lt;ImageReader&gt; readers = new ArrayList&lt;&gt;();</span>
<span class="fc" id="L589">        final List&lt;ImageReader&gt; tiffReaders = new ArrayList&lt;&gt;();</span>

<span class="pc bpc" id="L591" title="1 of 2 branches missed.">        if (!iterator.hasNext())</span>
          {
<span class="nc" id="L593">            log.warn(&quot;Iterator is empty&quot;);</span>
          }

<span class="fc bfc" id="L596" title="All 2 branches covered.">        while (iterator.hasNext())</span>
          {
<span class="fc" id="L598">            final var reader = iterator.next();</span>
<span class="fc" id="L599">            final var pluginClassName = reader.getOriginatingProvider().getPluginClassName();</span>

<span class="pc bpc" id="L601" title="1 of 2 branches missed.">            if (reader != null)</span>
              {
<span class="fc" id="L603">                log.trace(&quot;&gt;&gt;&gt;&gt; pre-testing reader: {}, vendor: {}&quot;,</span>
                          reader,
<span class="fc" id="L605">                          reader.getOriginatingProvider().getVendorName());</span>

<span class="pc bpc" id="L607" title="1 of 2 branches missed.">                if (pluginBlackList.contains(pluginClassName))</span>
                  {
<span class="nc" id="L609">                    log.trace(&quot;&gt;&gt;&gt;&gt; {} discarded because it's in the black list&quot;, reader);</span>
                  }
<span class="pc bpc" id="L611" title="1 of 2 branches missed.">                else if (!pluginClassName.contains(&quot;TIFF&quot;)) // TODO: maybe is it better to test for supported extension or mime?</span>
maybe is it better to test for supported extension or mime?
{ <span class="fc" id="L613"> readers.add(reader);</span> } else { <span class="nc" id="L617"> tiffReaders.add(reader);</span> } } <span class="fc" id="L620"> }</span> <span class="fc" id="L622"> readers.addAll(tiffReaders);</span> <span class="pc bpc" id="L624" title="1 of 2 branches missed."> for (final var reader : readers)</span> { <span class="fc" id="L626"> log.trace(&quot;&gt;&gt;&gt;&gt; testing reader: {}, vendor: {}&quot;, reader, reader.getOriginatingProvider().getVendorName());</span> <span class="pc bpc" id="L628" title="1 of 2 branches missed."> if (!reader.getOriginatingProvider().canDecodeInput(imageInputStream))</span> { <span class="nc" id="L630"> log.trace(&quot;&gt;&gt;&gt;&gt; discarded because it can't decode the input&quot;);</span> <span class="nc" id="L631"> continue;</span> } <span class="fc" id="L634"> reader.setInput(imageInputStream);</span> <span class="fc" id="L635"> log.trace(&quot;&gt;&gt;&gt;&gt; returning reader: {}&quot;, reader);</span> <span class="fc" id="L636"> return reader;</span> } <span class="nc" id="L639"> throw new IOException(&quot;No ImageReader&quot;);</span> } /******************************************************************************************************************* * * ******************************************************************************************************************/ private static void loadMetadata (final @Nonnull EditableImage image, final @Nonnull ImageReader reader, final @Nonnegative int imageIndex) { <span class="fc" id="L650"> log.trace(&quot;loadMetadata({}, {})&quot;, reader, imageIndex);</span> <span class="fc" id="L651"> var accessor = image.getInnerProperty(AccessorOp.class);</span> <span class="fc" id="L652"> var metadataMapByClass = accessor.getMetadataMapByClass();</span> final IIOMetadata iioMetadata; try { <span class="fc" id="L657"> iioMetadata = reader.getImageMetadata(imageIndex);</span> } <span class="nc" id="L659"> catch (Exception e)</span> { <span class="nc" id="L661"> throw new RuntimeException(e);</span> /* if (&quot;ICC APP2 encountered without prior JFIF!&quot;.equals(e.getMessage()) &amp;&amp; (workaroundBM25 != null)) { try { var tiff = new TIFF(); var exif = new EXIF(); var iptc = new IPTC(); var xmp = new XMP(); workaroundBM25.loadExifAndIptcFromJpeg(reader, tiff, exif, iptc, xmp); metadataMapByClass.put(TIFF.class, List.of(tiff)); metadataMapByClass.put(EXIF.class, List.of(exif)); metadataMapByClass.put(IPTC.class, List.of(iptc)); metadataMapByClass.put(XMP.class, List.of(xmp)); } catch (Exception e1) { log.error(&quot;Cannot load EXIF/IPTC metadata: &quot;, e1); } } else { log.error(&quot;Cannot load EXIF/IPTC metadata: &quot;, e); } return; */ <span class="fc" id="L689"> }</span> <span class="pc bpc" id="L691" title="1 of 2 branches missed."> if (iioMetadata == null)</span> { <span class="nc" id="L693"> log.warn(&quot;&gt;&gt;&gt;&gt; null imagemetadata&quot;);</span> <span class="nc" id="L694"> return;</span> } <span class="fc" id="L697"> accessor.setIIOMetadata(iioMetadata);</span> <span class="fc" id="L699"> var iioMetadataClass = iioMetadata.getClass();</span> final MetadataLoader metadataLoader; <span class="pc bpc" id="L702" title="1 of 2 branches missed."> if (isSubClass(iioMetadataClass, &quot;com.sun.imageio.plugins.jpeg.JPEGMetadata&quot;))</span> { <span class="fc" id="L704"> metadataLoader = new JpegDrewMetadataLoader(reader);</span> } <span class="nc bnc" id="L706" title="All 2 branches missed."> else if (isSubClass(iioMetadataClass, &quot;com.sun.media.imageio.plugins.tiff.TIFFImageMetadata&quot;))</span> { <span class="nc" id="L708"> metadataLoader = new TIFFMetadataLoader();</span> } <span class="nc bnc" id="L710" title="All 2 branches missed."> else if (isSubClass(iioMetadataClass, &quot;it.tidalwave.imageio.raw.RAWMetadataSupport&quot;))</span> { <span class="nc" id="L712"> metadataLoader = new RAWMetadataLoader();</span> } else { <span class="nc" id="L716"> metadataLoader = new DrewMetadataLoader();</span> } <span class="fc" id="L719"> log.debug(&quot;&gt;&gt;&gt;&gt; iioMetadata class: {}, using metadata loader: {}&quot;, iioMetadataClass, metadataLoader.getClass());</span> try { <span class="fc" id="L723"> List.of(TIFF.class, EXIF.class, MakerNote.class, IPTC.class, XMP.class)</span> <span class="fc" id="L724"> .forEach(_c(t -&gt; metadataMapByClass.put(t, loadDirectories(iioMetadata, metadataLoader, t))));</span> } <span class="nc" id="L726"> catch (Exception e)</span> { <span class="nc" id="L728"> log.error(&quot;loadMetadata()&quot;, e);</span> <span class="fc" id="L729"> }</span> <span class="fc" id="L730"> }</span> /******************************************************************************************************************* * * Loads directories of metadata by means of a loader. * * @param iioMetadata * @param metadataLoader the metadata loader * @param directoryClass the type of the directory to laod * @return the loaded items * ******************************************************************************************************************/ private static &lt;T extends Directory&gt; List&lt;Directory&gt; loadDirectories (final @Nonnull IIOMetadata iioMetadata, final @Nonnull MetadataLoader metadataLoader, final @Nonnull Class&lt;T&gt; directoryClass) { <span class="fc" id="L746"> log.debug(&quot;loadDirectories({}, {}, {})&quot;, iioMetadata, metadataLoader, directoryClass);</span> <span class="fc" id="L747"> final var items = new ArrayList&lt;Directory&gt;();</span> <span class="fc" id="L748"> Optional&lt;DirectoryLoader&gt; loader = Optional.empty();</span> // FIXME: get rid of the if chain <span class="fc bfc" id="L751" title="All 2 branches covered."> if (TIFF.class.equals(directoryClass))</span> { <span class="fc" id="L753"> loader = metadataLoader.getTiffLoader(iioMetadata);</span> } <span class="fc bfc" id="L755" title="All 2 branches covered."> else if (EXIF.class.equals(directoryClass))</span> { <span class="fc" id="L757"> loader = metadataLoader.getExifLoader(iioMetadata);</span> } <span class="fc bfc" id="L759" title="All 2 branches covered."> else if (IPTC.class.equals(directoryClass))</span> { <span class="fc" id="L761"> loader = metadataLoader.getIptcLoader(iioMetadata);</span> } <span class="fc bfc" id="L763" title="All 2 branches covered."> else if (XMP.class.equals(directoryClass))</span> { <span class="fc" id="L765"> loader = metadataLoader.getXmpLoader(iioMetadata);</span> } <span class="pc bpc" id="L767" title="1 of 2 branches missed."> else if (MakerNote.class.equals(directoryClass))</span> { <span class="fc" id="L769"> loader = metadataLoader.getMakerNoteLoader(iioMetadata);</span> } <span class="fc" id="L772"> loader.ifPresentOrElse(_c(a -&gt; items.addAll(loadDirectories(a, directoryClass))),</span> <span class="fc" id="L773"> () -&gt; log.warn(&quot;No loader for {}&quot;, directoryClass));</span> <span class="fc" id="L774"> return items;</span> } /******************************************************************************************************************* * ******************************************************************************************************************/ private static List&lt;Directory&gt; loadDirectories (@Nonnull DirectoryLoader loader, final @Nonnull Class&lt;? extends Directory&gt; itemClass) throws InstantiationException, IllegalAccessException { <span class="fc" id="L784"> var result = new ArrayList&lt;Directory&gt;();</span> <span class="fc" id="L786"> for (; ; loader = loader.next())</span> { <span class="fc" id="L788"> final var item = itemClass.newInstance();</span> <span class="fc" id="L789"> item.load(loader);</span> <span class="fc" id="L790"> result.add(item);</span> <span class="fc bfc" id="L792" title="All 2 branches covered."> if (!loader.hasNext())</span> { <span class="fc" id="L794"> break;</span> } } <span class="fc" id="L798"> return result;</span> } /******************************************************************************************************************* * * ******************************************************************************************************************/ private static boolean isSubClass (@Nonnull Class&lt;?&gt; aClass, final @Nonnull String ancestorClassName) { <span class="pc bpc" id="L807" title="1 of 2 branches missed."> for (; aClass != null; aClass = aClass.getSuperclass())</span> { <span class="pc bpc" id="L809" title="1 of 2 branches missed."> if (aClass.getName().equals(ancestorClassName))</span> { <span class="fc" id="L811"> return true;</span> } } <span class="nc" id="L815"> return false;</span> } } </pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.7.202105040129</span></div></body></html>