All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.tidalwave.image.EditableImage Maven / Gradle / Ivy

/***********************************************************************************************************************
 *
 * Mistral - open source imaging engine
 * Copyright (C) 2003-2011 by Tidalwave s.a.s.
 *
 ***********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); 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 "AS IS" 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.
 *
 ***********************************************************************************************************************
 *
 * WWW: http://mistral.tidalwave.it
 * SCM: https://kenai.com/hg/mistral~src
 *
 **********************************************************************************************************************/
package it.tidalwave.image;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import java.io.IOException;
import java.io.Serializable;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import it.tidalwave.image.op.AbstractCreateOp;
import it.tidalwave.image.op.ImplementationFactoryRegistry;
import it.tidalwave.image.op.Operation;
import it.tidalwave.image.op.OperationImplementation;
import it.tidalwave.image.op.ReadOp;
import it.tidalwave.image.op.ScaleOp;
import it.tidalwave.image.java2d.ImplementationFactoryJ2D;
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.WorkaroundBM25;
import it.tidalwave.image.metadata.loader.DirectoryAdapter;
import it.tidalwave.image.metadata.loader.DirectoryDrewAdapter;
import it.tidalwave.image.metadata.loader.DirectoryRawAdapter;
import it.tidalwave.image.metadata.loader.DirectoryTIFFAdapter;
import it.tidalwave.image.metadata.loader.DrewMetadataLoader;
import it.tidalwave.image.metadata.loader.MetadataLoader;
import it.tidalwave.image.metadata.loader.RAWMetadataLoader;
import it.tidalwave.image.metadata.loader.TIFFMetadataLoader;

/***************************************************************************
 *
 * An opaque class which encapsulates all the image manipulation logics,
 * and allows the implementation of these logics to be trasparently changed
 * (e.g. by using or not JAI, etc...)
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 ******************************************************************************/
public class EditableImage implements Cloneable, Serializable // Externalizable
  {
    private static final String CLASS = EditableImage.class.getName();
    private static final Logger logger = Logger.getLogger(CLASS);
    private static final long serialVersionUID = -4527258052032240717L;
    
    private static WorkaroundBM25 workaroundBM25;
    
    public static final String PROP_FORMAT = CLASS + ".format";
    public static final String PROP_MIME_TYPE = CLASS + ".mimeType";

    /** The current image model. */
    private ImageModelHolder imageModelHolder;

    /** The metadata as it comes from Image I/O. */
    private transient IIOMetadata iioMetadata; // TODO make it serializable

    private final Map, List> metadataMapByClass = 
            new HashMap, List>();
    
    /** The attributes, */
    private Map attributeMapByName = new HashMap();
    
    public /*FIXME*/ long latestOperationTime;

    private int latestSerializationSize;

   /***************************************************************************
     *
     *
     **************************************************************************/
    public static enum DataType
      {
        BYTE(DataBuffer.TYPE_BYTE),
        UNSIGNED_SHORT(DataBuffer.TYPE_USHORT),
        SHORT(DataBuffer.TYPE_SHORT),
        INT(DataBuffer.TYPE_INT),
        FLOAT(DataBuffer.TYPE_FLOAT),
        DOUBLE(DataBuffer.TYPE_DOUBLE),
        UNDEFINED(DataBuffer.TYPE_UNDEFINED);

        private final int value;

        /***********************************************************************
         *
         *
         **********************************************************************/
        DataType (int value)
          {
            this.value = value;
          }

        /***********************************************************************
         *
         * Returns the int value of this type, according to DataType constants.
         *
         **********************************************************************/
        public int value()
          {
            return value;
          }

        /***********************************************************************
         *
         * Returns the size in bits of this data type.
         *
         **********************************************************************/
        public int getSize()
          {
            return DataBuffer.getDataTypeSize(value);
          }

        /***********************************************************************
         *
         *
         **********************************************************************/
        public static DataType valueOf (final int value)
          {
            for (final EditableImage.DataType dataType : DataType.values())
              {
                if (dataType.value() == value)
                  {
                    return dataType;
                  }
              }

            return EditableImage.DataType.UNDEFINED;
          }
      }

    /***************************************************************************
     *
     *
     **************************************************************************/
    static
      {
        ImplementationFactoryJ2D.getInstance();

        try
          {
            workaroundBM25 = new WorkaroundBM25();
          }
        catch (Throwable e)
          {
            logger.warning("Workaround for BM25 not enabled because of: " + e);
          }
      }

    /***************************************************************************
     *
     * For serialization only. Do not use.
     *
     **************************************************************************/
    public EditableImage()
      {
        // By default put empty objects for which isAvailable() returns false
        metadataMapByClass.put(TIFF.class, Arrays.asList(new TIFF()));
        metadataMapByClass.put(EXIF.class, Arrays.asList(new EXIF()));
        metadataMapByClass.put(IPTC.class, Arrays.asList(new IPTC()));
        metadataMapByClass.put(MakerNote.class, Arrays.asList(new MakerNote()));
      }

    /***************************************************************************
     *
     * For inner implementation only. Do not use.
     *
     **************************************************************************/
    public EditableImage (final ImageModel imageModel) // FIXME: try to make it protected
      {
        this();
        // null imageModel is accepted for instances carrying only metadata
        imageModelHolder = ImageModelHolder.wrap(imageModel);
      }
    
    /***************************************************************************
     *
     *
     **************************************************************************/
    public void setNickName (final String nickName)
      {
        if (imageModelHolder != null)
          {
            imageModelHolder.setNickName(nickName);
          }
      }
    
    /***************************************************************************
     *
     *
     **************************************************************************/
    public String getNickName()
      {
        return (imageModelHolder != null) ? imageModelHolder.getNickName() : null;  
      }

    /***************************************************************************
     *
     * Creates a new EditableImage as specified by the parameter
     *
     * @param   createOp  the way the image should be created
     * @return            the image
     *
     **************************************************************************/
    public static EditableImage create (final AbstractCreateOp createOp)
      {
        checkNotNull(createOp, "createOp");

        final EditableImage editableImage = new EditableImage(null);
        final Object image = editableImage.internalExecute(createOp);
        final ImageModel imageModel = ImplementationFactoryRegistry.getInstance().createImageModel(image);
        editableImage.imageModelHolder = ImageModelHolder.wrap(imageModel);

        return editableImage;
      }

    /***************************************************************************
     *
     * Reads a new EditableImage as specified by the parameter
     *
     * @param   readOp    the way the image should be read
     * @return            the image
     *
     **************************************************************************/
    // FIXME: merge with create(AbstractCreateOp), introduce ReadJ2DOp
    public static EditableImage create (final ReadOp readOp)
        throws IOException
      {
        checkNotNull(readOp, "readOp");
        return readOp.execute();
      }

    /***************************************************************************
     *
     * Returns true if the image has a raster (EditableImages can be loaded with
     * metadata only).
     *
     * @return  true if the image has a raster
     *
     **************************************************************************/
    public final boolean hasRaster()
      {
        return imageModelHolder.get() != null;
      }

    /***************************************************************************
     *
     * DO NOT USE THIS. This method is only used by the module implementation.
     *
     **************************************************************************/
    public final ImageModel getImageModel()
      {
        return imageModelHolder.get();
      }

    private static boolean availableExtensionsLogged;
    
    /***************************************************************************
     *
     * Returns all the file extensions of file formats that can be read into an
     * EditableImage. The ImageIO registry is called to retrieve the
     * requested information.
     *
     * @return  an array of all file extensions
     *
     ******************************************************************************/
    public static Collection getAvailableExtensions()
      {
        boolean logExtensions;
        
        synchronized (EditableImage.class)
          {
            logExtensions = !availableExtensionsLogged;
            availableExtensionsLogged = true;
          }

        if (logExtensions)
          {
            logger.info("getAvailableExtensions()");
          }

        Set suffixList = new TreeSet();

        for (String formatName : ImageIO.getReaderFormatNames())
          {
            for (Iterator i = ImageIO.getImageReadersByFormatName(formatName); i.hasNext();)
              {
                ImageReader imageReader = i.next();
                ImageReaderSpi originatingProvider = imageReader.getOriginatingProvider();
                String[] suffixes = originatingProvider.getFileSuffixes();
                List suffixesAsList = Arrays.asList(suffixes);
                suffixList.addAll(suffixesAsList);
                
                if (logExtensions)
                  {
                    logger.info(">>>> reader - format name: " + formatName + " provider: "
                        + originatingProvider.getPluginClassName() + " supports " + suffixesAsList);
                  }
              }
          }

        if (logExtensions)
          {
            logger.info(">>>> returning " + suffixList);
          }

        return suffixList;
      }

    /***************************************************************************
     *
     *
     **************************************************************************/
    public final  T getMetadata (final Class metadataClass)
      {
        return getMetadata(metadataClass, 0);
      }
    
    /***************************************************************************
     *
     *
     **************************************************************************/
    public final  T getMetadata (final Class metadataClass, final int index)
      {
        final List objects = (List)metadataMapByClass.get(metadataClass);
        return objects.get(index);
      }
    
    /***************************************************************************
     *
     *
     **************************************************************************/
    public final int getMetadataCount (final Class metadataClass)
      {
        final List objects = metadataMapByClass.get(metadataClass);
        return objects.size();
      }
    
    /***************************************************************************
     *
     * @deprecated Use getMetadata(EXIF.class) instead.
     *
     ******************************************************************************/
    @Deprecated
    public final EXIF getEXIFDirectory()
      {
        return getMetadata(EXIF.class);
      }

    /***************************************************************************
     *
     * @deprecated Use getMetadata(MakerNote.class) instead.
     *
     ******************************************************************************/
    @Deprecated
    public final MakerNote getMakerNote()
      {
        return getMetadata(MakerNote.class);
      }

    /***************************************************************************
     *
     * Returns the width of this image.
     *
     * @return  the width
     *
     ******************************************************************************/
    public final int getWidth()
      {
        return imageModelHolder.get().getWidth();
      }

    /***************************************************************************
     *
     * Returns the height of this image.
     *
     * @return  the height
     *
     ******************************************************************************/
    public final int getHeight()
      {
        return imageModelHolder.get().getHeight();
      }

    /***************************************************************************
     *
     * Returns the dataType used by this image.
     *
     * @return  the data type
     *
     ******************************************************************************/
    public final DataType getDataType()
      {
        return imageModelHolder.get().getDataType();
      }

    /***************************************************************************
     *
     * Returns the number of bands this EditableImage is composed of.
     *
     * @return  the band count
     *
     ******************************************************************************/
    public final int getBandCount()
      {
        return imageModelHolder.get().getBandCount();
      }

    /***************************************************************************
     *
     * Returns the number of sample bits for each band this EditableImage is
     * composed of.
     *
     * @return  the number of bits
     *
     ******************************************************************************/
    public final int getBitsPerBand()
      {
        return getDataType().getSize();
      }

    /***************************************************************************
     *
     * Returns the number of sample bits for each pixel this EditableImage is
     * composed of.
     *
     * @return  the number of bits
     *
     ******************************************************************************/
    public final int getBitsPerPixel()
      {
        return getBandCount() * getBitsPerBand();
      }

    /***************************************************************************
     *
     * Executes an operation. The original image is lost and replaced by results.
     *
     * @param  operation      the operation to perform
     * @return                the operation (as a convenience in case it carries
     *                        results)
     *
     ******************************************************************************/
    public final  T execute (final T operation)
      {
        checkNotNull(operation, "operation");

        long time = System.currentTimeMillis();
        Object image = internalExecute(operation);
        imageModelHolder.get().setImage(image);
        latestOperationTime = System.currentTimeMillis() - time;

        return operation;
      }

    /***************************************************************************
     *
     * Executes an operation. The original image is untouched as the results are
     * placed in a brand new instance of EditableImage.
     *
     * @param  operation  the operation to perform
     * @return            the result
     *
     ******************************************************************************/
    public final EditableImage execute2 (final Operation operation)
      {
        try
          {
            checkNotNull(operation, "operation");

            long time = System.currentTimeMillis();
            Object image = internalExecute(operation);
            Class modelClass = imageModelHolder.get().getClass();
            Constructor constructor = modelClass.getConstructor(Object.class);
            ImageModel newModel = (ImageModel)constructor.newInstance(image);
            EditableImage result = new EditableImage(newModel);
            result.attributeMapByName = new HashMap(attributeMapByName);
            result.latestOperationTime = System.currentTimeMillis() - time;

            return result;
          }
        catch (Exception e)
          {
            throw new RuntimeException(e);
          }
      }

    /***************************************************************************
     *
     * Returns the elapsed time of the latest operation performed. Note that for
     * execute2() this value is available on the result. When an image is
     * deserialized, this method returns the serialization time (this relies upon
     * the fact that the clocks on all network nodes are synchronized).
     *
     * @return  the latest operation elapsed time
     *
     ******************************************************************************/
    public final long getLatestOperationTime ()
      {
        return latestOperationTime;
      }

    /***************************************************************************
     *
     * Creates a similar image, that is a blank image with the same characteristics
     * of this image (width, height, data type, color model).
     * @deprecated will be merged with create(AbstractCreateOp)
     *
     * @return  a new, similar image
     *
     ******************************************************************************/
    public final EditableImage createSimilarImage()
      {
        final EditableImage imageCopy = imageModelHolder.get().createCopy(false);
        imageCopy.attributeMapByName = new HashMap(attributeMapByName);

        return imageCopy;
      }

    /***************************************************************************
     *
     * Clones this image.
     *
     ******************************************************************************/
    public final EditableImage cloneImage()
      {
        final EditableImage imageCopy = imageModelHolder.get().createCopy(true);
        imageCopy.attributeMapByName = new HashMap(attributeMapByName);

        return imageCopy;
      }

    /***************************************************************************
     *
     ******************************************************************************/
    @Override
    public Object clone()
      {
        return cloneImage();
      }

    /***************************************************************************
     *
     * Creates a resized image. - FIXME should be removed
     * @deprecated
     *
     ******************************************************************************/
    public final EditableImage createResizedImage (final int width, final int height)
      {
        return createResizedImage(width, height, Quality.FASTEST);
      }

    /***************************************************************************
     *
     * Creates a resized image. - FIXME should be removed
     * @deprecated
     *
     ******************************************************************************/
    public final EditableImage createResizedImage (final int width, final int height, final Quality quality)
      {
        double hScale = (double)width / (double)getWidth();
        double vScale = (double)height / (double)getHeight();
        ScaleOp scaleOp = new ScaleOp(hScale, vScale, quality);
        execute(scaleOp);

        return this;
      }

    /***************************************************************************
     *
     * Sets an attribute of this image. Attributes are user-specific name-value pairs.
     *
     * @param  name   the attribute name
     * @param  value  the attribute value
     *
     ******************************************************************************/
    public final void setAttribute (final String name, final Object value)
      {
        attributeMapByName.put(name, value);
      }

    /***************************************************************************
     *
     * Returns an attribute of this image.
     *
     * @param  name   the attribute name
     * @return        the attribute value
     *
     ******************************************************************************/
    public final Object getAttribute (final String name)
      {
        return attributeMapByName.get(name);
      }

    /***************************************************************************
     *
     *
     ******************************************************************************/
    public final void setAttributes (final Map attributes)
      {
        attributeMapByName.clear();
        attributeMapByName.putAll(attributes);
      }
    
    /***************************************************************************
     *
     *
     ******************************************************************************/
    public final Map getAttributes()
      {
        // FIXME: no such a thing as CopyOnWriteHashMap
        final Map result = new HashMap();
        result.putAll(attributeMapByName);
        return result;
      }
                
    /***************************************************************************
     *
     * Removes an attribute from this image.
     *
     * @param  name   the attribute name
     * @return        the attribute value
     *
     ******************************************************************************/
    public final Object removeAttribute (final String name)
      {
        return attributeMapByName.remove(name);
      }

    /***************************************************************************
     *
     * Removes all the resources bound to this image.
     *
     ******************************************************************************/
    public final void dispose()
      {
        imageModelHolder.get().dispose();
        imageModelHolder = null;
        attributeMapByName.clear();
      }

    /***************************************************************************
     *
     * Returns an estimate of the memory allocated by this image.
     * 
     * @return  the memory allocated for this image
     *
     ******************************************************************************/
    public final long getMemorySize()
      {
        final ImageModel imageModel = imageModelHolder.get();
        return (imageModel != null) ? imageModel.getMemorySize() : 0;
      }
    
    /***************************************************************************
     *
     * Returns the ColorModel of this image.
     *
     * @return  the color model
     *
     ******************************************************************************/
    public final ColorModel getColorModel() // FIXME: to be removed
      {
        return imageModelHolder.get().getColorModel();
      }

    /***************************************************************************
     *
     * Returns the ICC_Profile of this image (null will be returned if the
     * ColorModel is not ICC-based). Note that this is the profile of the image as
     * it is optimized for the display, which is almost surely sRGB; and it's
     * probably different than the original image profile.
     *
     * @return  the color profile
     *
     ******************************************************************************/
    public final ICC_Profile getICCProfile() // FIXME: to be removed
      {
        final ColorModel colorModel = getColorModel();
        
        if (colorModel != null)
          {
            final ColorSpace colorSpace = colorModel.getColorSpace();

            if (colorSpace instanceof ICC_ColorSpace)
              {
                ICC_ColorSpace iccColorSpace = (ICC_ColorSpace)colorSpace;

                return iccColorSpace.getProfile();
              }
          }

        return null;
      }

    /***************************************************************************
     *
     *
     ******************************************************************************/

    /*
    private final static int COMPRESSED = 1;

    public void writeExternal (ObjectOutput out)
      throws IOException
      {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
    //        GZIPOutputStream gos = new GZIPOutputStream(baos);
        DataOutputStream dos = new DataOutputStream(baos);
        dos.writeUTF(imageModel.getClass().getName());
        imageModel.writeExternal(dos);
        dos.flush();
        dos.close();
        byte[] buffer = baos.toByteArray();

        out.writeLong(System.currentTimeMillis());
        out.writeInt(0);
        out.writeInt(buffer.length);
        out.write(buffer);
        out.flush();
      }*/

    /***************************************************************************
     *
     *
     ******************************************************************************/

    /*
    public void readExternal (ObjectInput in)
      throws IOException, ClassNotFoundException
      {
        long serializationTimeStamp = in.readLong();
        latestOperationTime = System.currentTimeMillis() - serializationTimeStamp;
        int mode = in.readInt();
        int bufferSize = in.readInt();
        byte[] buffer = new byte[bufferSize];
        in.readFully(buffer);
        InputStream is = new ByteArrayInputStream(buffer);

        if (mode == COMPRESSED)
          {
            is = new GZIPInputStream(is);
          }

        DataInputStream dis = new DataInputStream(is);
        String className = dis.readUTF();
        Class clazz = Class.forName(className);
        try
          {
            imageModel = (ImageModel)clazz.newInstance();
          }
        catch (InstantiationException e)
          {
            throw new RuntimeException(e);
          }
        catch (IllegalAccessException e)
          {
            throw new RuntimeException(e);
          }

        imageModel.readExternal(dis);
        dis.close();
        latestSerializationSize = bufferSize;
      }*/
    /***************************************************************************
     *
     * @inheritDoc
     *
     ******************************************************************************/
    public int getLatestSerializationSize ()
      {
        return latestSerializationSize;
      }

    /***************************************************************************
     *
     * @inheritDoc
     *
     ******************************************************************************/
    @Override
    public String toString()
      {
        return "EditableImage[imageModel: " + imageModelHolder + ", attributeMap: " + attributeMapByName + "]";
      }

    /***************************************************************************
     *
     * This is only for testing purposes.
     *
     ******************************************************************************/
    public final  T getInnerProperty (final Class propertyClass)
      {
        if (IIOMetadata.class.equals(propertyClass))
          {
            return (T)iioMetadata;
          }

        return imageModelHolder.get().getInnerProperty(propertyClass);
      }

    /***************************************************************************
     *
     * Executes an operation and return the raw result (the object to be wrapped by
     * the ImageModel).
     *
     * @param   operation  the operation to perform
     * @return             the result (the object wrapped by the ImageModel)
     *
     ******************************************************************************/
    private Object internalExecute (final Operation operation)
        throws UnsupportedOperationException
      {
        final ImplementationFactoryRegistry implementationFactoryRegistry = ImplementationFactoryRegistry.getInstance();
        final ImageModel imageModel = imageModelHolder.get();
        Object image = (imageModel != null) ? imageModel.getImage() : null;
        OperationImplementation implementation = null;

        if ((image == null) && !(operation instanceof AbstractCreateOp))
          {
            throw new RuntimeException("null image with an Op different that AbstractCreateOp");
          }

        try
          {
            implementation = implementationFactoryRegistry.findImplementation(operation, imageModelHolder.get(), false);
          }
        catch (UnsupportedOperationException e)
          {
            logger.warning("No default implementation of " + operation + " for model: " + image);
            implementation = implementationFactoryRegistry.findImplementation(operation, imageModelHolder.get(), true);
            logger.info("Found alternate implementation: " + implementation);

            if (!(operation instanceof AbstractCreateOp))
              {
                if (implementation.getFactory().canConvertFrom(image.getClass()))
                  {
                    logger.info(">>>> CONVERT FROM using " + implementation.getFactory());
                    imageModelHolder = ImageModelHolder.wrap(implementation.getFactory().convertFrom(image));
                    image = imageModelHolder.get().getImage();
                  }

                else if (imageModelHolder.get().getFactory().canConvertTo(implementation.getFactory().getModelClass()))
                  {
                    logger.info(">>>> CONVERT TO " + implementation.getFactory().getModelClass());
                    image = imageModelHolder.get().getFactory().convertTo(implementation.getFactory().getModelClass());
                  }

                else
                  {
                    throw new RuntimeException("Shouldn't get here");
                  }

                logger.info(">>>> NEW IMAGE " + image + " NEW IMAGE MODEL " + imageModelHolder);
              }
          }

        try
          {
            return implementation.execute(this, image);
          }
        catch (RuntimeException e)
          {
            logger.severe("Operation failed, offending image: " + image);
            throw e;
          }
      }

    /***************************************************************************
     *
     * Don't use it. Only for internal implementation.
     *
     **************************************************************************/
    public /*FIXME*/ void loadMetadata (final ImageReader reader, final int imageIndex)
      {
        logger.fine(String.format("loadMetadata(%s, %d)", reader, imageIndex));

        try
          {
            iioMetadata = reader.getImageMetadata(imageIndex);
          }
        catch (Exception e)
          {
            if ("ICC APP2 encountered without prior JFIF!".equals(e.getMessage()) && (workaroundBM25 != null))
              {
                try
                  {
                    workaroundBM25.loadEXIFFromJPEGFile(reader, getMetadata(EXIF.class));
                  }
                catch (Exception e1)
                  {
                    logger.severe("Cannot load EXIF metadata: " + e1);
                    logger.throwing(CLASS, "loadDirectories()", e1);
                  }
              }
            else
              {
                logger.severe("Cannot load EXIF metadata: " + e);
                logger.throwing(CLASS, "loadDirectories()", e);
              }
          }

        if (iioMetadata == null)
          {
            logger.fine(">>>> null imagemetadata");
            return;
          }

        logger.finer(">>>> metadata class: " + iioMetadata.getClass());
        MetadataLoader metadataLoader = null;

        if (isSubClass(iioMetadata.getClass(), "com.sun.media.imageioimpl.plugins.tiff.TIFFImageMetadata"))
          {
            metadataLoader = new TIFFMetadataLoader();
          }
        else if (isSubClass(iioMetadata.getClass(), "it.tidalwave.imageio.raw.RAWMetadataSupport"))
          {
            metadataLoader = new RAWMetadataLoader();
          }
        else
          {
            metadataLoader = new DrewMetadataLoader();
          }

        if (metadataLoader != null)
          {
            try
              {
                loadItem(metadataLoader, TIFF.class);
                loadItem(metadataLoader, EXIF.class);
                loadItem(metadataLoader, MakerNote.class);
                loadItem(metadataLoader, IPTC.class);
              }
            catch (Exception e)
              {
                logger.throwing(CLASS, "loadMetadata()", e);
              }
          }
      }
    
    /***************************************************************************
     *
     *
     **************************************************************************/
    private  void loadItem (final MetadataLoader metadataLoader, final Class itemClass)
      throws InstantiationException, IllegalAccessException 
      {
        final List items = new ArrayList();
        Object node = null;
        
        // FIXME: get rid of the if chain
        if (TIFF.class.equals(itemClass))
          {
            node = metadataLoader.findTIFF(iioMetadata);
          }
        else if (EXIF.class.equals(itemClass))
          {
            node = metadataLoader.findEXIF(iioMetadata);
          }
        else if (IPTC.class.equals(itemClass))
          {
            node = metadataLoader.findIPTC(iioMetadata);
          }
        else if (MakerNote.class.equals(itemClass))
          {
            node = metadataLoader.findMakerNote(iioMetadata);
          }

        if (node != null) 
          {
            for (DirectoryAdapter adapter = findAdapter(node);;) 
              {
                final T item = itemClass.newInstance();
                item.loadFromAdapter(adapter);
                items.add(item);

                if (adapter.hasNext()) // FIXME: put in the for (;;)
                  {
                    adapter = adapter.next();
                  }
                else 
                  {
                    break;
                  }
              }

            metadataMapByClass.put(itemClass, items);
          }
      }
    
    /***************************************************************************
     *
     *
     **************************************************************************/
    private DirectoryAdapter findAdapter (final Object object)
      {
        DirectoryAdapter adapter = null;
        
        // TODO: use a smarterdesign, remove if-else
        if (isSubClass(object.getClass(), "com.sun.media.imageioimpl.plugins.tiff.TIFFIFD"))
          {
            adapter = new DirectoryTIFFAdapter(object);
          }

        else if (isSubClass(object.getClass(), "com.drew.metadata.Directory"))
          {
            adapter = new DirectoryDrewAdapter(object);
          }

        else if (isSubClass(object.getClass(), "com.drew.metadata.exif.ExifDirectory"))
          {
            adapter = new DirectoryDrewAdapter(object);
          }

        else if (isSubClass(object.getClass(), "it.tidalwave.imageio.raw.Directory"))
          {
            adapter = new DirectoryRawAdapter(object);
          }
        
        return adapter;
      }

    /***************************************************************************
     *
     *
     **************************************************************************/
    private static boolean isSubClass (Class aClass, final String ancestorClassName)
      {
        for (; aClass != null; aClass = aClass.getSuperclass())
          {
            if (aClass.getName().equals(ancestorClassName))
              {
                return true;
              }
          }

        return false;
      }

    /***************************************************************************
     *
     *
     **************************************************************************/
    private static void checkNotNull (final Object object, final String name)
      {
        if (object == null)
          {
            throw new IllegalArgumentException("null " + name);
          }
      }
  }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy